diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7a3094a..0000000 --- a/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -Version 2, December 2004 - -Copyright (C) 2004 Sam Hocevar - -Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md deleted file mode 100644 index d179fcf..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Analog_System_Monitor - diff --git a/analog_system_monitor_dotnet/.gitignore b/analog_system_monitor_dotnet/.gitignore new file mode 100644 index 0000000..ea2ff72 --- /dev/null +++ b/analog_system_monitor_dotnet/.gitignore @@ -0,0 +1,55 @@ +# Build outputs +bin/ +obj/ +out/ +publish/ + +# Visual Studio / VS Code +.vs/ +.vscode/ + +# User-specific files +*.user +*.suo +*.userosscache +*.sln.docstates + +# Logs +*.log + +# Temporary files +*.tmp +*.temp +*.cache +*.db +*.db-shm +*.db-wal + +# NuGet +*.nupkg +packages/ +.nuget/ + +# OS-specific +Thumbs.db +ehthumbs.db +Desktop.ini +.DS_Store + +# Rider / JetBrains +.idea/ +*.iml + +# Resharper +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# Single-file publish artifacts +*.pdb +*.deps.json +*.runtimeconfig.json + +# Ignore config if you want it local-only +# Uncomment if you don't want config.json in git +config.json diff --git a/analog_system_monitor_dotnet/Config.cs b/analog_system_monitor_dotnet/Config.cs new file mode 100644 index 0000000..3151f59 --- /dev/null +++ b/analog_system_monitor_dotnet/Config.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Text.Json; + +public class Config +{ + public string OscIp { get; set; } = "127.0.0.1"; + public int OscPort { get; set; } = 9000; + public int UpdateRateMs { get; set; } = 1000; + + private static string ConfigPath => + Path.Combine(AppContext.BaseDirectory, "config.json"); + + public static Config Load() + { + try + { + if (File.Exists(ConfigPath)) + { + string json = File.ReadAllText(ConfigPath); + return JsonSerializer.Deserialize(json) ?? new Config(); + } + } + catch { } + + // If loading fails, create a default config + var cfg = new Config(); + cfg.Save(); + return cfg; + } + + public void Save() + { + try + { + string json = JsonSerializer.Serialize(this, new JsonSerializerOptions + { + WriteIndented = true + }); + File.WriteAllText(ConfigPath, json); + } + catch { } + } +} diff --git a/analog_system_monitor_dotnet/LibreHardwareMonitor.dll b/analog_system_monitor_dotnet/LibreHardwareMonitor.dll new file mode 100644 index 0000000..ec8ee42 Binary files /dev/null and b/analog_system_monitor_dotnet/LibreHardwareMonitor.dll differ diff --git a/analog_system_monitor_dotnet/Program.cs b/analog_system_monitor_dotnet/Program.cs new file mode 100644 index 0000000..f6e0e37 --- /dev/null +++ b/analog_system_monitor_dotnet/Program.cs @@ -0,0 +1,15 @@ +using System; +using System.Windows.Forms; + +namespace analog_system_monitor +{ + internal static class Program + { + [STAThread] + static void Main() + { + ApplicationConfiguration.Initialize(); + Application.Run(new TrayApp()); + } + } +} diff --git a/analog_system_monitor_dotnet/Telemetry.cs b/analog_system_monitor_dotnet/Telemetry.cs new file mode 100644 index 0000000..7a49e36 --- /dev/null +++ b/analog_system_monitor_dotnet/Telemetry.cs @@ -0,0 +1,338 @@ +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; + } +} diff --git a/analog_system_monitor_dotnet/TrayApp.cs b/analog_system_monitor_dotnet/TrayApp.cs new file mode 100644 index 0000000..70a7c4f --- /dev/null +++ b/analog_system_monitor_dotnet/TrayApp.cs @@ -0,0 +1,86 @@ +using System; +using System.Drawing; +using System.Reflection; +using System.Threading; +using System.Windows.Forms; + +namespace analog_system_monitor +{ + public class TrayApp : ApplicationContext + { + private NotifyIcon trayIcon; + private Thread workerThread; + private bool running = true; + + public TrayApp() + { + trayIcon = new NotifyIcon() + { + Icon = LoadEmbeddedIcon(), + Text = "Analog System Monitor", + Visible = true, + ContextMenuStrip = BuildMenu() + }; + + workerThread = new Thread(WorkerLoop) + { + IsBackground = true + }; + workerThread.Start(); + } + + private Icon LoadEmbeddedIcon() + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = "analog_system_monitor.telemetry_icon.ico"; + + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) + { + MessageBox.Show("Embedded icon not found: " + resourceName); + return SystemIcons.Application; + } + + return new Icon(stream); + } + + private ContextMenuStrip BuildMenu() + { + var menu = new ContextMenuStrip(); + + var exitItem = new ToolStripMenuItem("Exit"); + exitItem.Click += (s, e) => ExitApp(); + + menu.Items.Add(exitItem); + return menu; + } + + private void WorkerLoop() + { + var telemetry = new Telemetry(); + telemetry.Initialize(); + + while (running) + { + telemetry.UpdateAndSend(); + Thread.Sleep(telemetry.UpdateRateMs); + } + } + + private void ExitApp() + { + running = false; + + try + { + workerThread?.Join(500); + } + catch { } + + trayIcon.Visible = false; + trayIcon.Dispose(); + + Application.Exit(); + } + } +} diff --git a/analog_system_monitor_dotnet/analog_system_monitor.csproj b/analog_system_monitor_dotnet/analog_system_monitor.csproj new file mode 100644 index 0000000..35a8c0b --- /dev/null +++ b/analog_system_monitor_dotnet/analog_system_monitor.csproj @@ -0,0 +1,23 @@ + + + + WinExe + net10.0-windows + true + enable + enable + true + true + true + None + false + telemetry_icon.ico + + + + + + + + + diff --git a/analog_system_monitor_dotnet/telemetry_icon.ico b/analog_system_monitor_dotnet/telemetry_icon.ico new file mode 100644 index 0000000..2bd2b24 Binary files /dev/null and b/analog_system_monitor_dotnet/telemetry_icon.ico differ