This commit is contained in:
audioprog 2025-08-15 23:40:39 +02:00
commit fb4730effe
13 changed files with 390 additions and 0 deletions

55
.gitignore vendored Normal file
View file

@ -0,0 +1,55 @@
## A streamlined .gitignore for modern .NET projects
## including temporary files, build results, and
## files generated by popular .NET tools. If you are
## developing with Visual Studio, the VS .gitignore
## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
## has more thorough IDE-specific entries.
##
## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# Others
~$*
*~
CodeCoverage/
# MSBuild Binary and Structured Log
*.binlog
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
settings.json

19
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net6.0/HAcontrol.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
}
]
}

14
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "msbuild",
"problemMatcher": [
"$msCompile"
],
"group": "build",
"label": "Build: HAcontrol.csproj",
"detail": "Build the HAcontrol.csproj project using dotnet build"
}
]
}

10
App.axaml Normal file
View file

@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="HAcontrol.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

23
App.axaml.cs Normal file
View file

@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace HAcontrol;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}

22
HAcontrol.csproj Normal file
View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.4" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.4" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.4" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.4">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

24
HAcontrol.sln Normal file
View file

@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HAcontrol", "HAcontrol.csproj", "{3179FA97-84FD-5AFA-A765-7986A9AE24D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3179FA97-84FD-5AFA-A765-7986A9AE24D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3179FA97-84FD-5AFA-A765-7986A9AE24D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3179FA97-84FD-5AFA-A765-7986A9AE24D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3179FA97-84FD-5AFA-A765-7986A9AE24D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {003ED283-95ED-4466-8E3A-44DCDAA21BE1}
EndGlobalSection
EndGlobal

17
MainWindow.axaml Normal file
View file

@ -0,0 +1,17 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="50"
x:Class="HAcontrol.MainWindow"
Title="HAcontrol"
Width="100" Height="50"
MinWidth="100" MinHeight="50"
MaxWidth="100" MaxHeight="50"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="0">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
<Button x:Name="StatusText" Content="Stream" Width="100" Height="50" Click="Button_Click"/>
</StackPanel>
</Window>

98
MainWindow.axaml.cs Normal file
View file

@ -0,0 +1,98 @@
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
namespace HAcontrol;
public partial class MainWindow : Window
{
private System.Threading.CancellationTokenSource? _cts;
public MainWindow()
{
InitializeComponent();
_cts = new System.Threading.CancellationTokenSource();
StartStatePolling(_cts.Token);
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_cts?.Cancel();
_cts?.Dispose();
}
private async void StartStatePolling(System.Threading.CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await GetState();
try
{
await Task.Delay(10000, token);
}
catch (TaskCanceledException)
{
break;
}
}
}
private async void Button_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
string webhookUrl = "/api/webhook/";
string payload = "{\"event\": \"\"}";
// HttpClientHandler to ignore SSL certificate errors (for development only!)
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
using (var client = new HttpClient(handler))
{
var content = new StringContent(payload, Encoding.UTF8, "application/json");
using var _ = await client.PostAsync(webhookUrl, content);
await Task.Delay(100);
}
await GetState();
}
private async Task GetState()
{
// HttpClientHandler to ignore SSL certificate errors (for development only!)
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
using (var client = new HttpClient(handler))
{
string statusUrl = "/api/states/";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "");
using var response = await client.GetAsync(statusUrl);
string statusJson = await response.Content.ReadAsStringAsync();
// Parse the JSON and extract the state
try
{
var jsonDoc = System.Text.Json.JsonDocument.Parse(statusJson);
string? state = jsonDoc.RootElement.GetProperty("state").GetString();
string? entity = jsonDoc.RootElement.GetProperty("entity_id").GetString();
StatusText.Content = $"{state}";
if (state == "on")
StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Green);
else if (state == "off")
StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Red);
else
StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Orange);
}
catch
{
StatusText.Content = "Fehler!";
StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Red);
}
}
}
}

21
Program.cs Normal file
View file

@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace HAcontrol;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

16
SettingsWindow.axaml Normal file
View file

@ -0,0 +1,16 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="HAcontrol.SettingsWindow"
Title="Einstellungen" Width="400" Height="250">
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Home Assistant URL:"/>
<TextBox x:Name="UrlBox"/>
<TextBlock Text="Access Token:"/>
<TextBox x:Name="TokenBox"/>
<TextBlock Text="Webhook ID:"/>
<TextBox x:Name="WebhookBox"/>
<Button Content="Speichern" HorizontalAlignment="Right" Click="Save_Click"/>
</StackPanel>
</Window>

53
SettingsWindow.axaml.cs Normal file
View file

@ -0,0 +1,53 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using System.IO;
using System.Text.Json;
namespace HAcontrol;
public partial class SettingsWindow : Window
{
private const string SettingsFile = "settings.json";
public SettingsWindow()
{
InitializeComponent();
LoadSettings();
}
private void LoadSettings()
{
if (File.Exists(SettingsFile))
{
var json = File.ReadAllText(SettingsFile);
var settings = JsonSerializer.Deserialize<Settings>(json);
if (settings != null)
{
UrlBox.Text = settings.HomeAssistantUrl;
TokenBox.Text = settings.AccessToken;
WebhookBox.Text = settings.WebhookId;
}
}
}
private void Save_Click(object? sender, RoutedEventArgs e)
{
var settings = new Settings
{
HomeAssistantUrl = UrlBox.Text ?? string.Empty,
AccessToken = TokenBox.Text ?? string.Empty,
WebhookId = WebhookBox.Text ?? string.Empty
};
var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettingsFile, json);
this.Close();
}
}
public class Settings
{
public string HomeAssistantUrl { get; set; } = string.Empty;
public string AccessToken { get; set; } = string.Empty;
public string WebhookId { get; set; } = string.Empty;
public string Entity { get; set; } = string.Empty;
}

18
app.manifest Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="HAcontrol.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>