using System; using System.IO; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; namespace HAcontrol; public partial class MainWindow : Window { private Settings? settings = null; private System.Threading.CancellationTokenSource? _cts; private static MainWindow? self; public static MainWindow? Self => self; public bool ShowFader => !string.IsNullOrEmpty(settings?.FaderEntity); private PixelPoint? _windowPosition; public MainWindow() { self = this; InitializeComponent(); Topmost = true; LoadSettings(); // Restore window position if available if (settings != null) { if (settings.WindowTop != 0 || settings.WindowLeft != 0) this.Position = new Avalonia.PixelPoint((int)settings.WindowLeft, (int)settings.WindowTop); } _cts = new System.Threading.CancellationTokenSource(); StartStatePolling(_cts.Token); } public void LoadSettings() { if (!File.Exists(SettingsWindow.SettingsFile)) { // Create default settings file if it does not exist var defaultSettings = new Settings(); var json = JsonSerializer.Serialize(defaultSettings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(SettingsWindow.SettingsFile, json); } { var json = File.ReadAllText(SettingsWindow.SettingsFile); settings = JsonSerializer.Deserialize(json); } FaderControl.IsVisible = ShowFader; var newHeight = ShowFader ? 500 : 100; MaxHeight = newHeight; MinHeight = newHeight; Height = newHeight; } protected override void OnClosed(EventArgs e) { base.OnClosed(e); // Save window position if (settings != null && _windowPosition != null) { settings.WindowTop = _windowPosition.Value.Y; settings.WindowLeft = _windowPosition.Value.X; var json = System.Text.Json.JsonSerializer.Serialize(settings, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); System.IO.File.WriteAllText(SettingsWindow.SettingsFile, json); } _cts?.Cancel(); _cts?.Dispose(); self = null; } 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) { if (settings == null || string.IsNullOrEmpty(settings.HomeAssistantUrl) || string.IsNullOrEmpty(settings.WebhookId)) { StatusText.Content = "Einstellungen fehlen!"; StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Red); return; } string webhookUrl = $"{settings.HomeAssistantUrl}/api/webhook/{settings.WebhookId}"; 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() { _windowPosition = Position; if (settings == null || string.IsNullOrEmpty(settings.HomeAssistantUrl) || string.IsNullOrEmpty(settings.Entity) || string.IsNullOrEmpty(settings.AccessToken)) { StatusText.Content = "Einstellungen fehlen!"; StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Red); return; } try { // 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 = $"{settings.HomeAssistantUrl}/api/states/{settings.Entity}"; client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", settings.AccessToken); using (var response = await client.GetAsync(statusUrl)) { string statusJson = await response.Content.ReadAsStringAsync(); // Parse the JSON and extract the state try { var jsonDoc = 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); } } if (!string.IsNullOrEmpty(settings.FaderEntity)) { statusUrl = $"{settings.HomeAssistantUrl}/api/states/{settings.FaderEntity}"; using (var response = await client.GetAsync(statusUrl)) { string statusJson = await response.Content.ReadAsStringAsync(); // Parse the JSON and extract the state try { // {"entity_id":"number.wing_pp_control_main_4_fader","state":"20.0000002980232","attributes":{"min":0,"max":100,"step":1.0,"mode":"auto","db":"-38.0","low_precision":20.0,"attribution":"","icon":"mdi:volume-source","friendly_name":"Main 4 Fader"},"last_changed":"2025-08-16T14:14:32.455801+00:00","last_reported":"2025-08-16T14:14:32.455801+00:00","last_updated":"2025-08-16T14:14:32.455801+00:00","context":{"id":"01K2SKPTR48X14WGRN6AZDP4Y4","parent_id":null,"user_id":"d548e3619c124b8ead8043c4d6e9223b"}} var jsonDoc = JsonDocument.Parse(statusJson); string? state = jsonDoc.RootElement.GetProperty("state").GetString(); string? entity = jsonDoc.RootElement.GetProperty("entity_id").GetString(); if (double.TryParse(state, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double faderValue)) { FaderControl.Value = faderValue; FaderControl.IsVisible = true; } else { FaderControl.Value = 0.0; FaderControl.IsVisible = false; } } catch { FaderControl.Value = 0.0; FaderControl.IsVisible = false; } } } } } catch (Exception ex) { StatusText.Content = "Fehler!"; StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Red); Console.WriteLine($"Error fetching state: {ex.Message}"); } } private void SettingsButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { var settingsWindow = new SettingsWindow(); settingsWindow.ShowDialog(this); } private async void Fader_ValueChanged(object? sender, RangeBaseValueChangedEventArgs e) { if (settings == null || string.IsNullOrEmpty(settings.HomeAssistantUrl) || string.IsNullOrEmpty(settings.FaderEntity)) { StatusText.Content = "Einstellungen fehlen!"; StatusText.Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Colors.Red); return; } string newState = e.NewValue.ToString(System.Globalization.CultureInfo.InvariantCulture); await ChangeEntityState(settings.FaderEntity, newState); } private async Task ChangeEntityState(string entity, string newState) { if (settings == null || string.IsNullOrEmpty(settings.HomeAssistantUrl) || string.IsNullOrEmpty(settings.AccessToken)) { Console.WriteLine("Einstellungen fehlen!"); return; } var url = $"{settings.HomeAssistantUrl}/api/states/{entity}"; var handler = new HttpClientHandler(); handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; using var client = new HttpClient(handler); // Setze den Authorization-Header client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", settings.AccessToken); // Erstelle den JSON-Inhalt var jsonContent = new { state = newState }; var content = new StringContent(JsonSerializer.Serialize(jsonContent), Encoding.UTF8, "application/json"); try { // Sende die POST-Anfrage HttpResponseMessage response = await client.PostAsync(url, content); // Überprüfe den Statuscode der Antwort if (response.IsSuccessStatusCode) { Console.WriteLine("Status erfolgreich geändert."); } else { Console.WriteLine($"Fehler: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"); } } catch (Exception ex) { Console.WriteLine($"Ein Fehler ist aufgetreten: {ex.Message}"); } } private void FaderControl_PointerWheelChanged(object? sender, Avalonia.Input.PointerWheelEventArgs e) { if (FaderControl.IsVisible && FaderControl.IsEnabled) { double delta = e.Delta.Y > 0 ? 0.5 : -0.5; double newValue = FaderControl.Value + delta; if (newValue < FaderControl.Minimum) newValue = FaderControl.Minimum; if (newValue > FaderControl.Maximum) newValue = FaderControl.Maximum; FaderControl.Value = newValue; } } }