Project initial upload

This commit is contained in:
NinjaPug
2025-03-28 11:31:30 -04:00
parent 9d80c6a125
commit 553849d67f
24 changed files with 6585 additions and 2 deletions

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-PCPalService-11cebed9-d814-495b-a86d-2d912052e92d</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.4" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IO.Ports" Version="9.0.3" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35828.75 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCPalService", "PCPalService.csproj", "{71DA320E-9016-4178-89D7-32DEF26AE4E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{71DA320E-9016-4178-89D7-32DEF26AE4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71DA320E-9016-4178-89D7-32DEF26AE4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71DA320E-9016-4178-89D7-32DEF26AE4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71DA320E-9016-4178-89D7-32DEF26AE4E1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {50DABD9C-9653-4E3A-8800-8A3D4C5CB3BF}
EndGlobalSection
EndGlobal

22
PCPalService/Program.cs Normal file
View File

@@ -0,0 +1,22 @@
using PCPalService;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace PCPalService
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}

View File

@@ -0,0 +1,12 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"PCPalService": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}

192
PCPalService/Worker.cs Normal file
View File

@@ -0,0 +1,192 @@
using System;
using System.IO;
using System.IO.Ports;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware;
using Newtonsoft.Json;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace PCPalService
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private SerialPort serialPort;
private Computer computer;
private readonly string ConfigFile = GetConfigPath();
private const string LogFile = "service_log.txt";
public Worker(ILogger<Worker> logger)
{
_logger = logger;
computer = new Computer { IsCpuEnabled = true, IsMemoryEnabled = true };
computer.Open();
}
private static string GetConfigPath()
{
string folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PCPal");
Directory.CreateDirectory(folder); // Ensure folder exists
return Path.Combine(folder, "config.json");
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Log("ESP32 Background Service Starting...");
try
{
string portName = AutoDetectESP32();
if (portName == null)
{
Log("ESP32-C3 not found. Service stopping...");
throw new Exception("ESP32 not detected.");
}
serialPort = new SerialPort(portName, 115200);
serialPort.Open();
Log($"Connected to ESP32-C3 on {portName}");
while (!stoppingToken.IsCancellationRequested)
{
UpdateLCD();
await Task.Delay(5000, stoppingToken);
}
}
catch (Exception ex)
{
Log($"Fatal Error: {ex.Message}");
Environment.Exit(1); // Force exit, triggering restart
}
}
private string AutoDetectESP32()
{
foreach (string port in SerialPort.GetPortNames())
{
if (IsESP32Device(port)) return port;
}
return null;
}
private bool IsESP32Device(string portName)
{
try
{
using (SerialPort testPort = new SerialPort(portName, 115200))
{
testPort.Open();
testPort.WriteLine("CMD:GET_LCD_TYPE");
Thread.Sleep(500);
string response = testPort.ReadExisting();
testPort.Close();
return response.Contains("LCD_TYPE:1602A");
}
}
catch { return false; }
}
private void UpdateLCD()
{
ConfigData config = LoadConfig();
string line1 = GetLCDContent(config.Line1Selection, config.Line1CustomText, config.Line1PostText);
string line2 = GetLCDContent(config.Line2Selection, config.Line2CustomText, config.Line2PostText);
SendCommand($"CMD:LCD,0,{line1}");
SendCommand($"CMD:LCD,1,{line2}");
}
private string GetLCDContent(string selection, string prefix, string postfix)
{
var parsed = ParseSensorSelection(selection);
if (parsed == null)
return "N/A";
string value = GetSensorValue(parsed.Value.hardwareType, parsed.Value.sensorName, parsed.Value.sensorType);
return $"{prefix}{value}{postfix}";
}
private (HardwareType hardwareType, string sensorName, SensorType sensorType)? ParseSensorSelection(string input)
{
try
{
var parts = input.Split(':', 2);
if (parts.Length != 2) return null;
var hardwareType = Enum.Parse<HardwareType>(parts[0].Trim());
var sensorInfo = parts[1].Trim();
int idx = sensorInfo.LastIndexOf('(');
if (idx == -1) return null;
string name = sensorInfo[..idx].Trim();
string typeStr = sensorInfo[(idx + 1)..].Trim(' ', ')');
var sensorType = Enum.Parse<SensorType>(typeStr);
return (hardwareType, name, sensorType);
}
catch
{
return null;
}
}
private string GetSensorValue(HardwareType type, string name, SensorType sensorType)
{
foreach (IHardware hardware in computer.Hardware)
{
if (hardware.HardwareType == type)
{
hardware.Update();
foreach (ISensor sensor in hardware.Sensors)
{
if (sensor.SensorType == sensorType && sensor.Name == name)
{
return sensor.Value?.ToString("0.0") ?? "N/A";
}
}
}
}
return "N/A";
}
private ConfigData LoadConfig()
{
if (File.Exists(ConfigFile))
{
string json = File.ReadAllText(ConfigFile);
return JsonConvert.DeserializeObject<ConfigData>(json) ?? new ConfigData();
}
return new ConfigData();
}
private void SendCommand(string command)
{
serialPort.WriteLine(command);
Log($"Sent: {command}");
}
private void Log(string message)
{
string logEntry = $"{DateTime.Now}: {message}";
File.AppendAllText(LogFile, logEntry + Environment.NewLine);
_logger.LogInformation(message);
}
}
class ConfigData
{
public string Line1Selection { get; set; }
public string Line1CustomText { get; set; }
public string Line2Selection { get; set; }
public string Line2CustomText { get; set; }
public string Line1PostText { get; set; }
public string Line2PostText { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

10
PCPalService/config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"LastUsedPort": null,
"ScreenType": null,
"Line1Selection": "Cpu: CPU Total (Load)",
"Line1CustomText": "CPU ",
"Line2Selection": "Memory: Memory Used (Data)",
"Line2CustomText": "Memory ",
"Line1PostText": "%",
"Line2PostText": "GB"
}

5543
PCPalService/service_log.txt Normal file

File diff suppressed because it is too large Load Diff