diff --git a/analog_system_monitor_arduino/Command_Summary.txt b/analog_system_monitor_arduino/Command_Summary.txt new file mode 100644 index 0000000..580e20e --- /dev/null +++ b/analog_system_monitor_arduino/Command_Summary.txt @@ -0,0 +1,54 @@ +COMMAND BEHAVIOR SUMMARY +======================== + +PING +---- +Format: + PING + +Response: + Analog_System_Monitor_ + +Purpose: + - Confirms the device is alive + - Allows PC-side auto-discovery + - Returns firmware version + + +SETALL +------ +Format: + SETALL: v0,v1,v2,v3,v4,v5,v6,v7 + +Rules: + - Exactly 8 comma-separated values + - Each value must be numeric + - Each value must be between 0 and 100 + - No extra values allowed + - No missing values allowed + - No trailing commas or extra characters + +Success Response: + OK + +Error Response: + ERROR + +Purpose: + - Updates all 8 channels in one atomic operation + - Applies calibration to each channel + - Resets watchdog timer + + +UNKNOWN / MALFORMED COMMANDS +---------------------------- +Format: + Anything not matching PING or a valid SETALL command + +Response: + ERROR + +Purpose: + - Keeps protocol strict and predictable + - Prevents accidental meter movement + - Simplifies debugging diff --git a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino index 4ecaca5..b2342d2 100644 --- a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino +++ b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino @@ -103,19 +103,68 @@ void loop() { continue; } - int eq = s.indexOf('='); - if (eq > 0) { - int ch = s.substring(0, eq).toInt(); - float val = s.substring(eq + 1).toFloat(); - if (ch >= 0 && ch < NUM_CHANNELS && val >= 0.0f && val <= 100.0f) { + // --- Batch update command --- + if (s.startsWith("SETALL:")) { + String payload = s.substring(7); + payload.trim(); - currentDuty[ch] = val; + float newValues[NUM_CHANNELS]; + int count = 0; + bool error = false; + // Parse up to NUM_CHANNELS values + while (payload.length() > 0 && count < NUM_CHANNELS) { + int comma = payload.indexOf(','); + String part; + + if (comma >= 0) { + part = payload.substring(0, comma); + payload = payload.substring(comma + 1); + } else { + part = payload; + payload = ""; + } + + part.trim(); + if (part.length() == 0) { + error = true; + break; + } + + float val = part.toFloat(); + + if (val < 0.0f || val > 100.0f) { + error = true; + break; + } + + newValues[count++] = val; + } + + // Conditions for a valid SETALL: + // - exactly NUM_CHANNELS values parsed + // - no leftover payload + // - no parse/range errors + if (error || count != NUM_CHANNELS || payload.length() > 0) { + Serial.println("ERROR"); + continue; + } + + // All good → apply values + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + currentDuty[ch] = newValues[ch]; float calibratedDuty = applyCalibration(ch, currentDuty[ch]); pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty); - - lastSerialTime = millis(); // reset watchdog } + + Serial.println("OK"); + lastSerialTime = millis(); + continue; + } + + // --- Unknown command --- + if (s.length() > 0) { + Serial.println("ERROR"); } } diff --git a/analog_system_monitor_dotnet/Config.cs b/analog_system_monitor_dotnet/Config.cs deleted file mode 100644 index 3151f59..0000000 --- a/analog_system_monitor_dotnet/Config.cs +++ /dev/null @@ -1,44 +0,0 @@ -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/Program.cs b/analog_system_monitor_dotnet/Program.cs index f6e0e37..13c4253 100644 --- a/analog_system_monitor_dotnet/Program.cs +++ b/analog_system_monitor_dotnet/Program.cs @@ -1,15 +1,12 @@ using System; using System.Windows.Forms; -namespace analog_system_monitor +internal static class Program { - internal static class Program + [STAThread] + static void Main() { - [STAThread] - static void Main() - { - ApplicationConfiguration.Initialize(); - Application.Run(new TrayApp()); - } + ApplicationConfiguration.Initialize(); + Application.Run(new TrayApp()); } } diff --git a/analog_system_monitor_dotnet/SerialManager.cs b/analog_system_monitor_dotnet/SerialManager.cs new file mode 100644 index 0000000..65937a9 --- /dev/null +++ b/analog_system_monitor_dotnet/SerialManager.cs @@ -0,0 +1,158 @@ +#nullable enable + +using System; +using System.IO.Ports; +using System.Linq; +using System.Threading; + +public class SerialManager : IDisposable +{ + private SerialPort? port; + private DateTime lastResponse = DateTime.UtcNow; + + private const int BaudRate = 115200; + private const int PingTimeoutMs = 300; + private const int WatchdogTimeoutMs = 3000; + + public bool IsConnected => port != null && port.IsOpen; + + // ---------------- DEVICE DISCOVERY ---------------- + + public void DiscoverDevice() + { + DisposePort(); + + foreach (string com in SerialPort.GetPortNames().OrderBy(s => s)) + { + string? response = null; + + try + { + using (var testPort = CreatePort(com, PingTimeoutMs)) + { + testPort.Open(); + Thread.Sleep(350); // RP2040 resets on open + + testPort.DiscardInBuffer(); + testPort.DiscardOutBuffer(); + + testPort.Write("PING\n"); + + try + { + if (testPort.BytesToRead > 0) + response = testPort.ReadLine(); + } + catch (TimeoutException) + { + // No response — move to next port + } + } + + if (response != null && + response.StartsWith("Analog_System_Monitor_")) + { + port = CreatePort(com, 500); + port.Open(); + Thread.Sleep(350); // allow RP2040 reboot again + + lastResponse = DateTime.UtcNow; + return; + } + } + catch + { + // Silent fail — move to next COM port + } + } + + port = null; + } + + // ---------------- PORT FACTORY ---------------- + + private SerialPort CreatePort(string com, int timeout) + { + return new SerialPort(com, BaudRate) + { + Parity = Parity.None, + DataBits = 8, + StopBits = StopBits.One, + Handshake = Handshake.None, + + NewLine = "\n", + Encoding = System.Text.Encoding.ASCII, + + ReadTimeout = timeout, + WriteTimeout = timeout, + + DtrEnable = true, // REQUIRED for RP2040 USB CDC + RtsEnable = true + }; + } + + // ---------------- WATCHDOG ---------------- + + public bool WatchdogExpired => + (DateTime.UtcNow - lastResponse).TotalMilliseconds > WatchdogTimeoutMs; + + // ---------------- SEND COMMAND ---------------- + + public void SendSetAll(string cmd) + { + if (!IsConnected) + return; + + try + { + port!.Write(cmd + "\n"); + + string? response = null; + + try + { + if (port.BytesToRead > 0) + response = port.ReadLine(); + } + catch (TimeoutException) + { + // No response this tick — totally fine + } + + if (response != null && + (response.StartsWith("OK") || response.StartsWith("ERROR"))) + { + lastResponse = DateTime.UtcNow; + } + } + catch + { + // Silent fail — watchdog will trigger reconnect + } + } + + // ---------------- CLEANUP ---------------- + + private void DisposePort() + { + try + { + if (port != null) + { + port.Close(); + port.Dispose(); + } + } + catch + { + // ignore + } + + port = null; + } + + public void Dispose() + { + DisposePort(); + } +} diff --git a/analog_system_monitor_dotnet/Telemetry.cs b/analog_system_monitor_dotnet/Telemetry.cs index 7a49e36..415ea29 100644 --- a/analog_system_monitor_dotnet/Telemetry.cs +++ b/analog_system_monitor_dotnet/Telemetry.cs @@ -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(); 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? 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(); + + 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(); + 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 messages) + public void Dispose() { - 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; + serial.Dispose(); + computer.Close(); } } diff --git a/analog_system_monitor_dotnet/TrayApp.cs b/analog_system_monitor_dotnet/TrayApp.cs index 70a7c4f..5c0d8ed 100644 --- a/analog_system_monitor_dotnet/TrayApp.cs +++ b/analog_system_monitor_dotnet/TrayApp.cs @@ -1,86 +1,41 @@ +#nullable enable + using System; using System.Drawing; -using System.Reflection; -using System.Threading; using System.Windows.Forms; -namespace analog_system_monitor +public class TrayApp : ApplicationContext { - public class TrayApp : ApplicationContext + private NotifyIcon trayIcon; + private Telemetry telemetry; + + public TrayApp() { - private NotifyIcon trayIcon; - private Thread workerThread; - private bool running = true; + telemetry = new Telemetry(); + telemetry.Initialize(); - public TrayApp() + trayIcon = new NotifyIcon() { - trayIcon = new NotifyIcon() - { - Icon = LoadEmbeddedIcon(), - Text = "Analog System Monitor", - Visible = true, - ContextMenuStrip = BuildMenu() - }; + Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath), + Visible = true, + Text = "Telemetry Running" + }; - workerThread = new Thread(WorkerLoop) - { - IsBackground = true - }; - workerThread.Start(); - } + var menu = new ContextMenuStrip(); + menu.Items.Add("Exit", null, OnExit); + trayIcon.ContextMenuStrip = menu; - private Icon LoadEmbeddedIcon() - { - var assembly = Assembly.GetExecutingAssembly(); - var resourceName = "analog_system_monitor.telemetry_icon.ico"; + // Start telemetry loop + var timer = new System.Windows.Forms.Timer(); + timer.Interval = 1000; + timer.Tick += (s, e) => telemetry.UpdateAndSend(); + timer.Start(); + } - 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(); - } + private void OnExit(object? sender, EventArgs e) + { + telemetry.Dispose(); + trayIcon.Visible = false; + Application.Exit(); } } diff --git a/analog_system_monitor_dotnet/analog_system_monitor.csproj b/analog_system_monitor_dotnet/analog_system_monitor.csproj index 35a8c0b..e208a14 100644 --- a/analog_system_monitor_dotnet/analog_system_monitor.csproj +++ b/analog_system_monitor_dotnet/analog_system_monitor.csproj @@ -4,20 +4,29 @@ WinExe net10.0-windows true - enable - enable - true - true - true - None - false - telemetry_icon.ico + + + true + true + + + false + + + false + + + none + false + + telemetry_icon.ico + app.manifest - - + + diff --git a/analog_system_monitor_dotnet/app.manifest b/analog_system_monitor_dotnet/app.manifest new file mode 100644 index 0000000..1a65e34 --- /dev/null +++ b/analog_system_monitor_dotnet/app.manifest @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/analog_system_monitor_dotnet/build-release.ps1 b/analog_system_monitor_dotnet/build-release.ps1 new file mode 100644 index 0000000..b5ea79c --- /dev/null +++ b/analog_system_monitor_dotnet/build-release.ps1 @@ -0,0 +1,53 @@ +# ============================================ +# Build Release Single-File EXE for Analog System Monitor +# Output directory: ./release/ +# ============================================ + +Write-Host "=== Analog System Monitor Release Build ===" -ForegroundColor Cyan + +# Ensure script runs from project directory +$project = "analog_system_monitor.csproj" +if (!(Test-Path $project)) { + Write-Host "Error: Telemetry.csproj not found in this directory." -ForegroundColor Red + exit 1 +} + +# Clean previous builds +Write-Host "Cleaning previous build artifacts..." +dotnet clean -c Release + +# Publish using settings from the .csproj +Write-Host "Publishing Release build..." +dotnet publish -c Release + +# Determine publish folder +$publishDir = Join-Path "bin" "Release\net10.0-windows\win-x64\publish" + +if (!(Test-Path $publishDir)) { + Write-Host "Error: Publish directory not found." -ForegroundColor Red + exit 1 +} + +# Custom output directory +$releaseDir = Join-Path (Get-Location) "release" + +# Create directory if missing +if (!(Test-Path $releaseDir)) { + Write-Host "Creating release directory..." + New-Item -ItemType Directory -Path $releaseDir | Out-Null +} + +# Copy all published files to ./release/ +Write-Host "Copying published files to ./release/ ..." +Copy-Item -Path "$publishDir\*" -Destination $releaseDir -Recurse -Force + +Write-Host "Build completed successfully!" -ForegroundColor Green +Write-Host "Release output: $releaseDir" + +# List produced files +Write-Host "`nRelease files:" +Get-ChildItem $releaseDir | Format-Table Name, Length + +# Open folder in Explorer +Write-Host "`nOpening release folder..." +Start-Process explorer.exe $releaseDir diff --git a/analog_system_monitor_dotnet/release.ps1 b/analog_system_monitor_dotnet/release.ps1 deleted file mode 100644 index 800beed..0000000 --- a/analog_system_monitor_dotnet/release.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -# ============================================ -# Analog System Monitor – Release Script -# Creates a clean, single-file Windows build -# ============================================ - -param( - [string]$Version = "1.0.0" -) - -$ErrorActionPreference = "Stop" - -Write-Host "=== Analog System Monitor Release Script ===" -Write-Host "Version: $Version" -Write-Host "" - -# Paths -$project = "analog_system_monitor.csproj" -$releaseDir = "release\$Version" - -# Clean old release -if (Test-Path $releaseDir) { - Write-Host "Cleaning old release directory..." - Remove-Item -Recurse -Force $releaseDir -} - -# Ensure release directory exists -New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null - -Write-Host "Restoring packages..." -dotnet restore $project - -Write-Host "Cleaning project..." -dotnet clean $project -c Release - -Write-Host "Publishing single-file executable..." -dotnet publish $project ` - -c Release ` - -r win-x64 ` - --self-contained true ` - /p:PublishSingleFile=true ` - /p:IncludeNativeLibrariesForSelfExtract=true ` - /p:DebugType=None ` - /p:DebugSymbols=false ` - /p:Version=$Version ` - -o "$releaseDir" - -Write-Host "" -Write-Host "============================================" -Write-Host " Release build completed successfully" -Write-Host " Output folder: $releaseDir" -Write-Host "============================================"