initial version with serial link
This commit is contained in:
54
analog_system_monitor_arduino/Command_Summary.txt
Normal file
54
analog_system_monitor_arduino/Command_Summary.txt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
COMMAND BEHAVIOR SUMMARY
|
||||||
|
========================
|
||||||
|
|
||||||
|
PING
|
||||||
|
----
|
||||||
|
Format:
|
||||||
|
PING
|
||||||
|
|
||||||
|
Response:
|
||||||
|
Analog_System_Monitor_<version>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -103,19 +103,68 @@ void loop() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int eq = s.indexOf('=');
|
// --- Batch update command ---
|
||||||
if (eq > 0) {
|
if (s.startsWith("SETALL:")) {
|
||||||
int ch = s.substring(0, eq).toInt();
|
String payload = s.substring(7);
|
||||||
float val = s.substring(eq + 1).toFloat();
|
payload.trim();
|
||||||
if (ch >= 0 && ch < NUM_CHANNELS && val >= 0.0f && val <= 100.0f) {
|
|
||||||
|
|
||||||
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]);
|
float calibratedDuty = applyCalibration(ch, currentDuty[ch]);
|
||||||
pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty);
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<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 { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace analog_system_monitor
|
internal static class Program
|
||||||
{
|
{
|
||||||
internal static class Program
|
|
||||||
{
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
ApplicationConfiguration.Initialize();
|
ApplicationConfiguration.Initialize();
|
||||||
Application.Run(new TrayApp());
|
Application.Run(new TrayApp());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
158
analog_system_monitor_dotnet/SerialManager.cs
Normal file
158
analog_system_monitor_dotnet/SerialManager.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,338 +1,242 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Sockets;
|
using System.Globalization;
|
||||||
using System.Text;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using LibreHardwareMonitor.Hardware;
|
using LibreHardwareMonitor.Hardware;
|
||||||
|
|
||||||
public class Telemetry
|
public class Telemetry : IDisposable
|
||||||
{
|
{
|
||||||
private Config config;
|
private const int UpdateRateDefaultMs = 1000;
|
||||||
private string oscIp = "127.0.0.1";
|
public int UpdateRateMs => UpdateRateDefaultMs;
|
||||||
private int oscPort = 9000;
|
|
||||||
|
|
||||||
|
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[] cpuLoadSensors = Array.Empty<ISensor>();
|
||||||
private ISensor? cpuTempSensor;
|
private ISensor? cpuTempSensor;
|
||||||
private ISensor? gpuVramUsedSensor;
|
|
||||||
private ISensor? gpuVramTotalSensor;
|
|
||||||
private ISensor? gpu3DLoadSensor;
|
private ISensor? gpu3DLoadSensor;
|
||||||
private ISensor? gpuTempSensor;
|
private ISensor? gpuTempSensor;
|
||||||
|
private ISensor? gpuVramUsedSensor;
|
||||||
|
private ISensor? gpuVramTotalSensor;
|
||||||
|
|
||||||
private Computer computer = new Computer();
|
private ISensor? memUsedSensor;
|
||||||
private UdpClient udp = new UdpClient();
|
private ISensor? memAvailSensor;
|
||||||
|
|
||||||
// ---------------- INITIALIZATION ----------------
|
private static readonly CultureInfo CI = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
config = Config.Load();
|
serial.DiscoverDevice();
|
||||||
|
|
||||||
// Load defaults from config
|
// Enable only what we need
|
||||||
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.IsCpuEnabled = true;
|
||||||
computer.IsGpuEnabled = true;
|
computer.IsGpuEnabled = true;
|
||||||
|
computer.IsMemoryEnabled = true;
|
||||||
|
|
||||||
|
computer.IsMotherboardEnabled = false;
|
||||||
|
computer.IsControllerEnabled = false;
|
||||||
|
computer.IsNetworkEnabled = false;
|
||||||
|
computer.IsStorageEnabled = false;
|
||||||
|
computer.IsBatteryEnabled = false;
|
||||||
|
computer.IsPsuEnabled = false;
|
||||||
|
|
||||||
computer.Open();
|
computer.Open();
|
||||||
|
|
||||||
DetectSensors();
|
CacheHardwareAndSensors();
|
||||||
}
|
|
||||||
|
|
||||||
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 CacheHardwareAndSensors()
|
||||||
|
|
||||||
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)
|
foreach (var hw in computer.Hardware)
|
||||||
{
|
{
|
||||||
hw.Update();
|
hw.Update();
|
||||||
|
|
||||||
if (hw.HardwareType == HardwareType.Cpu)
|
switch (hw.HardwareType)
|
||||||
{
|
{
|
||||||
foreach (var sensor in hw.Sensors)
|
case HardwareType.Cpu:
|
||||||
{
|
cpuHw = hw;
|
||||||
if (sensor.SensorType == SensorType.Load &&
|
CacheCpuSensors(hw);
|
||||||
sensor.Name.Contains("CPU Core"))
|
break;
|
||||||
cpuLoadList.Add(sensor);
|
|
||||||
|
|
||||||
if (sensor.SensorType == SensorType.Temperature)
|
case HardwareType.GpuNvidia:
|
||||||
{
|
case HardwareType.GpuAmd:
|
||||||
if (sensor.Name == "Core (Tctl/Tdie)")
|
case HardwareType.GpuIntel:
|
||||||
bestCpuTemp = sensor;
|
gpuHw = hw;
|
||||||
else if (bestCpuTemp == null)
|
CacheGpuSensors(hw);
|
||||||
bestCpuTemp = sensor;
|
break;
|
||||||
|
|
||||||
|
case HardwareType.Memory:
|
||||||
|
memHw = hw;
|
||||||
|
CacheMemorySensors(hw);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hw.HardwareType == HardwareType.GpuNvidia ||
|
private void CacheCpuSensors(IHardware hw)
|
||||||
hw.HardwareType == HardwareType.GpuAmd ||
|
|
||||||
hw.HardwareType == HardwareType.GpuIntel)
|
|
||||||
{
|
{
|
||||||
foreach (var sensor in hw.Sensors)
|
var loads = new System.Collections.Generic.List<ISensor>();
|
||||||
|
|
||||||
|
foreach (var s in hw.Sensors)
|
||||||
{
|
{
|
||||||
if (sensor.SensorType == SensorType.Temperature)
|
if (s.SensorType == SensorType.Load &&
|
||||||
|
s.Name.Contains("CPU Core"))
|
||||||
|
loads.Add(s);
|
||||||
|
|
||||||
|
if (s.SensorType == SensorType.Temperature)
|
||||||
{
|
{
|
||||||
if (sensor.Name == "GPU Core")
|
if (s.Name == "Core (Tctl/Tdie)")
|
||||||
bestGpuTemp = sensor;
|
cpuTempSensor = s;
|
||||||
else if (bestGpuTemp == null)
|
else if (cpuTempSensor == null)
|
||||||
bestGpuTemp = sensor;
|
cpuTempSensor = s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sensor.SensorType == SensorType.Load)
|
cpuLoadSensors = loads.ToArray();
|
||||||
{
|
|
||||||
if (sensor.Name == "D3D 3D")
|
|
||||||
bestGpu3D = sensor;
|
|
||||||
else if (bestGpu3D == null && sensor.Name == "GPU Core")
|
|
||||||
bestGpu3D = sensor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sensor.SensorType == SensorType.SmallData)
|
private void CacheGpuSensors(IHardware hw)
|
||||||
{
|
{
|
||||||
if (sensor.Name == "GPU Memory Used")
|
foreach (var s in hw.Sensors)
|
||||||
bestVramUsed = sensor;
|
{
|
||||||
|
if (s.SensorType == SensorType.Load)
|
||||||
if (sensor.Name == "GPU Memory Total")
|
{
|
||||||
bestVramTotal = sensor;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cpuLoadSensors = cpuLoadList.ToArray();
|
private void CacheMemorySensors(IHardware hw)
|
||||||
cpuTempSensor = bestCpuTemp;
|
{
|
||||||
gpuTempSensor = bestGpuTemp;
|
foreach (var s in hw.Sensors)
|
||||||
gpu3DLoadSensor = bestGpu3D;
|
{
|
||||||
gpuVramUsedSensor = bestVramUsed;
|
if (s.SensorType == SensorType.Data && s.Name == "Memory Used")
|
||||||
gpuVramTotalSensor = bestVramTotal;
|
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 ----------------
|
// ---------------- METRICS ----------------
|
||||||
|
|
||||||
private int GetMemoryUsagePercent()
|
private float GetCpuLoadPercent()
|
||||||
{
|
|
||||||
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;
|
if (cpuLoadSensors.Length == 0) return 0;
|
||||||
|
|
||||||
float total = 0;
|
float total = 0;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
foreach (var sensor in cpuLoadSensors)
|
foreach (var s in cpuLoadSensors)
|
||||||
{
|
{
|
||||||
sensor.Hardware.Update();
|
total += s.Value ?? 0;
|
||||||
total += sensor.Value ?? 0;
|
|
||||||
count++;
|
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)
|
float t = cpuTempSensor?.Value ?? 0;
|
||||||
return 0;
|
return Math.Clamp(t, 0, 100);
|
||||||
|
|
||||||
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()
|
private float GetGpu3DLoad()
|
||||||
{
|
{
|
||||||
if (gpu3DLoadSensor == null) return 0;
|
return gpu3DLoadSensor?.Value ?? 0;
|
||||||
|
|
||||||
gpu3DLoadSensor.Hardware.Update();
|
|
||||||
return (int)Math.Round(gpu3DLoadSensor.Value ?? 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetCpuTemperaturePercent()
|
private float GetGpuTemperaturePercent()
|
||||||
{
|
{
|
||||||
if (cpuTempSensor == null) return 0;
|
float t = gpuTempSensor?.Value ?? 0;
|
||||||
|
return Math.Clamp(t, 0, 100);
|
||||||
cpuTempSensor.Hardware.Update();
|
|
||||||
float temp = cpuTempSensor.Value ?? 0;
|
|
||||||
|
|
||||||
return (int)Math.Round(Math.Clamp(temp, 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();
|
if (total <= 0) return 0;
|
||||||
float temp = gpuTempSensor.Value ?? 0;
|
return (used / total) * 100f;
|
||||||
|
|
||||||
return (int)Math.Round(Math.Clamp(temp, 0, 100));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- OSC ----------------
|
private float GetMemoryUsagePercent()
|
||||||
|
|
||||||
private void SendOscBundle(
|
|
||||||
float cpu, float cpuTemp, float mem,
|
|
||||||
float gpu3d, float gpuTemp, float vram)
|
|
||||||
{
|
{
|
||||||
var messages = new List<byte[]>();
|
float used = memUsedSensor?.Value ?? 0;
|
||||||
|
float avail = memAvailSensor?.Value ?? 0;
|
||||||
|
|
||||||
messages.Add(BuildOscFloatMessage("/cpu", cpu));
|
float total = used + avail;
|
||||||
messages.Add(BuildOscFloatMessage("/cputemp", cpuTemp));
|
if (total <= 0) return 0;
|
||||||
messages.Add(BuildOscFloatMessage("/memory", mem));
|
|
||||||
messages.Add(BuildOscFloatMessage("/gpu3d", gpu3d));
|
|
||||||
messages.Add(BuildOscFloatMessage("/gputemp", gpuTemp));
|
|
||||||
messages.Add(BuildOscFloatMessage("/vram", vram));
|
|
||||||
|
|
||||||
byte[] bundle = BuildOscBundle(messages);
|
return (used / total) * 100f;
|
||||||
udp.Send(bundle, bundle.Length, oscIp, oscPort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] BuildOscBundle(List<byte[]> messages)
|
public void Dispose()
|
||||||
{
|
{
|
||||||
List<byte[]> parts = new List<byte[]>();
|
serial.Dispose();
|
||||||
|
computer.Close();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,41 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace analog_system_monitor
|
public class TrayApp : ApplicationContext
|
||||||
{
|
{
|
||||||
public class TrayApp : ApplicationContext
|
|
||||||
{
|
|
||||||
private NotifyIcon trayIcon;
|
private NotifyIcon trayIcon;
|
||||||
private Thread workerThread;
|
private Telemetry telemetry;
|
||||||
private bool running = true;
|
|
||||||
|
|
||||||
public TrayApp()
|
public TrayApp()
|
||||||
{
|
{
|
||||||
trayIcon = new NotifyIcon()
|
telemetry = new Telemetry();
|
||||||
{
|
|
||||||
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();
|
telemetry.Initialize();
|
||||||
|
|
||||||
while (running)
|
trayIcon = new NotifyIcon()
|
||||||
{
|
{
|
||||||
telemetry.UpdateAndSend();
|
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath),
|
||||||
Thread.Sleep(telemetry.UpdateRateMs);
|
Visible = true,
|
||||||
}
|
Text = "Telemetry Running"
|
||||||
|
};
|
||||||
|
|
||||||
|
var menu = new ContextMenuStrip();
|
||||||
|
menu.Items.Add("Exit", null, OnExit);
|
||||||
|
trayIcon.ContextMenuStrip = menu;
|
||||||
|
|
||||||
|
// Start telemetry loop
|
||||||
|
var timer = new System.Windows.Forms.Timer();
|
||||||
|
timer.Interval = 1000;
|
||||||
|
timer.Tick += (s, e) => telemetry.UpdateAndSend();
|
||||||
|
timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExitApp()
|
private void OnExit(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
running = false;
|
telemetry.Dispose();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
workerThread?.Join(500);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
trayIcon.Visible = false;
|
trayIcon.Visible = false;
|
||||||
trayIcon.Dispose();
|
|
||||||
|
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,23 @@
|
|||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net10.0-windows</TargetFramework>
|
<TargetFramework>net10.0-windows</TargetFramework>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
<!-- Single-file EXE -->
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
|
||||||
<DebugType>None</DebugType>
|
<!-- No trimming (LHM + reflection will break) -->
|
||||||
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
|
|
||||||
|
<!-- No compression (avoids startup delays + debugging issues) -->
|
||||||
|
<EnableCompressionInSingleFile>false</EnableCompressionInSingleFile>
|
||||||
|
|
||||||
|
<!-- Keep debugging symbols optional -->
|
||||||
|
<DebugType>none</DebugType>
|
||||||
<DebugSymbols>false</DebugSymbols>
|
<DebugSymbols>false</DebugSymbols>
|
||||||
|
|
||||||
<ApplicationIcon>telemetry_icon.ico</ApplicationIcon>
|
<ApplicationIcon>telemetry_icon.ico</ApplicationIcon>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
35
analog_system_monitor_dotnet/app.manifest
Normal file
35
analog_system_monitor_dotnet/app.manifest
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
|
||||||
|
<assemblyIdentity
|
||||||
|
version="1.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
name="AnalogSystemMonitor"
|
||||||
|
type="win32"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<!-- Force the EXE to always run as administrator -->
|
||||||
|
<requestedExecutionLevel
|
||||||
|
level="requireAdministrator"
|
||||||
|
uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</assembly>
|
||||||
53
analog_system_monitor_dotnet/build-release.ps1
Normal file
53
analog_system_monitor_dotnet/build-release.ps1
Normal file
@@ -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
|
||||||
@@ -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 "============================================"
|
|
||||||
Reference in New Issue
Block a user