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

11
LICENSE
View File

@@ -1,11 +0,0 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

View File

@@ -1,2 +0,0 @@
# Analog_System_Monitor

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