initial version with serial link
This commit is contained in:
@@ -1,338 +1,242 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using LibreHardwareMonitor.Hardware;
|
||||
|
||||
public class Telemetry
|
||||
public class Telemetry : IDisposable
|
||||
{
|
||||
private Config config;
|
||||
private string oscIp = "127.0.0.1";
|
||||
private int oscPort = 9000;
|
||||
private const int UpdateRateDefaultMs = 1000;
|
||||
public int UpdateRateMs => UpdateRateDefaultMs;
|
||||
|
||||
private readonly SerialManager serial = new SerialManager();
|
||||
private readonly Computer computer = new Computer();
|
||||
|
||||
// Cached hardware references
|
||||
private IHardware? cpuHw;
|
||||
private IHardware? gpuHw;
|
||||
private IHardware? memHw;
|
||||
|
||||
// Cached sensors
|
||||
private ISensor[] cpuLoadSensors = Array.Empty<ISensor>();
|
||||
private ISensor? cpuTempSensor;
|
||||
private ISensor? gpuVramUsedSensor;
|
||||
private ISensor? gpuVramTotalSensor;
|
||||
|
||||
private ISensor? gpu3DLoadSensor;
|
||||
private ISensor? gpuTempSensor;
|
||||
private ISensor? gpuVramUsedSensor;
|
||||
private ISensor? gpuVramTotalSensor;
|
||||
|
||||
private Computer computer = new Computer();
|
||||
private UdpClient udp = new UdpClient();
|
||||
private ISensor? memUsedSensor;
|
||||
private ISensor? memAvailSensor;
|
||||
|
||||
// ---------------- INITIALIZATION ----------------
|
||||
private static readonly CultureInfo CI = CultureInfo.InvariantCulture;
|
||||
|
||||
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)
|
||||
public void Initialize()
|
||||
{
|
||||
if (arg.StartsWith("--ip="))
|
||||
oscIp = arg.Substring("--ip=".Length);
|
||||
serial.DiscoverDevice();
|
||||
|
||||
if (arg.StartsWith("--port=") &&
|
||||
int.TryParse(arg.Substring("--port=".Length), out int p))
|
||||
oscPort = p;
|
||||
// Enable only what we need
|
||||
computer.IsCpuEnabled = true;
|
||||
computer.IsGpuEnabled = true;
|
||||
computer.IsMemoryEnabled = true;
|
||||
|
||||
if (arg.StartsWith("--rate=") &&
|
||||
int.TryParse(arg.Substring("--rate=".Length), out int r))
|
||||
config.UpdateRateMs = r;
|
||||
}
|
||||
}
|
||||
computer.IsMotherboardEnabled = false;
|
||||
computer.IsControllerEnabled = false;
|
||||
computer.IsNetworkEnabled = false;
|
||||
computer.IsStorageEnabled = false;
|
||||
computer.IsBatteryEnabled = false;
|
||||
computer.IsPsuEnabled = false;
|
||||
|
||||
computer.Open();
|
||||
|
||||
// ---------------- 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
|
||||
);
|
||||
CacheHardwareAndSensors();
|
||||
}
|
||||
|
||||
// ---------------- SENSOR DETECTION ----------------
|
||||
|
||||
private void DetectSensors()
|
||||
private void CacheHardwareAndSensors()
|
||||
{
|
||||
var cpuLoadList = new List<ISensor>();
|
||||
|
||||
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)
|
||||
switch (hw.HardwareType)
|
||||
{
|
||||
foreach (var sensor in hw.Sensors)
|
||||
{
|
||||
if (sensor.SensorType == SensorType.Load &&
|
||||
sensor.Name.Contains("CPU Core"))
|
||||
cpuLoadList.Add(sensor);
|
||||
case HardwareType.Cpu:
|
||||
cpuHw = hw;
|
||||
CacheCpuSensors(hw);
|
||||
break;
|
||||
|
||||
if (sensor.SensorType == SensorType.Temperature)
|
||||
{
|
||||
if (sensor.Name == "Core (Tctl/Tdie)")
|
||||
bestCpuTemp = sensor;
|
||||
else if (bestCpuTemp == null)
|
||||
bestCpuTemp = sensor;
|
||||
}
|
||||
}
|
||||
case HardwareType.GpuNvidia:
|
||||
case HardwareType.GpuAmd:
|
||||
case HardwareType.GpuIntel:
|
||||
gpuHw = hw;
|
||||
CacheGpuSensors(hw);
|
||||
break;
|
||||
|
||||
case HardwareType.Memory:
|
||||
memHw = hw;
|
||||
CacheMemorySensors(hw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hw.HardwareType == HardwareType.GpuNvidia ||
|
||||
hw.HardwareType == HardwareType.GpuAmd ||
|
||||
hw.HardwareType == HardwareType.GpuIntel)
|
||||
private void CacheCpuSensors(IHardware hw)
|
||||
{
|
||||
var loads = new System.Collections.Generic.List<ISensor>();
|
||||
|
||||
foreach (var s in hw.Sensors)
|
||||
{
|
||||
if (s.SensorType == SensorType.Load &&
|
||||
s.Name.Contains("CPU Core"))
|
||||
loads.Add(s);
|
||||
|
||||
if (s.SensorType == SensorType.Temperature)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (s.Name == "Core (Tctl/Tdie)")
|
||||
cpuTempSensor = s;
|
||||
else if (cpuTempSensor == null)
|
||||
cpuTempSensor = s;
|
||||
}
|
||||
}
|
||||
|
||||
cpuLoadSensors = cpuLoadList.ToArray();
|
||||
cpuTempSensor = bestCpuTemp;
|
||||
gpuTempSensor = bestGpuTemp;
|
||||
gpu3DLoadSensor = bestGpu3D;
|
||||
gpuVramUsedSensor = bestVramUsed;
|
||||
gpuVramTotalSensor = bestVramTotal;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static string F(float v) => v.ToString("0.###", CI);
|
||||
|
||||
public void UpdateAndSend()
|
||||
{
|
||||
if (!serial.IsConnected)
|
||||
{
|
||||
serial.DiscoverDevice();
|
||||
return;
|
||||
}
|
||||
|
||||
if (serial.WatchdogExpired)
|
||||
{
|
||||
serial.DiscoverDevice();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update only the hardware we need
|
||||
cpuHw?.Update();
|
||||
gpuHw?.Update();
|
||||
memHw?.Update();
|
||||
|
||||
float cpu = GetCpuLoadPercent();
|
||||
float cpuTemp = GetCpuTemperaturePercent();
|
||||
float mem = GetMemoryUsagePercent();
|
||||
float gpu3d = GetGpu3DLoad();
|
||||
float gpuTemp = GetGpuTemperaturePercent();
|
||||
float vram = GetGpuVramPercent();
|
||||
|
||||
string cmd =
|
||||
$"SETALL: {F(cpu)},{F(cpuTemp)},{F(mem)},{F(gpu3d)},{F(gpuTemp)},{F(vram)},0,0";
|
||||
|
||||
serial.SendSetAll(cmd);
|
||||
}
|
||||
|
||||
// ---------------- 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()
|
||||
private float GetCpuLoadPercent()
|
||||
{
|
||||
if (cpuLoadSensors.Length == 0) return 0;
|
||||
|
||||
float total = 0;
|
||||
int count = 0;
|
||||
|
||||
foreach (var sensor in cpuLoadSensors)
|
||||
foreach (var s in cpuLoadSensors)
|
||||
{
|
||||
sensor.Hardware.Update();
|
||||
total += sensor.Value ?? 0;
|
||||
total += s.Value ?? 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count == 0 ? 0 : (int)Math.Round(total / count);
|
||||
return count == 0 ? 0 : total / count;
|
||||
}
|
||||
|
||||
private int GetGpuVramPercent()
|
||||
private float GetCpuTemperaturePercent()
|
||||
{
|
||||
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);
|
||||
float t = cpuTempSensor?.Value ?? 0;
|
||||
return Math.Clamp(t, 0, 100);
|
||||
}
|
||||
|
||||
private int GetGpu3DLoad()
|
||||
private float GetGpu3DLoad()
|
||||
{
|
||||
if (gpu3DLoadSensor == null) return 0;
|
||||
|
||||
gpu3DLoadSensor.Hardware.Update();
|
||||
return (int)Math.Round(gpu3DLoadSensor.Value ?? 0);
|
||||
return gpu3DLoadSensor?.Value ?? 0;
|
||||
}
|
||||
|
||||
private int GetCpuTemperaturePercent()
|
||||
private float GetGpuTemperaturePercent()
|
||||
{
|
||||
if (cpuTempSensor == null) return 0;
|
||||
|
||||
cpuTempSensor.Hardware.Update();
|
||||
float temp = cpuTempSensor.Value ?? 0;
|
||||
|
||||
return (int)Math.Round(Math.Clamp(temp, 0, 100));
|
||||
float t = gpuTempSensor?.Value ?? 0;
|
||||
return Math.Clamp(t, 0, 100);
|
||||
}
|
||||
|
||||
private int GetGpuTemperaturePercent()
|
||||
private float GetGpuVramPercent()
|
||||
{
|
||||
if (gpuTempSensor == null) return 0;
|
||||
float used = gpuVramUsedSensor?.Value ?? 0;
|
||||
float total = gpuVramTotalSensor?.Value ?? 0;
|
||||
|
||||
gpuTempSensor.Hardware.Update();
|
||||
float temp = gpuTempSensor.Value ?? 0;
|
||||
|
||||
return (int)Math.Round(Math.Clamp(temp, 0, 100));
|
||||
if (total <= 0) return 0;
|
||||
return (used / total) * 100f;
|
||||
}
|
||||
|
||||
// ---------------- OSC ----------------
|
||||
|
||||
private void SendOscBundle(
|
||||
float cpu, float cpuTemp, float mem,
|
||||
float gpu3d, float gpuTemp, float vram)
|
||||
private float GetMemoryUsagePercent()
|
||||
{
|
||||
var messages = new List<byte[]>();
|
||||
float used = memUsedSensor?.Value ?? 0;
|
||||
float avail = memAvailSensor?.Value ?? 0;
|
||||
|
||||
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));
|
||||
float total = used + avail;
|
||||
if (total <= 0) return 0;
|
||||
|
||||
byte[] bundle = BuildOscBundle(messages);
|
||||
udp.Send(bundle, bundle.Length, oscIp, oscPort);
|
||||
return (used / total) * 100f;
|
||||
}
|
||||
|
||||
private byte[] BuildOscBundle(List<byte[]> messages)
|
||||
public void Dispose()
|
||||
{
|
||||
List<byte[]> parts = new List<byte[]>();
|
||||
|
||||
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;
|
||||
serial.Dispose();
|
||||
computer.Close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user