initial commit

This commit is contained in:
2026-01-16 09:22:10 +01:00
parent 5ae1ebef0e
commit 15b2b881c7
10 changed files with 561 additions and 13 deletions

55
analog_system_monitor_dotnet/.gitignore vendored Normal file
View File

@@ -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

View File

@@ -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<Config>(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 { }
}
}

Binary file not shown.

View File

@@ -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());
}
}
}

View File

@@ -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<ISensor>();
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>();
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<byte[]>();
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<byte[]> messages)
{
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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<DebugType>None</DebugType>
<DebugSymbols>false</DebugSymbols>
<ApplicationIcon>telemetry_icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.5" />
<None Include="telemetry_icon.ico" />
<EmbeddedResource Include="telemetry_icon.ico" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB