From cc85b8a81d4bdc8e8a8e31a6d1f671588346cf50 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:18:15 +0100 Subject: [PATCH 01/45] Fix: missing audio commands due to improper configuration (#39) This PR adds additional configuration without which the "SetAudioOutputCommand" and "SetApplicationVolume" commands were not available within UI. In addition: - it adds ability to configure the device audio name in UI for SetAudioOutputCommand - it adds translations for audio sensors missing in previous versions --- .../HASS.Agent.Satellite.Service.csproj | 6 +-- .../HASS.Agent.Shared/Enums/CommandType.cs | 4 ++ .../HASS.Agent.Shared.csproj | 6 +-- .../Localization/Languages.Designer.cs | 9 ++++ .../Resources/Localization/Languages.de.resx | 6 +++ .../Resources/Localization/Languages.en.resx | 6 +++ .../Resources/Localization/Languages.es.resx | 6 +++ .../Resources/Localization/Languages.fr.resx | 6 +++ .../Resources/Localization/Languages.nl.resx | 6 +++ .../Resources/Localization/Languages.pl.resx | 6 +++ .../Localization/Languages.pt-br.resx | 6 +++ .../Resources/Localization/Languages.resx | 3 ++ .../Resources/Localization/Languages.ru.resx | 6 +++ .../Resources/Localization/Languages.sl.resx | 6 +++ .../Resources/Localization/Languages.tr.resx | 6 +++ .../HASS.Agent/Commands/CommandsManager.cs | 20 ++++++++- .../HASS.Agent/Forms/Commands/CommandsMod.cs | 45 +++++++++++++++++-- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 6 +-- .../Localization/Languages.Designer.cs | 37 +++++++++++++++ .../Resources/Localization/Languages.de.resx | 13 ++++++ .../Resources/Localization/Languages.en.resx | 13 ++++++ .../Resources/Localization/Languages.es.resx | 13 ++++++ .../Resources/Localization/Languages.fr.resx | 13 ++++++ .../Resources/Localization/Languages.nl.resx | 13 ++++++ .../Resources/Localization/Languages.pl.resx | 15 ++++++- .../Localization/Languages.pt-br.resx | 13 ++++++ .../Resources/Localization/Languages.resx | 13 ++++++ .../Resources/Localization/Languages.ru.resx | 13 ++++++ .../Resources/Localization/Languages.sl.resx | 13 ++++++ .../Resources/Localization/Languages.tr.resx | 13 ++++++ .../HASS.Agent/Settings/StoredCommands.cs | 3 ++ 31 files changed, 328 insertions(+), 16 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index 5fbcd2b0..bf279fa5 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 x64 x64;x86 - 2.0.1 + 2.0.2-beta1 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service @@ -17,9 +17,9 @@ https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent hass.png - 2.0.1 + 2.0.2 hass.ico - 2.0.1 + 2.0.2 10.0.17763.0 false diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs index d2b6e148..ee0fa0a0 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs @@ -101,6 +101,10 @@ public enum CommandType [EnumMember(Value = "SetApplicationVolumeCommand")] SetApplicationVolumeCommand, + [LocalizedDescription("CommandType_SetAudioOutputCommand", typeof(Languages))] + [EnumMember(Value = "SetAudioOutputCommand")] + SetAudioOutputCommand, + [LocalizedDescription("CommandType_ShutdownCommand", typeof(Languages))] [EnumMember(Value = "ShutdownCommand")] ShutdownCommand, diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index 9d3b507f..faf7bb08 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -10,9 +10,9 @@ Shared functions and models for the HASS.Agent platform. https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent - 2.0.1 - 2.0.1 - 2.0.1 + 2.0.2 + 2.0.2 + 2.0.2-beta1 logo_128.png True hassagent.ico diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index fac76b2c..b529d8a8 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -1284,6 +1284,15 @@ internal static string CommandType_SetApplicationVolumeCommand { } } + /// + /// Looks up a localized string similar to SetAudioOutputCommand. + /// + internal static string CommandType_SetAudioOutputCommand { + get { + return ResourceManager.GetString("CommandType_SetAudioOutputCommand", resourceCulture); + } + } + /// /// Looks up a localized string similar to SetVolume. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index fcd970f4..883f905a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3352,4 +3352,10 @@ Willst Du den Runtime Installer herunterladen? AktiverDesktop + + Legen Sie die Anwendungslautstärke fest + + + Legen Sie den Audioausgabebefehl fest + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index 4a7ea851..d635f2ae 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3231,4 +3231,10 @@ Do you want to download the runtime installer? ActiveDesktop + + SetApplicationVolume + + + SetAudioOutputCommand + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 1bac329d..b384718b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3228,4 +3228,10 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. EscritorioActivo + + Establecer volumen de aplicación + + + Establecer comando de salida de audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index f78ca843..677293f0 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3261,4 +3261,10 @@ Do you want to download the runtime installer? ActiveDesktop + + Définir le volume d'application + + + Définir la commande de sortie audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 8765dac7..f9248ab5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3250,4 +3250,10 @@ Wil je de runtime installatie downloaden? ActiveDesktop + + Stel het toepassingsvolume in + + + Stel het audio-uitvoercommando in + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index 23625103..563325c4 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3338,4 +3338,10 @@ Czy chcesz pobrać plik instalacyjny? AktywnyPulpit + + Ustaw głośność aplikacji + + + Ustaw wyjście audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index 32ffbfff..ac97d593 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3275,4 +3275,10 @@ Deseja baixar o Microsoft WebView2 runtime? ActiveDesktop + + Definir Volume de Aplicativo + + + Definir comando de saída de áudio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index f033ea56..311f0b79 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -3214,4 +3214,7 @@ Do you want to download the runtime installer? SwitchDesktop + + SetAudioOutputCommand + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index e448d6b5..2d922706 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3296,4 +3296,10 @@ Home Assistant. Активдесктоп + + Установить громкость приложения + + + Установить команду вывода звука + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index d0acb0a5..d9f52478 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3377,4 +3377,10 @@ Ali želite prenesti runtime installer? ActiveDesktop + + Nastavite glasnost aplikacije + + + Nastavite ukaz za avdio izhod + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index 3a6a5395..1b6e9a8c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2835,4 +2835,10 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku AktifMasaüstü + + Uygulama Sesini Ayarla + + + Ses Çıkışı Komutunu Ayarla + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs index 99057184..a0850e70 100644 --- a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs @@ -455,10 +455,10 @@ internal static void LoadCommandInfo() Languages.CommandsManager_SwitchDesktopCommandDescription, true, false, true); - // ================================= - CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.SetVolumeCommand, Languages.CommandsManager_SetVolumeCommandDescription, true, true, true); @@ -467,6 +467,22 @@ internal static void LoadCommandInfo() // ================================= + commandInfoCard = new CommandInfoCard(CommandType.SetApplicationVolumeCommand, + Languages.CommandsManager_SetApplicationVolumeCommandDescription, + true, false, true); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + + commandInfoCard = new CommandInfoCard(CommandType.SetAudioOutputCommand, + Languages.CommandsManager_SetAudioOutputCommandDescription, + true, false, true); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.ShutdownCommand, Languages.CommandsManager_ShutdownCommandDescription, true, true, false); diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs index d279bef5..525bcc87 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs @@ -205,6 +205,7 @@ private void LoadCommand() case CommandType.SetVolumeCommand: case CommandType.SetApplicationVolumeCommand: + case CommandType.SetAudioOutputCommand: TbSetting.Text = Command.Command; break; } @@ -455,6 +456,21 @@ private void BtnStore_Click(object sender, EventArgs e) } break; + case CommandType.SetAudioOutputCommand: + var audioDeviceName = TbSetting.Text.Trim(); + if (string.IsNullOrEmpty(audioDeviceName)) + { + var q = MessageBoxAdv.Show(this, Languages.CommandsMod_BtnStore_MessageBox8, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (q != DialogResult.Yes) + { + ActiveControl = BtnConfigureCommand; + return; + } + } + Command.Command = audioDeviceName; + break; + + case CommandType.WebViewCommand: var webview = TbSetting.Text.Trim(); if (string.IsNullOrEmpty(webview)) @@ -613,6 +629,10 @@ private bool SetType(bool setDefaultValues = true) SetApplicationVolumeUi(); break; + case CommandType.SetAudioOutputCommand: + SetAudioOutputUi(); + break; + case CommandType.RadioCommand: CbConfigDropdown.DataSource = new BindingSource(_radioDevices, null); SetRadioUi(); @@ -795,10 +815,10 @@ private void SetVolumeUi() { SetEmptyGui(); - LblSetting.Text = "volume (between 0 and 100)"; - LblSetting.Visible = true; + LblSetting.Text = Languages.CommandsMod_LblSetting_VolumeRange; + LblSetting.Visible = true; - TbSetting.Text = string.Empty; + TbSetting.Text = string.Empty; TbSetting.Visible = true; })); } @@ -812,7 +832,24 @@ private void SetApplicationVolumeUi() { SetEmptyGui(); - LblSetting.Text = "JSON Command Payload"; + LblSetting.Text = Languages.CommandsMod_LblSetting_JsonPayload; + LblSetting.Visible = true; + + TbSetting.Text = string.Empty; + TbSetting.Visible = true; + })); + } + + /// + /// Change the UI to a 'setaudiooutput' type + /// + private void SetAudioOutputUi() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting.Text = Languages.CommandsMod_LblSetting_AudioDeviceName; LblSetting.Visible = true; TbSetting.Text = string.Empty; diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index ec6feb51..5a943915 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -12,7 +12,7 @@ x64 x64;x86 full - 2.0.1 + 2.0.2-beta1 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. @@ -22,8 +22,8 @@ https://github.com/hass-agent/HASS.Agent MIT app.manifest - 2.0.1 - 2.0.1 + 2.0.2 + 2.0.2 HASS.Agent None win10-x64;win10-x86 diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index e0f8d42d..779cfe72 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -608,6 +608,16 @@ internal static string CommandsManager_SetApplicationVolumeCommandDescription { } } + /// + /// Looks up a localized string similar to Sets the default audio output for the system. + ///Requires audio device name as a payload.. + /// + internal static string CommandsManager_SetAudioOutputCommandDescription { + get { + return ResourceManager.GetString("CommandsManager_SetAudioOutputCommandDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sets the volume of the current default audiodevice to the specified level.. /// @@ -1038,6 +1048,15 @@ internal static string CommandsMod_LblSetting { } } + /// + /// Looks up a localized string similar to Audio Device Name. + /// + internal static string CommandsMod_LblSetting_AudioDeviceName { + get { + return ResourceManager.GetString("CommandsMod_LblSetting_AudioDeviceName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Command. /// @@ -1056,6 +1075,15 @@ internal static string CommandsMod_LblSetting_CommandScript { } } + /// + /// Looks up a localized string similar to JSON Command Payload. + /// + internal static string CommandsMod_LblSetting_JsonPayload { + get { + return ResourceManager.GetString("CommandsMod_LblSetting_JsonPayload", resourceCulture); + } + } + /// /// Looks up a localized string similar to Keycode. /// @@ -1092,6 +1120,15 @@ internal static string CommandsMod_LblSetting_Url { } } + /// + /// Looks up a localized string similar to Volume (between 0 and 100). + /// + internal static string CommandsMod_LblSetting_VolumeRange { + get { + return ResourceManager.GetString("CommandsMod_LblSetting_VolumeRange", resourceCulture); + } + } + /// /// Looks up a localized string similar to HASS.Agent only!. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index be5cc84d..d378941c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3464,4 +3464,17 @@ aus der Originalversion Fehler beim Starten des Satellitendienstes! + + Legt die Standard-Audioausgabe für das System fest. +Erfordert den Namen des Audiogeräts als Nutzlast. + + + Name des Audiogeräts + + + JSON-Befehlsnutzlast + + + Lautstärke (zwischen 0 und 100) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 0baa62cb..23c49a32 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3341,4 +3341,17 @@ from the original version Error communicating with the satellite service! + + Sets the default audio output for the system. +Requires audio device name as a payload. + + + Audio Device Name + + + JSON Command Payload + + + Volume (between 0 and 100) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 1dce77ca..ff26c4b5 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3340,4 +3340,17 @@ HASS.Agent de la versión original ¡Error al iniciar el servicio satelital! + + Establece la salida de audio predeterminada para el sistema. +Requiere el nombre del dispositivo de audio como carga útil. + + + Volumen (entre 0 y 100) + + + Carga útil del comando JSON + + + Nombre del dispositivo de audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 03e69a61..5e3fde18 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3373,4 +3373,17 @@ HASS.Agent de la version originale Erreur lors du démarrage du service satellite ! + + Définit la sortie audio par défaut du système. +Nécessite le nom du périphérique audio comme charge utile. + + + Volume (entre 0 et 100) + + + Charge utile de la commande JSON + + + Nom du périphérique audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 4523dc56..fc9c643e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3361,4 +3361,17 @@ van de originele versie Fout bij starten satellietservice! + + Stelt de standaard audio-uitvoer voor het systeem in. +Vereist de naam van het audioapparaat als payload. + + + Naam audioapparaat + + + JSON-opdrachtpayload + + + Volume (tussen 0 en 100) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 0296c3a4..c2a0e768 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3093,7 +3093,7 @@ Uwaga: wyłączyłeś czyszczenie nazw, więc upewnij się, że nazwa Twojego ur Stara się wybudzić wszystkie monitory poprzez symulowanie wciśnięcia przycisku "do góry". - Ustawia poziom głośności domyślnego urządzenia na podaną wartość. + Ustawia poziom głośności domyślnego urządzenia na podaną wartość. Wprowadź wartość pomiędzy 0-100 jako nowy poziom głośności! @@ -3450,4 +3450,17 @@ z oryginalnej wersji Błąd podczas uruchamiania usługi satelity! + + Ustawia domyślne wyjście audio dla systemu. +Wymaga nazwy urządzenia audio jako ładunku. + + + Głośność (od 0 do 100) + + + Ładunek polecenia JSON + + + Nazwa urządzenia audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 483830cc..16e45e80 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3386,4 +3386,17 @@ HASS.Agent da versão original Erro ao iniciar o serviço de satélite! + + Define a saída de áudio padrão para o sistema. +Requer o nome do dispositivo de áudio como carga útil. + + + Nome do dispositivo de áudio + + + Carga útil do comando JSON + + + Volume (entre 0 e 100) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 81c2b7ab..72abfe05 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3353,4 +3353,17 @@ from the original version Error communicating with the satellite service! + + Sets the default audio output for the system. +Requires audio device name as a payload. + + + Audio Device Name + + + JSON Command Payload + + + Volume (between 0 and 100) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index d9f1ac5d..7aecfda6 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3409,4 +3409,17 @@ Home Assistant. Ошибка при запуске спутниковой службы! + + Устанавливает аудиовыход по умолчанию для системы. +В качестве полезной нагрузки требуется имя аудиоустройства. + + + Громкость (от 0 до 100) + + + Полезная нагрузка команды JSON + + + Имя аудиоустройства + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index c01e258d..0a7b3781 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3489,4 +3489,17 @@ iz izvirne različice Napaka pri zagonu satelitske storitve! + + Nastavi privzeti zvočni izhod za sistem. +Zahteva ime zvočne naprave kot tovor. + + + Ime zvočne naprave + + + Tovor ukazov JSON + + + Glasnost (med 0 in 100) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index f8e566f5..064d8465 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2952,4 +2952,17 @@ taşınması orijinal versiyondan Uydu hizmeti başlatılırken hata oluştu! + + Sistem için varsayılan ses çıkışını ayarlar. +Yük olarak ses cihazı adını gerektirir. + + + Ses Cihazı Adı + + + JSON Komut Yükü + + + Hacim (0 ile 100 arasında) + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs index 7639fc74..c277e876 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs @@ -175,6 +175,9 @@ internal static AbstractCommand ConvertConfiguredToAbstract(ConfiguredCommand co case CommandType.SetApplicationVolumeCommand: abstractCommand = new SetApplicationVolumeCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); break; + case CommandType.SetAudioOutputCommand: + abstractCommand = new SetAudioOutputCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); + break; default: Log.Error("[SETTINGS_COMMANDS] [{name}] Unknown configured command type: {type}", command.EntityName, command.Type.ToString()); break; From 01463dac928d16579d32a7f27a290f9159f2dc8d Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:32:34 +0100 Subject: [PATCH 02/45] updated VirtualDesktop library version and removed unused one (#40) This PR attempts to fix HASS.Agent crash when virtual desktops are switched. Probable root cause was bug in the management library HASS.Agent uses, this PR bumps the dependency version of this library to newest version. --- .../HASS.Agent.Satellite.Service.csproj | 2 +- src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj | 2 +- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index bf279fa5..f8cf3dd8 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 x64 x64;x86 - 2.0.2-beta1 + 2.0.2-beta2 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index faf7bb08..ab356af1 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -12,7 +12,7 @@ https://github.com/hass-agent/HASS.Agent 2.0.2 2.0.2 - 2.0.2-beta1 + 2.0.2-beta2 logo_128.png True hassagent.ico diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index 5a943915..04598222 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -12,7 +12,7 @@ x64 x64;x86 full - 2.0.2-beta1 + 2.0.2-beta2 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. @@ -69,13 +69,12 @@ - + - From 2ac75747bd0f3950dbd8cd0e07ccbc0a1ff32831 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:33:48 +0100 Subject: [PATCH 03/45] Development: 2.1.0-beta1 version change (#46) * updated versions to 2.1.0-beta1 * updated installer version --- src/HASS.Agent.Installer/InstallerScript.iss | 2 +- .../HASS.Agent.Satellite.Service.csproj | 6 +++--- src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj | 6 +++--- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/HASS.Agent.Installer/InstallerScript.iss b/src/HASS.Agent.Installer/InstallerScript.iss index 6b2aa517..8e7190f5 100644 --- a/src/HASS.Agent.Installer/InstallerScript.iss +++ b/src/HASS.Agent.Installer/InstallerScript.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent" -#define MyAppVersion "2.0.1" +#define MyAppVersion "2.1.0-beta1" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.exe" diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index f8cf3dd8..72f396fe 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 x64 x64;x86 - 2.0.2-beta2 + 2.1.0-beta1 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service @@ -17,9 +17,9 @@ https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent hass.png - 2.0.2 + 2.1.0 hass.ico - 2.0.2 + 2.1.0 10.0.17763.0 false diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index ab356af1..092eaffa 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -10,9 +10,9 @@ Shared functions and models for the HASS.Agent platform. https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent - 2.0.2 - 2.0.2 - 2.0.2-beta2 + 2.1.0 + 2.1.0 + 2.1.0-beta1 logo_128.png True hassagent.ico diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index 04598222..bbaf5a51 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -12,7 +12,7 @@ x64 x64;x86 full - 2.0.2-beta2 + 2.1.0-beta1 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. @@ -22,8 +22,8 @@ https://github.com/hass-agent/HASS.Agent MIT app.manifest - 2.0.2 - 2.0.2 + 2.1.0 + 2.1.0 HASS.Agent None win10-x64;win10-x86 From 952d8ec46c29f46cd9e26de1f51d5b2bdacc7b92 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:37:58 +0100 Subject: [PATCH 04/45] Fix: sensor being updated constantly when value doesn't change (#45) This PR fixes issue where the "LastUpdated" value is not updated if sensor value is same as previously. --- .../HomeAssistant/Sensors/PowershellSensor.cs | 92 ++++---- .../AbstractSingleValueSensor.cs | 210 +++++++++--------- 2 files changed, 158 insertions(+), 144 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs index a84e57ea..ccbd2859 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs @@ -5,59 +5,63 @@ using HASS.Agent.Shared.Models.HomeAssistant; using Serilog; -namespace HASS.Agent.Shared.HomeAssistant.Sensors +namespace HASS.Agent.Shared.HomeAssistant.Sensors; + +/// +/// Sensor containing the result of the provided Powershell command or script +/// +public class PowershellSensor : AbstractSingleValueSensor { - /// - /// Sensor containing the result of the provided Powershell command or script - /// - public class PowershellSensor : AbstractSingleValueSensor + private const string DefaultName = "powershellsensor"; + + public string Command { get; private set; } + public bool ApplyRounding { get; private set; } + public int? Round { get; private set; } + + public PowershellSensor(string command, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { - private const string DefaultName = "powershellsensor"; + Command = command; + ApplyRounding = applyRounding; + Round = round; + } - public string Command { get; private set; } - public bool ApplyRounding { get; private set; } - public int? Round { get; private set; } + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + if (Variables.MqttManager == null) + return null; - public PowershellSensor(string command, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) - { - Command = command; - ApplyRounding = applyRounding; - Round = round; - } + var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); + if (deviceConfig == null) + return null; - public override DiscoveryConfigModel GetAutoDiscoveryConfig() - { - if (Variables.MqttManager == null) return null; - - var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); - if (deviceConfig == null) return null; - - return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() - { - EntityName = EntityName, - Name = Name, - Unique_id = Id, - Device = deviceConfig, - State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{EntityName}/state", - Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" - }); - } - - public override string GetState() + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() { - var executed = PowershellManager.ExecuteWithOutput(Command, TimeSpan.FromMinutes(5), out var output, out var errors); - if (!executed) return "error_during_execution"; + EntityName = EntityName, + Name = Name, + Unique_id = Id, + Device = deviceConfig, + State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{EntityName}/state", + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" + }); + } - if (string.IsNullOrWhiteSpace(output) && string.IsNullOrWhiteSpace(errors)) return string.Empty; - if (string.IsNullOrWhiteSpace(output)) return errors.Trim(); + public override string GetState() + { + var executed = PowershellManager.ExecuteWithOutput(Command, TimeSpan.FromMinutes(5), out var output, out var errors); + if (!executed) + return "error_during_execution"; - // optionally apply rounding - if (ApplyRounding && Round != null && double.TryParse(output, out var dblValue)) { output = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); } + if (string.IsNullOrWhiteSpace(output) && string.IsNullOrWhiteSpace(errors)) + return string.Empty; + if (string.IsNullOrWhiteSpace(output)) + return errors.Trim(); - // done - return string.IsNullOrWhiteSpace(errors) ? output : $"{output} | {errors}"; - } + // optionally apply rounding + if (ApplyRounding && Round != null && double.TryParse(output, out var dblValue)) { output = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); } - public override string GetAttributes() => string.Empty; + // done + return string.IsNullOrWhiteSpace(errors) ? output : $"{output} | {errors}"; } + + public override string GetAttributes() => string.Empty; } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs index 63261023..25e41799 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs @@ -3,134 +3,144 @@ using MQTTnet; using Serilog; -namespace HASS.Agent.Shared.Models.HomeAssistant +namespace HASS.Agent.Shared.Models.HomeAssistant; + +/// +/// Abstract singlevalue-sensor from which all singlevalue-sensors are derived +/// +public abstract class AbstractSingleValueSensor : AbstractDiscoverable { - /// - /// Abstract singlevalue-sensor from which all singlevalue-sensors are derived - /// - public abstract class AbstractSingleValueSensor : AbstractDiscoverable - { - public int UpdateIntervalSeconds { get; protected set; } - public DateTime? LastUpdated { get; protected set; } + public int UpdateIntervalSeconds { get; protected set; } + public DateTime? LastUpdated { get; protected set; } - public string PreviousPublishedState { get; protected set; } = string.Empty; - public string PreviousPublishedAttributes { get; protected set; } = string.Empty; + public string PreviousPublishedState { get; protected set; } = string.Empty; + public string PreviousPublishedAttributes { get; protected set; } = string.Empty; - protected AbstractSingleValueSensor(string entityName, string name, int updateIntervalSeconds = 10, string id = default, bool useAttributes = false) - { - Id = id == null || id == Guid.Empty.ToString() ? Guid.NewGuid().ToString() : id; - EntityName = entityName; - Name = name; - UpdateIntervalSeconds = updateIntervalSeconds; - Domain = "sensor"; - UseAttributes = useAttributes; - } + protected AbstractSingleValueSensor(string entityName, string name, int updateIntervalSeconds = 10, string id = default, bool useAttributes = false) + { + Id = id == null || id == Guid.Empty.ToString() ? Guid.NewGuid().ToString() : id; + EntityName = entityName; + Name = name; + UpdateIntervalSeconds = updateIntervalSeconds; + Domain = "sensor"; + UseAttributes = useAttributes; + } - protected SensorDiscoveryConfigModel AutoDiscoveryConfigModel; - protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config) - { - AutoDiscoveryConfigModel = config; - return config; - } + protected SensorDiscoveryConfigModel AutoDiscoveryConfigModel; + protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config) + { + AutoDiscoveryConfigModel = config; + return config; + } - public override void ClearAutoDiscoveryConfig() => AutoDiscoveryConfigModel = null; + public override void ClearAutoDiscoveryConfig() => AutoDiscoveryConfigModel = null; - // nullable in preparation for possible future "nullable enablement" - public abstract string? GetState(); + // nullable in preparation for possible future "nullable enablement" + public abstract string? GetState(); - // nullable in preparation for possible future "nullable enablement" - public abstract string? GetAttributes(); + // nullable in preparation for possible future "nullable enablement" + public abstract string? GetAttributes(); - public void ResetChecks() - { - LastUpdated = DateTime.MinValue; + public void ResetChecks() + { + LastUpdated = DateTime.MinValue; - PreviousPublishedState = string.Empty; - PreviousPublishedAttributes = string.Empty; - } + PreviousPublishedState = string.Empty; + PreviousPublishedAttributes = string.Empty; + } - public async Task PublishStateAsync(bool respectChecks = true) + public async Task PublishStateAsync(bool respectChecks = true) + { + try { - try - { - if (Variables.MqttManager == null) return; + if (Variables.MqttManager == null) + return; - // are we asked to check elapsed time? - if (respectChecks) - { - if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(UpdateIntervalSeconds) > DateTime.Now) return; - } - - // get the current state/attributes - var state = GetState(); - if (state == null) + // are we asked to check elapsed time? + if (respectChecks) + { + if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(UpdateIntervalSeconds) > DateTime.Now) return; + } - var attributes = GetAttributes(); + // get the current state/attributes + var state = GetState(); + if (state == null) + return; - // are we asked to check state changes? - if (respectChecks) + var attributes = GetAttributes(); + + // are we asked to check state changes? + if (respectChecks) + { + if (PreviousPublishedState == state && PreviousPublishedAttributes == attributes) { - if (PreviousPublishedState == state && PreviousPublishedAttributes == attributes) return; + LastUpdated = DateTime.Now; + return; } + } - // fetch the autodiscovery config - var autoDiscoConfig = (SensorDiscoveryConfigModel)GetAutoDiscoveryConfig(); - if (autoDiscoConfig == null) return; - - // prepare the state message - var message = new MqttApplicationMessageBuilder() - .WithTopic(autoDiscoConfig.State_topic) - .WithPayload(state) + // fetch the autodiscovery config + var autoDiscoConfig = (SensorDiscoveryConfigModel)GetAutoDiscoveryConfig(); + if (autoDiscoConfig == null) + return; + + // prepare the state message + var message = new MqttApplicationMessageBuilder() + .WithTopic(autoDiscoConfig.State_topic) + .WithPayload(state) + .WithRetainFlag(Variables.MqttManager.UseRetainFlag()) + .Build(); + + // send it + var published = await Variables.MqttManager.PublishAsync(message); + if (!published) + return; + + // optionally prepare and send attributes + if (UseAttributes && attributes != null) + { + message = new MqttApplicationMessageBuilder() + .WithTopic(autoDiscoConfig.Json_attributes_topic) + .WithPayload(attributes) .WithRetainFlag(Variables.MqttManager.UseRetainFlag()) .Build(); - // send it - var published = await Variables.MqttManager.PublishAsync(message); + published = await Variables.MqttManager.PublishAsync(message); if (!published) - return; // failed, don't store the state - - // optionally prepare and send attributes - if (UseAttributes && attributes != null) - { - message = new MqttApplicationMessageBuilder() - .WithTopic(autoDiscoConfig.Json_attributes_topic) - .WithPayload(attributes) - .WithRetainFlag(Variables.MqttManager.UseRetainFlag()) - .Build(); - - published = await Variables.MqttManager.PublishAsync(message); - if (!published) - return; // failed, don't store the state - } - - // only store the values if the checks are respected - // otherwise, we might stay in 'unknown' state until the value changes - if (!respectChecks) return; + } - PreviousPublishedState = state; - if (attributes != null) - PreviousPublishedAttributes = attributes; + // only store the values if the checks are respected + // otherwise, we might stay in 'unknown' state until the value changes + if (!respectChecks) + return; - LastUpdated = DateTime.Now; - } - catch (Exception ex) - { - Log.Fatal("[SENSOR] [{name}] Error publishing state: {err}", EntityName, ex.Message); - } - } + PreviousPublishedState = state; + if (attributes != null) + PreviousPublishedAttributes = attributes; - public async Task PublishAutoDiscoveryConfigAsync() - { - if (Variables.MqttManager == null) return; - await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain); + LastUpdated = DateTime.Now; } - - public async Task UnPublishAutoDiscoveryConfigAsync() + catch (Exception ex) { - if (Variables.MqttManager == null) return; - await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain, true); + Log.Fatal("[SENSOR] [{name}] Error publishing state: {err}", EntityName, ex.Message); } } + + public async Task PublishAutoDiscoveryConfigAsync() + { + if (Variables.MqttManager == null) + return; + + await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain); + } + + public async Task UnPublishAutoDiscoveryConfigAsync() + { + if (Variables.MqttManager == null) + return; + + await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain, true); + } } From 87887aac22e63be3b3f8ed5fa968f917e00a5599 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:39:35 +0100 Subject: [PATCH 05/45] Feature: screenshot camera sensor (#42) This PR adds screenshot sensor (camera entity) which was originally proposed and implemented by @denisabt in the PR to original HASS.Agent repository LAB02-Research#8 This sensor is supported only for client part of HASS.Agent. --- .../HASS.Agent.Shared/Enums/SensorType.cs | 6 +- .../HASS.Agent.Shared.csproj | 1 + .../SingleValue/ScreenshotSensor.cs | 93 +++++++++++++++++++ .../HomeAssistant/DiscoveryConfigModel.cs | 14 +++ .../Localization/Languages.Designer.cs | 9 ++ .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 3 + .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3 + .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + .../HASS.Agent/Forms/Sensors/SensorsMod.cs | 43 ++++++++- .../Localization/Languages.Designer.cs | 19 ++++ .../Resources/Localization/Languages.de.resx | 7 ++ .../Resources/Localization/Languages.en.resx | 7 ++ .../Resources/Localization/Languages.es.resx | 7 ++ .../Resources/Localization/Languages.fr.resx | 7 ++ .../Resources/Localization/Languages.nl.resx | 7 ++ .../Resources/Localization/Languages.pl.resx | 7 ++ .../Localization/Languages.pt-br.resx | 7 ++ .../Resources/Localization/Languages.resx | 7 ++ .../Resources/Localization/Languages.ru.resx | 7 ++ .../Resources/Localization/Languages.sl.resx | 7 ++ .../Resources/Localization/Languages.tr.resx | 7 ++ .../HASS.Agent/Sensors/SensorsManager.cs | 8 ++ .../HASS.Agent/Settings/StoredSensors.cs | 23 ++++- 31 files changed, 318 insertions(+), 8 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs index bf3b31eb..65348afa 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs @@ -165,6 +165,10 @@ public enum SensorType [LocalizedDescription("SensorType_InternalDeviceSensor", typeof(Languages))] [EnumMember(Value = "InternalDeviceSensor")] - InternalDeviceSensor + InternalDeviceSensor, + + [LocalizedDescription("SensorType_ScreenshotSensor", typeof(Languages))] + [EnumMember(Value = "ScreenshotSensor")] + ScreenshotSensor } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index 092eaffa..504a0bcb 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -42,6 +42,7 @@ + diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs new file mode 100644 index 00000000..0fc98195 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using HASS.Agent.Shared.Models.HomeAssistant; +using Serilog; +using System.Windows.Forms; +using System.Xml.Linq; +using System.Runtime.InteropServices; +using System.Drawing.Imaging; +using System.Text.RegularExpressions; + +namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; +public class ScreenshotSensor : AbstractSingleValueSensor +{ + private const string DefaultName = "screenshot"; + + public int ScreenIndex; + + public ScreenshotSensor(string screenIndex = "0", int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id) + { + ScreenIndex = int.TryParse(screenIndex, out var parsedScreenIndex) ? parsedScreenIndex : 0; + Domain = "camera"; + } + + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + if (Variables.MqttManager == null) + return null; + + var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); + if (deviceConfig == null) + return null; + + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new CameraSensorDiscoveryConfigModel() + { + EntityName = EntityName, + Name = Name, + Unique_id = Id, + Device = deviceConfig, + Image_encoding = "b64", + Icon = "mdi:camera", + State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + Topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability" + }); + } + + public override string GetState() + { + var screenCount = Screen.AllScreens.Length; + if (ScreenIndex >= screenCount || ScreenIndex < 0) + { + Log.Warning("[SCREENSHOT] Wrong index '{index}' - returning image for screen 0", ScreenIndex); + ScreenIndex = 0; + } + + var screenImage = CaptureScreen(ScreenIndex); + return Convert.ToBase64String(screenImage); + } + + private byte[] CaptureScreen(int screenIndex) + { + try + { + return CapturePngFile(Screen.AllScreens[screenIndex]); + } + catch (Exception ex) + { + Log.Error(ex, "[SCREENSHOT] Internal Error capturing screen {index}, {ex}", ex.Message); + return Array.Empty(); + } + } + + private byte[] CapturePngFile(Screen screen) + { + var captureRectangle = screen.Bounds; + var captureBitmap = new Bitmap(captureRectangle.Width, captureRectangle.Height, PixelFormat.Format32bppArgb); + var captureGraphics = Graphics.FromImage(captureBitmap); + captureGraphics.CopyFromScreen(captureRectangle.Left, captureRectangle.Top, 0, 0, captureRectangle.Size); + + using var ms = new MemoryStream(); + captureBitmap.Save(ms, ImageFormat.Png); + + return ms.ToArray(); + } + + public override string GetAttributes() => string.Empty; +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs index 68c4d4a6..4a98233b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs @@ -143,6 +143,20 @@ public string Object_id public string Value_template { get; set; } } + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class CameraSensorDiscoveryConfigModel : SensorDiscoveryConfigModel + { + /// + /// Messages published to this topic need to contain full contents of an image + /// + public string Topic { get; set; } + + /// + /// (Optional) The encoding of the image payloads received. + /// + public string Image_encoding { get; set; } + } + [SuppressMessage("ReSharper", "InconsistentNaming")] public class CommandDiscoveryConfigModel : DiscoveryConfigModel { diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index b529d8a8..16f7dcde 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -6735,6 +6735,15 @@ internal static string SensorType_ProcessActiveSensor { } } + /// + /// Looks up a localized string similar to Screenshot. + /// + internal static string SensorType_ScreenshotSensor { + get { + return ResourceManager.GetString("SensorType_ScreenshotSensor", resourceCulture); + } + } + /// /// Looks up a localized string similar to ServiceState. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index 883f905a..c20b1ed0 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3358,4 +3358,7 @@ Willst Du den Runtime Installer herunterladen? Legen Sie den Audioausgabebefehl fest + + Bildschirmfoto + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index d635f2ae..b7f04ec8 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3237,4 +3237,7 @@ Do you want to download the runtime installer? SetAudioOutputCommand + + Screenshot + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index b384718b..37db7723 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3234,4 +3234,7 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. Establecer comando de salida de audio + + Captura de pantalla + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index 677293f0..4dfc6e87 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3267,4 +3267,7 @@ Do you want to download the runtime installer? Définir la commande de sortie audio + + Capture d'écran + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index f9248ab5..6c01199c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3256,4 +3256,7 @@ Wil je de runtime installatie downloaden? Stel het audio-uitvoercommando in + + Schermafbeelding + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index 563325c4..cfeff95d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3344,4 +3344,7 @@ Czy chcesz pobrać plik instalacyjny? Ustaw wyjście audio + + Zrzut ekranu + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index ac97d593..8e8420d5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3281,4 +3281,7 @@ Deseja baixar o Microsoft WebView2 runtime? Definir comando de saída de áudio + + Captura de tela + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index 311f0b79..ed3f3224 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -3217,4 +3217,7 @@ Do you want to download the runtime installer? SetAudioOutputCommand + + Screenshot + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index 2d922706..fac40f1d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3302,4 +3302,7 @@ Home Assistant. Установить команду вывода звука + + Скриншот + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index d9f52478..7a2426ef 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3383,4 +3383,7 @@ Ali želite prenesti runtime installer? Nastavite ukaz za avdio izhod + + Posnetek zaslona + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index 1b6e9a8c..f5cfa8d2 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2841,4 +2841,7 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku Ses Çıkışı Komutunu Ayarla + + Ekran görüntüsü + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs index ba5c019d..987cbad3 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs @@ -228,7 +228,11 @@ private void LoadSensor() CbApplyRounding.Checked = Sensor.ApplyRounding; NumRound.Text = Sensor.Round?.ToString() ?? LastActiveSensor.DefaultTimeWindow.ToString(); ; break; - } + + case SensorType.ScreenshotSensor: + TbSetting1.Text = Sensor.Query; + break; + } } /// @@ -327,6 +331,10 @@ private bool SetType(bool setDefaultValues = true) SetLastActiveGui(); break; + case SensorType.ScreenshotSensor: + SetScreenshotGui(); + break; + default: SetEmptyGui(); break; @@ -528,10 +536,24 @@ private void SetLastActiveGui() })); } - /// - /// Change the UI to a general type - /// - private void SetEmptyGui() + private void SetScreenshotGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_ScreenNumber; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + BtnTest.Visible = false; + })); + } + + /// + /// Change the UI to a general type + /// + private void SetEmptyGui() { Invoke(new MethodInvoker(delegate { @@ -799,6 +821,17 @@ private void BtnStore_Click(object sender, EventArgs e) return; } break; + + case SensorType.ScreenshotSensor: + var screenIndex = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(screenIndex)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = screenIndex; + break; } // set values diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index 779cfe72..8a43773e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -6095,6 +6095,16 @@ internal static string SensorsManager_ProcessActiveSensorDescription { } } + /// + /// Looks up a localized string similar to Provides a screenshot sensor in form of a camera entity. + ///Screen number depends on system configuration - starts at 0.. + /// + internal static string SensorsManager_ScreenshotSensorDescription { + get { + return ResourceManager.GetString("SensorsManager_ScreenshotSensorDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Returns the state of the provided service: NotFound, Stopped, StartPending, StopPending, Running, ContinuePending, PausePending or Paused. /// @@ -6520,6 +6530,15 @@ internal static string SensorsMod_LblSetting1_Process { } } + /// + /// Looks up a localized string similar to Screen number. + /// + internal static string SensorsMod_LblSetting1_ScreenNumber { + get { + return ResourceManager.GetString("SensorsMod_LblSetting1_ScreenNumber", resourceCulture); + } + } + /// /// Looks up a localized string similar to Service. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index d378941c..376c1f7c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3477,4 +3477,11 @@ Erfordert den Namen des Audiogeräts als Nutzlast. Lautstärke (zwischen 0 und 100) + + Sie stellen einen Screenshot-Sensor in Form einer Kameraeinheit bereit. +Die Bildschirmnummer hängt von der Systemkonfiguration ab – beginnt bei 0. + + + Bildschirmnummer + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 23c49a32..6e767239 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3354,4 +3354,11 @@ Requires audio device name as a payload. Volume (between 0 and 100) + + Screen number + + + Provides a screenshot sensor in form of a camera entity. +Screen number depends on system configuration - starts at 0. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index ff26c4b5..89b491b3 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3353,4 +3353,11 @@ Requiere el nombre del dispositivo de audio como carga útil. Nombre del dispositivo de audio + + Proporciona un sensor de captura de pantalla en forma de entidad de cámara. +El número de pantalla depende de la configuración del sistema: comienza en 0. + + + Número de pantalla + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 5e3fde18..ffe6fbe4 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3386,4 +3386,11 @@ Nécessite le nom du périphérique audio comme charge utile. Nom du périphérique audio + + Vous fournissez un capteur de capture d’écran sous la forme d’une entité caméra. +Le numéro d'écran dépend de la configuration du système - commence à 0. + + + Numéro d'écran + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index fc9c643e..7629e0fd 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3374,4 +3374,11 @@ Vereist de naam van het audioapparaat als payload. Volume (tussen 0 en 100) + + U levert een screenshot-sensor in de vorm van een camera-entiteit. +Schermnummer is afhankelijk van de systeemconfiguratie - begint bij 0. + + + Schermnummer + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index c2a0e768..0f937e96 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3463,4 +3463,11 @@ Wymaga nazwy urządzenia audio jako ładunku. Nazwa urządzenia audio + + Czujnik zrzutu ekranu w postaci kamery. +Numer ekranu zależy od konfiguracji systemu – zaczyna się od 0. + + + Numer ekranu + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 16e45e80..27b31828 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3399,4 +3399,11 @@ Requer o nome do dispositivo de áudio como carga útil. Volume (entre 0 e 100) + + Você fornece um sensor de captura de tela na forma de uma entidade de câmera. +O número da tela depende da configuração do sistema – começa em 0. + + + Número da tela + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 72abfe05..8cf4f2a9 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3366,4 +3366,11 @@ Requires audio device name as a payload. Volume (between 0 and 100) + + Screen number + + + Provides a screenshot sensor in form of a camera entity. +Screen number depends on system configuration - starts at 0. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 7aecfda6..8846437c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3422,4 +3422,11 @@ Home Assistant. Имя аудиоустройства + + Вы предоставляете датчик скриншота в виде объекта камеры. +Номер экрана зависит от конфигурации системы — начинается с 0. + + + Номер экрана + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 0a7b3781..e32b3a8c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3502,4 +3502,11 @@ Zahteva ime zvočne naprave kot tovor. Glasnost (med 0 in 100) + + Zagotovite senzor posnetka zaslona v obliki entitete kamere. +Številka zaslona je odvisna od konfiguracije sistema - začne se pri 0. + + + Številka zaslona + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index 064d8465..ebd9375f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2965,4 +2965,11 @@ Yük olarak ses cihazı adını gerektirir. Hacim (0 ile 100 arasında) + + Bir kamera varlığı biçiminde bir ekran görüntüsü sensörü sağlarsınız. +Ekran numarası sistem konfigürasyonuna bağlıdır - 0'dan başlar. + + + Ekran numarası + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs index e01731c8..0f5f4eba 100644 --- a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs @@ -661,6 +661,14 @@ internal static void LoadSensorInfo() SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); // ================================= + + sensorInfoCard = new SensorInfoCard(SensorType.ScreenshotSensor, + Languages.SensorsManager_ScreenshotSensorDescription, + 10, false, true, false); + + SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); + + // ================================= } } } diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs index 6dce967d..8c908fd2 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs @@ -3,6 +3,7 @@ using HASS.Agent.Extensions; using HASS.Agent.HomeAssistant.Sensors.GeneralSensors.MultiValue; using HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue; +using HASS.Agent.Managers.DeviceSensors; using HASS.Agent.Resources.Localization; using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Extensions; @@ -196,7 +197,10 @@ internal static AbstractSingleValueSensor ConvertConfiguredToAbstractSingleValue abstractSensor = new BluetoothLeDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); break; case SensorType.InternalDeviceSensor: - abstractSensor = new InternalDeviceSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.ScreenshotSensor: + abstractSensor = new ScreenshotSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); break; default: Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured single-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); @@ -389,7 +393,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract }; } - case InternalDeviceSensor internalDeviceSensor: + case HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor internalDeviceSensor: { _ = Enum.TryParse(internalDeviceSensor.GetType().Name, out var type); return new ConfiguredSensor @@ -404,6 +408,21 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract }; } + case ScreenshotSensor screenshotSensor: + { + _ = Enum.TryParse(screenshotSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(screenshotSensor.Id), + EntityName = screenshotSensor.EntityName, + Name = screenshotSensor.Name, + Type = type, + UpdateInterval = screenshotSensor.UpdateIntervalSeconds, + IgnoreAvailability = screenshotSensor.IgnoreAvailability, + Query = screenshotSensor.ScreenIndex.ToString() + }; + } + default: { _ = Enum.TryParse(sensor.GetType().Name, out var type); From 7f6b3f536f6c9ad231d7623360b8f57e5946e4f1 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:25:37 +0100 Subject: [PATCH 06/45] Feature: set audio input command (#41) This PR adds "SetAudioInputCommand" requested by Chrisdegod. In current state both default audio input and default communications device is set. Supports setting device configured within UI as well as sent as a payload on the command topic. --- .../HASS.Agent.Shared/Enums/CommandType.cs | 4 ++ .../InternalCommands/SetAudioInputCommand.cs | 67 +++++++++++++++++++ .../Localization/Languages.Designer.cs | 9 +++ .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 6 +- .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3 + .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + .../HASS.Agent/Commands/CommandsManager.cs | 8 +++ .../HASS.Agent/Forms/Commands/CommandsMod.cs | 3 + .../Localization/Languages.Designer.cs | 10 +++ .../Resources/Localization/Languages.de.resx | 4 ++ .../Resources/Localization/Languages.en.resx | 4 ++ .../Resources/Localization/Languages.es.resx | 4 ++ .../Resources/Localization/Languages.fr.resx | 4 ++ .../Resources/Localization/Languages.nl.resx | 4 ++ .../Resources/Localization/Languages.pl.resx | 4 ++ .../Localization/Languages.pt-br.resx | 4 ++ .../Resources/Localization/Languages.resx | 4 ++ .../Resources/Localization/Languages.ru.resx | 4 ++ .../Resources/Localization/Languages.sl.resx | 4 ++ .../Resources/Localization/Languages.tr.resx | 4 ++ .../HASS.Agent/Settings/StoredCommands.cs | 3 + 29 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs index ee0fa0a0..e92db473 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs @@ -105,6 +105,10 @@ public enum CommandType [EnumMember(Value = "SetAudioOutputCommand")] SetAudioOutputCommand, + [LocalizedDescription("CommandType_SetAudioInputCommand", typeof(Languages))] + [EnumMember(Value = "SetAudioInputCommand")] + SetAudioInputCommand, + [LocalizedDescription("CommandType_ShutdownCommand", typeof(Languages))] [EnumMember(Value = "ShutdownCommand")] ShutdownCommand, diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs new file mode 100644 index 00000000..292b09ba --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs @@ -0,0 +1,67 @@ +using CoreAudio; +using HASS.Agent.Shared.Enums; +using Newtonsoft.Json; +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands; + +public class SetAudioInputCommand : InternalCommand +{ + private const string DefaultName = "setaudioinput"; + + private string InputDevice { get => CommandConfig; } + + public SetAudioInputCommand(string entityName = DefaultName, string name = DefaultName, string audioDevice = "", CommandEntityType entityType = CommandEntityType.Button, string id = default) : base(entityName ?? DefaultName, name ?? null, audioDevice, entityType, id) + { + State = "OFF"; + } + + public override void TurnOn() + { + if (string.IsNullOrWhiteSpace(InputDevice)) + { + Log.Error("[SETAUDIOIN] Error, input device name cannot be null/blank"); + + return; + } + + TurnOnWithAction(InputDevice); + } + + private MMDevice GetAudioDeviceOrDefault(string playbackDeviceName) + { + var devices = Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active); + var playbackDevice = devices.Where(d => d.DeviceFriendlyName == playbackDeviceName).FirstOrDefault(); + + return playbackDevice ?? Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications); + } + + public override void TurnOnWithAction(string action) + { + State = "ON"; + + try + { + var outputDevice = GetAudioDeviceOrDefault(action); + if (outputDevice == Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications)) + return; + + outputDevice.Selected = true; + } + catch (Exception ex) + { + Log.Error("[SETAUDIOIN] Error while processing action '{action}': {err}", action, ex.Message); + } + finally + { + State = "OFF"; + } + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index 16f7dcde..be913c63 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -1284,6 +1284,15 @@ internal static string CommandType_SetApplicationVolumeCommand { } } + /// + /// Looks up a localized string similar to SetAudioInputCommand. + /// + internal static string CommandType_SetAudioInputCommand { + get { + return ResourceManager.GetString("CommandType_SetAudioInputCommand", resourceCulture); + } + } + /// /// Looks up a localized string similar to SetAudioOutputCommand. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index c20b1ed0..736f2957 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3361,4 +3361,7 @@ Willst Du den Runtime Installer herunterladen? Bildschirmfoto + + Legen Sie den Audioeingabebefehl fest + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index b7f04ec8..decc96d4 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3225,9 +3225,6 @@ Do you want to download the runtime installer? InternalDeviceSensor - - SwitchDesktop - ActiveDesktop @@ -3240,4 +3237,7 @@ Do you want to download the runtime installer? Screenshot + + SetAudioInputCommand + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 37db7723..58a31529 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3237,4 +3237,7 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. Captura de pantalla + + Establecer comando de entrada de audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index 4dfc6e87..32f1ca61 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3270,4 +3270,7 @@ Do you want to download the runtime installer? Capture d'écran + + Définir la commande d'entrée audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 6c01199c..664534da 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3259,4 +3259,7 @@ Wil je de runtime installatie downloaden? Schermafbeelding + + Stel het audio-invoercommando in + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index cfeff95d..c6ffb694 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3347,4 +3347,7 @@ Czy chcesz pobrać plik instalacyjny? Zrzut ekranu + + Ustaw wejście audio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index 8e8420d5..76c7b10d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3284,4 +3284,7 @@ Deseja baixar o Microsoft WebView2 runtime? Captura de tela + + Definir comando de entrada de áudio + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index ed3f3224..94dad3f7 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -3220,4 +3220,7 @@ Do you want to download the runtime installer? Screenshot + + SetAudioInputCommand + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index fac40f1d..758aa8bd 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3305,4 +3305,7 @@ Home Assistant. Скриншот + + Установить команду аудиовхода + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index 7a2426ef..77cf6153 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3386,4 +3386,7 @@ Ali želite prenesti runtime installer? Posnetek zaslona + + Nastavite ukaz za zvočni vhod + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index f5cfa8d2..e2c8d79d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2844,4 +2844,7 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku Ekran görüntüsü + + Ses Giriş Komutunu Ayarla + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs index a0850e70..6dd989f7 100644 --- a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs @@ -483,6 +483,14 @@ internal static void LoadCommandInfo() // ================================= + commandInfoCard = new CommandInfoCard(CommandType.SetAudioInputCommand, + Languages.CommandsManager_SetAudioInputCommandDescription, + true, false, true); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.ShutdownCommand, Languages.CommandsManager_ShutdownCommandDescription, true, true, false); diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs index 525bcc87..983b41b8 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs @@ -206,6 +206,7 @@ private void LoadCommand() case CommandType.SetVolumeCommand: case CommandType.SetApplicationVolumeCommand: case CommandType.SetAudioOutputCommand: + case CommandType.SetAudioInputCommand: TbSetting.Text = Command.Command; break; } @@ -457,6 +458,7 @@ private void BtnStore_Click(object sender, EventArgs e) break; case CommandType.SetAudioOutputCommand: + case CommandType.SetAudioInputCommand: var audioDeviceName = TbSetting.Text.Trim(); if (string.IsNullOrEmpty(audioDeviceName)) { @@ -630,6 +632,7 @@ private bool SetType(bool setDefaultValues = true) break; case CommandType.SetAudioOutputCommand: + case CommandType.SetAudioInputCommand: SetAudioOutputUi(); break; diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index 8a43773e..b84f6070 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -608,6 +608,16 @@ internal static string CommandsManager_SetApplicationVolumeCommandDescription { } } + /// + /// Looks up a localized string similar to Sets the default audio input for the system (including default communication device). + ///Requires audio device name as a payload.. + /// + internal static string CommandsManager_SetAudioInputCommandDescription { + get { + return ResourceManager.GetString("CommandsManager_SetAudioInputCommandDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sets the default audio output for the system. ///Requires audio device name as a payload.. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index 376c1f7c..fbd3e43b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3484,4 +3484,8 @@ Die Bildschirmnummer hängt von der Systemkonfiguration ab – beginnt bei 0. Bildschirmnummer + + Legt den Standard-Audioeingang für das System fest (einschließlich des Standard-Kommunikationsgeräts). +Als Payload benötigen Sie den Namen des Audiogeräts. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 6e767239..7be618f1 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3361,4 +3361,8 @@ Requires audio device name as a payload. Provides a screenshot sensor in form of a camera entity. Screen number depends on system configuration - starts at 0. + + Sets the default audio input for the system (including default communication device). +Requires audio device name as a payload. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 89b491b3..7aafaaa8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3360,4 +3360,8 @@ El número de pantalla depende de la configuración del sistema: comienza en 0.< Número de pantalla + + Establece la entrada de audio predeterminada para el sistema (incluido el dispositivo de comunicación predeterminado). +Necesita el nombre del dispositivo de audio como carga útil. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index ffe6fbe4..62fb1f9c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3393,4 +3393,8 @@ Le numéro d'écran dépend de la configuration du système - commence à 0. Numéro d'écran + + Définit l'entrée audio par défaut du système (y compris le périphérique de communication par défaut). +Vous avez besoin du nom du périphérique audio comme charge utile. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 7629e0fd..a56c0472 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3381,4 +3381,8 @@ Schermnummer is afhankelijk van de systeemconfiguratie - begint bij 0. Schermnummer + + Stelt de standaardaudio-invoer in voor het systeem (inclusief het standaardcommunicatieapparaat). +U hebt de naam van het audioapparaat nodig als payload. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 0f937e96..a68dbf65 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3470,4 +3470,8 @@ Numer ekranu zależy od konfiguracji systemu – zaczyna się od 0. Numer ekranu + + Ustawia domyślne wejście audio dla systemu (w tym domyślne urządzenie komunikacyjne). +Wymaga nazwy urządzenia audio jako ładunku. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 27b31828..ff0267af 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3406,4 +3406,8 @@ O número da tela depende da configuração do sistema – começa em 0. Número da tela + + Define a entrada de áudio padrão para o sistema (incluindo o dispositivo de comunicação padrão). +Você precisa do nome do dispositivo de áudio como carga útil. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 8cf4f2a9..47ff6ae3 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3373,4 +3373,8 @@ Requires audio device name as a payload. Provides a screenshot sensor in form of a camera entity. Screen number depends on system configuration - starts at 0. + + Sets the default audio input for the system (including default communication device). +Requires audio device name as a payload. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 8846437c..9a34e67d 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3429,4 +3429,8 @@ Home Assistant. Номер экрана + + Устанавливает аудиовход по умолчанию для системы (включая устройство связи по умолчанию). +В качестве полезной нагрузки требуется имя аудиоустройства. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index e32b3a8c..970518e6 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3509,4 +3509,8 @@ Zahteva ime zvočne naprave kot tovor. Številka zaslona + + Nastavi privzeti zvočni vhod za sistem (vključno s privzeto komunikacijsko napravo). +Ime zvočne naprave potrebujete kot obremenitev. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index ebd9375f..a9138f11 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2972,4 +2972,8 @@ Ekran numarası sistem konfigürasyonuna bağlıdır - 0'dan başlar. Ekran numarası + + Sistem için varsayılan ses girişini ayarlar (varsayılan iletişim cihazı dahil). +Yük olarak ses cihazı adına ihtiyacınız vardır. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs index c277e876..9ddb228a 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs @@ -178,6 +178,9 @@ internal static AbstractCommand ConvertConfiguredToAbstract(ConfiguredCommand co case CommandType.SetAudioOutputCommand: abstractCommand = new SetAudioOutputCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); break; + case CommandType.SetAudioInputCommand: + abstractCommand = new SetAudioInputCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); + break; default: Log.Error("[SETTINGS_COMMANDS] [{name}] Unknown configured command type: {type}", command.EntityName, command.Type.ToString()); break; From dd55e187423ab88b854b97335b117d336f90df1c Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 15 Feb 2024 20:23:20 +0100 Subject: [PATCH 07/45] Fix: Powershell command argument binding (#47) This PR fixes how action arguments are passed on to the PowershellCommand. Previously whole action string was passed as a one argument, this prevented parameter binding - after the changes the action string itself defines how the parameter/s will be handled. Thanks to @shupershuff for reporting :) --- .../Managers/PowershellManager.cs | 625 +++++++++--------- 1 file changed, 313 insertions(+), 312 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs index 576823dc..f28df5d7 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs @@ -2,322 +2,323 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Text; +using System.Text.RegularExpressions; using CliWrap; using Newtonsoft.Json; using Serilog; -namespace HASS.Agent.Shared.Managers +namespace HASS.Agent.Shared.Managers; + +/// +/// Performs Powershell-related actions +/// +public static class PowershellManager { - /// - /// Performs Powershell-related actions - /// - public static class PowershellManager - { - /// - /// Execute a Powershell command without waiting for or checking results - /// - /// - /// - public static bool ExecuteCommandHeadless(string command) => ExecuteHeadless(command, string.Empty, false); - - /// - /// Executes a Powershell script without waiting for or checking results - /// - /// - /// - /// - public static bool ExecuteScriptHeadless(string script, string parameters) => ExecuteHeadless(script, parameters, true); - - private static string GetProcessArguments(string command, string parameters, bool isScript) - { - if (isScript) - { - return string.IsNullOrWhiteSpace(parameters) - ? $"-File \"{command}\"" - : $"-File \"{command}\" \"{parameters}\""; - } - else - { - return $@"& {{{command}}}"; //NOTE: place to fix any potential future issues with "command part of the command" - } - } - - private static bool ExecuteHeadless(string command, string parameters, bool isScript) - { - var descriptor = isScript ? "script" : "command"; - - try - { - var workingDir = string.Empty; - if (isScript) - { - // try to get the script's startup path - var scriptDir = Path.GetDirectoryName(command); - workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; - } - - var psExec = GetPsExecutable(); - if (string.IsNullOrEmpty(psExec)) - return false; - - var processInfo = new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - FileName = psExec, - WorkingDirectory = workingDir, - Arguments = GetProcessArguments(command, parameters, isScript) - }; - - using var process = new Process(); - process.StartInfo = processInfo; - var start = process.Start(); - - if (!start) - { - Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {command}", descriptor, command); - - return false; - } - - return true; - } - catch (Exception ex) - { - Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); - - return false; - } - } - - /// - /// Execute a Powershell command, logs the output if it fails - /// - /// - /// - /// - public static bool ExecuteCommand(string command, TimeSpan timeout) => Execute(command, string.Empty, false, timeout); - - /// - /// Executes a Powershell script, logs the output if it fails - /// - /// - /// - /// - public static bool ExecuteScript(string script, string parameters, TimeSpan timeout) => Execute(script, parameters, true, timeout); - - private static bool Execute(string command, string parameters, bool isScript, TimeSpan timeout) - { - var descriptor = isScript ? "script" : "command"; - - try - { - var workingDir = string.Empty; - if (isScript) - { - // try to get the script's startup path - var scriptDir = Path.GetDirectoryName(command); - workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; - } - - var psExec = GetPsExecutable(); - if (string.IsNullOrEmpty(psExec)) return false; - - var processInfo = new ProcessStartInfo - { - FileName = psExec, - RedirectStandardError = true, - RedirectStandardOutput = true, - UseShellExecute = false, - WorkingDirectory = workingDir, - Arguments = GetProcessArguments(command, parameters, isScript) - }; - - using var process = new Process(); - process.StartInfo = processInfo; - var start = process.Start(); - - if (!start) - { - Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {script}", descriptor, command); - - return false; - } - - process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); - - if (process.ExitCode == 0) - return true; - - // non-zero exitcode, process as failed - Log.Error("[POWERSHELL] The {descriptor} returned non-zero exitcode: {code}", descriptor, process.ExitCode); - - var errors = process.StandardError.ReadToEnd().Trim(); - if (!string.IsNullOrEmpty(errors)) - { - Log.Error("[POWERSHELL] Error output:\r\n{output}", errors); - } - else - { - var console = process.StandardOutput.ReadToEnd().Trim(); - if (!string.IsNullOrEmpty(console)) - Log.Error("[POWERSHELL] No error output, console output:\r\n{output}", errors); - else - Log.Error("[POWERSHELL] No error and no console output"); - } - - return false; - } - catch (Exception ex) - { - Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); - - return false; - } - } - - private static Encoding TryParseCodePage(int codePage) - { - Encoding encoding = null; - try - { - encoding = Encoding.GetEncoding(codePage); - } - catch - { - // best effort - } - - return encoding; - } - - private static Encoding GetEncoding() - { - var encoding = TryParseCodePage(CultureInfo.InstalledUICulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - encoding = TryParseCodePage(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - encoding = TryParseCodePage(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - encoding = TryParseCodePage(CultureInfo.InvariantCulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - Log.Warning("[POWERSHELL] Cannot parse system text culture to encoding, returning UTF-8 as a fallback, please report this as a GitHub issue"); - - Log.Debug("[POWERSHELL] currentInstalledUICulture {c}", JsonConvert.SerializeObject(CultureInfo.InstalledUICulture.TextInfo)); - Log.Debug("[POWERSHELL] currentCulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentCulture.TextInfo)); - Log.Debug("[POWERSHELL] currentUICulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentUICulture.TextInfo)); - Log.Debug("[POWERSHELL] invariantCulture {c}", JsonConvert.SerializeObject(CultureInfo.InvariantCulture.TextInfo)); - - return Encoding.UTF8; - } - - /// - /// Executes the command or script, and returns the standard and error output - /// - /// - /// - /// - /// - /// - internal static bool ExecuteWithOutput(string command, TimeSpan timeout, out string output, out string errors) - { - output = string.Empty; - errors = string.Empty; - - try - { - var isScript = command.ToLower().EndsWith(".ps1"); - - var workingDir = string.Empty; - if (isScript) - { - // try to get the script's startup path - var scriptDir = Path.GetDirectoryName(command); - workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; - } - - var psExec = GetPsExecutable(); - if (string.IsNullOrEmpty(psExec)) - return false; - - var encoding = GetEncoding(); - - var processInfo = new ProcessStartInfo - { - FileName = psExec, - RedirectStandardError = true, - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = workingDir, - StandardOutputEncoding = encoding, - StandardErrorEncoding = encoding, - // set the right type of arguments - Arguments = isScript - ? $@"& '{command}'" - : $@"& {{{command}}}" - }; - - using var process = new Process(); - process.StartInfo = processInfo; - - var start = process.Start(); - if (!start) - { - Log.Error("[POWERSHELL] Unable to begin executing the {type}: {cmd}", isScript ? "script" : "command", command); - - return false; - } - - var completed = process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); - if (!completed) - Log.Error("[POWERSHELL] Timeout executing the {type}: {cmd}", isScript ? "script" : "command", command); - - output = process.StandardOutput.ReadToEnd().Trim(); - errors = process.StandardError.ReadToEnd().Trim(); - - process.StandardOutput.Dispose(); - process.StandardError.Dispose(); - - process.Kill(); - - return completed; - } - catch (Exception ex) - { - Log.Fatal(ex, ex.Message); - - return false; - } - } - - /// - /// Attempt to locate powershell.exe - /// - /// - public static string GetPsExecutable() - { - // try regular location - var psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell\\v1.0\\powershell.exe"); - if (File.Exists(psExec)) - return psExec; - - // try specific - psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "WindowsPowerShell\\v1.0\\powershell.exe"); - if (File.Exists(psExec)) - return psExec; - - Log.Error("[POWERSHELL] PS executable not found, make sure you have powershell installed on your system"); - return string.Empty; - } - } + /// + /// Execute a Powershell command without waiting for or checking results + /// + /// + /// + public static bool ExecuteCommandHeadless(string command) => ExecuteHeadless(command, string.Empty, false); + + /// + /// Executes a Powershell script without waiting for or checking results + /// + /// + /// + /// + public static bool ExecuteScriptHeadless(string script, string parameters) => ExecuteHeadless(script, parameters, true); + + private static string GetProcessArguments(string command, string parameters, bool isScript) + { + if (isScript) + { + return string.IsNullOrWhiteSpace(parameters) + ? $"-File \"{command}\"" + : $"-File \"{command}\" {parameters}"; + } + else + { + return $@"& {{{command}}}"; //NOTE: place to fix any potential future issues with "command part of the command" + } + } + + private static bool ExecuteHeadless(string command, string parameters, bool isScript) + { + var descriptor = isScript ? "script" : "command"; + + try + { + var workingDir = string.Empty; + if (isScript) + { + // try to get the script's startup path + var scriptDir = Path.GetDirectoryName(command); + workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; + } + + var psExec = GetPsExecutable(); + if (string.IsNullOrEmpty(psExec)) + return false; + + var processInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + FileName = psExec, + WorkingDirectory = workingDir, + Arguments = GetProcessArguments(command, parameters, isScript) + }; + + using var process = new Process(); + process.StartInfo = processInfo; + var start = process.Start(); + + if (!start) + { + Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {command}", descriptor, command); + + return false; + } + + return true; + } + catch (Exception ex) + { + Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); + + return false; + } + } + + /// + /// Execute a Powershell command, logs the output if it fails + /// + /// + /// + /// + public static bool ExecuteCommand(string command, TimeSpan timeout) => Execute(command, string.Empty, false, timeout); + + /// + /// Executes a Powershell script, logs the output if it fails + /// + /// + /// + /// + public static bool ExecuteScript(string script, string parameters, TimeSpan timeout) => Execute(script, parameters, true, timeout); + + private static bool Execute(string command, string parameters, bool isScript, TimeSpan timeout) + { + var descriptor = isScript ? "script" : "command"; + + try + { + var workingDir = string.Empty; + if (isScript) + { + // try to get the script's startup path + var scriptDir = Path.GetDirectoryName(command); + workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; + } + + var psExec = GetPsExecutable(); + if (string.IsNullOrEmpty(psExec)) return false; + + var processInfo = new ProcessStartInfo + { + FileName = psExec, + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + WorkingDirectory = workingDir, + Arguments = GetProcessArguments(command, parameters, isScript) + }; + + using var process = new Process(); + process.StartInfo = processInfo; + var start = process.Start(); + + if (!start) + { + Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {script}", descriptor, command); + + return false; + } + + process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); + + if (process.ExitCode == 0) + return true; + + // non-zero exitcode, process as failed + Log.Error("[POWERSHELL] The {descriptor} returned non-zero exitcode: {code}", descriptor, process.ExitCode); + + var errors = process.StandardError.ReadToEnd().Trim(); + if (!string.IsNullOrEmpty(errors)) + { + Log.Error("[POWERSHELL] Error output:\r\n{output}", errors); + } + else + { + var console = process.StandardOutput.ReadToEnd().Trim(); + if (!string.IsNullOrEmpty(console)) + Log.Error("[POWERSHELL] No error output, console output:\r\n{output}", errors); + else + Log.Error("[POWERSHELL] No error and no console output"); + } + + return false; + } + catch (Exception ex) + { + Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); + + return false; + } + } + + private static Encoding TryParseCodePage(int codePage) + { + Encoding encoding = null; + try + { + encoding = Encoding.GetEncoding(codePage); + } + catch + { + // best effort + } + + return encoding; + } + + private static Encoding GetEncoding() + { + var encoding = TryParseCodePage(CultureInfo.InstalledUICulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + encoding = TryParseCodePage(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + encoding = TryParseCodePage(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + encoding = TryParseCodePage(CultureInfo.InvariantCulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + Log.Warning("[POWERSHELL] Cannot parse system text culture to encoding, returning UTF-8 as a fallback, please report this as a GitHub issue"); + + Log.Debug("[POWERSHELL] currentInstalledUICulture {c}", JsonConvert.SerializeObject(CultureInfo.InstalledUICulture.TextInfo)); + Log.Debug("[POWERSHELL] currentCulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentCulture.TextInfo)); + Log.Debug("[POWERSHELL] currentUICulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentUICulture.TextInfo)); + Log.Debug("[POWERSHELL] invariantCulture {c}", JsonConvert.SerializeObject(CultureInfo.InvariantCulture.TextInfo)); + + return Encoding.UTF8; + } + + /// + /// Executes the command or script, and returns the standard and error output + /// + /// + /// + /// + /// + /// + internal static bool ExecuteWithOutput(string command, TimeSpan timeout, out string output, out string errors) + { + output = string.Empty; + errors = string.Empty; + + try + { + var isScript = command.ToLower().EndsWith(".ps1"); + + var workingDir = string.Empty; + if (isScript) + { + // try to get the script's startup path + var scriptDir = Path.GetDirectoryName(command); + workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; + } + + var psExec = GetPsExecutable(); + if (string.IsNullOrEmpty(psExec)) + return false; + + var encoding = GetEncoding(); + + var processInfo = new ProcessStartInfo + { + FileName = psExec, + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = workingDir, + StandardOutputEncoding = encoding, + StandardErrorEncoding = encoding, + // set the right type of arguments + Arguments = isScript + ? $@"& '{command}'" + : $@"& {{{command}}}" + }; + + using var process = new Process(); + process.StartInfo = processInfo; + + var start = process.Start(); + if (!start) + { + Log.Error("[POWERSHELL] Unable to begin executing the {type}: {cmd}", isScript ? "script" : "command", command); + + return false; + } + + var completed = process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); + if (!completed) + Log.Error("[POWERSHELL] Timeout executing the {type}: {cmd}", isScript ? "script" : "command", command); + + output = process.StandardOutput.ReadToEnd().Trim(); + errors = process.StandardError.ReadToEnd().Trim(); + + process.StandardOutput.Dispose(); + process.StandardError.Dispose(); + + process.Kill(); + + return completed; + } + catch (Exception ex) + { + Log.Fatal(ex, ex.Message); + + return false; + } + } + + /// + /// Attempt to locate powershell.exe + /// + /// + public static string GetPsExecutable() + { + // try regular location + var psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell\\v1.0\\powershell.exe"); + if (File.Exists(psExec)) + return psExec; + + // try specific + psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "WindowsPowerShell\\v1.0\\powershell.exe"); + if (File.Exists(psExec)) + return psExec; + + Log.Error("[POWERSHELL] PS executable not found, make sure you have powershell installed on your system"); + return string.Empty; + } } From d1af9d6cd3a3ed6294f159d55d311924233df10f Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 24 Feb 2024 11:44:05 +0100 Subject: [PATCH 08/45] Fix: active window title encoding (#50) * added charset to the dllimport * added check for the window name length --- .../SingleValue/ActiveWindowSensor.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs index c6711c3e..713a50e7 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs @@ -16,10 +16,12 @@ public ActiveWindowSensor(int? updateInterval = null, string entityName = Defaul public override DiscoveryConfigModel GetAutoDiscoveryConfig() { - if (Variables.MqttManager == null) return null; + if (Variables.MqttManager == null) + return null; var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); - if (deviceConfig == null) return null; + if (deviceConfig == null) + return null; return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() { @@ -43,16 +45,20 @@ public override string GetState() [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); - [DllImport("user32.dll")] - private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder builder, int count); private static string GetActiveWindowTitle() { - const int nChars = 256; - var buff = new StringBuilder(nChars); - var handle = GetForegroundWindow(); + var windowHandle = GetForegroundWindow(); + var titleLength = GetWindowTextLength(windowHandle) + 1; + var builder = new StringBuilder(titleLength); + var windowTitle = GetWindowText(windowHandle, builder, titleLength) > 0 ? builder.ToString() : string.Empty; - return GetWindowText(handle, buff, nChars) > 0 ? buff.ToString() : string.Empty; + return windowTitle.Length > 255 ? windowTitle[..255]: windowTitle; //Note(Amadeo): to make sure we don't exceed HA limitation of 255 payload length } } } From a426aee510551dfffaeeb655acbae843acfaf638 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:35:40 +0100 Subject: [PATCH 09/45] Fix: constant MQTT discovery payload publish (#54) * discovery payload is sent only once per run and config change --- .../Commands/CommandsManager.cs | 19 ++++++----- .../Sensors/SensorsManager.cs | 32 +++++++++++-------- .../HASS.Agent/Commands/CommandsManager.cs | 22 +++++++++---- .../HASS.Agent/Sensors/SensorsManager.cs | 32 +++++++++++-------- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs index d65f8dba..872371fd 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs @@ -15,6 +15,8 @@ internal static class CommandsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -122,20 +124,22 @@ private static async void Process() // do we have commands? if (!CommandsPresent()) continue; - // publish availability & sensor autodisco's every 30 sec + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { - // let hass know we're still here await Variables.MqttManager.AnnounceAvailabilityAsync(); - // publish command autodisco's - foreach (var command in Variables.Commands.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (!_discoveryPublished) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await command.PublishAutoDiscoveryConfigAsync(); + foreach (var command in Variables.Commands.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await command.PublishAutoDiscoveryConfigAsync(); + } + + _discoveryPublished = true; } - // are we subscribed? if (!_subscribed) { foreach (var command in Variables.Commands.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) @@ -146,7 +150,6 @@ private static async void Process() _subscribed = true; } - // log moment _lastAutoDiscoPublish = DateTime.Now; } diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs index 8a7e3623..10122601 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs @@ -14,6 +14,8 @@ internal static class SensorsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -116,32 +118,34 @@ private static async void Process() // optionally flag as the first real run if (!firstRunDone) firstRunDone = true; - // publish availability & sensor autodisco's every 30 sec + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { - // let hass know we're still here await Variables.MqttManager.AnnounceAvailabilityAsync(); - // publish the autodisco's - if (SingleValueSensorsPresent()) + if (!_discoveryPublished) { - foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (SingleValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } - } - if (MultiValueSensorsPresent()) - { - foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (MultiValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } + + _discoveryPublished = true; } - // log moment _lastAutoDiscoPublish = DateTime.Now; } diff --git a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs index 6dd989f7..0d4dbc69 100644 --- a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs @@ -17,6 +17,8 @@ internal static class CommandsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -66,6 +68,8 @@ internal static async Task UnpublishAllCommands() await command.UnPublishAutoDiscoveryConfigAsync(); await Variables.MqttManager.UnsubscribeAsync(command); } + + _discoveryPublished = false; } /// @@ -103,18 +107,24 @@ private static async void Process() firstRun = false; + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { await Variables.MqttManager.AnnounceAvailabilityAsync(); - foreach (var command in Variables.Commands - .TakeWhile(_ => !_pause) - .TakeWhile(_ => _active)) + if (!_discoveryPublished) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) - continue; + foreach (var command in Variables.Commands + .TakeWhile(_ => !_pause) + .TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) + continue; + + await command.PublishAutoDiscoveryConfigAsync(); + } - await command.PublishAutoDiscoveryConfigAsync(); + _discoveryPublished = true; } if (!subscribed) diff --git a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs index 0f5f4eba..f8cc5b7a 100644 --- a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs @@ -20,6 +20,8 @@ internal static class SensorsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -96,32 +98,34 @@ private static async void Process() // optionally flag as the first real run if (!firstRunDone) firstRunDone = true; - // publish availability & sensor autodisco's every 30 sec + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { - // let hass know we're still here await Variables.MqttManager.AnnounceAvailabilityAsync(); - // publish the autodisco's - if (SingleValueSensorsPresent()) + if (!_discoveryPublished) { - foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (SingleValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } - } - if (MultiValueSensorsPresent()) - { - foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (MultiValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } + + _discoveryPublished = true; } - // log moment _lastAutoDiscoPublish = DateTime.Now; } From b828cf814d1548d70b2912111229297efa17d767 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:36:26 +0100 Subject: [PATCH 10/45] Feature: ability to ignore MQTT grace period after waking up from hibernation (#51) This PR adds an option to ignore grace period if system was woken up from the hibernation not later than the grace period itself - disabled by default. --- .../Configuration/ConfigMqtt.Designer.cs | 99 +++++++++++-------- .../Controls/Configuration/ConfigMqtt.cs | 5 + .../Controls/Configuration/ConfigMqtt.resx | 59 ++++++----- .../HASS.Agent/Forms/Configuration.cs | 2 + src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs | 37 +++++-- .../HASS.Agent/Managers/SystemStateManager.cs | 21 +++- .../HASS.Agent/Models/Config/AppSettings.cs | 2 + .../Localization/Languages.Designer.cs | 9 ++ .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 3 + .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3 + .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + 19 files changed, 192 insertions(+), 75 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs index 48c9bfbb..670ddf22 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs @@ -56,6 +56,7 @@ private void InitializeComponent() this.LblClientId = new System.Windows.Forms.Label(); this.NumMqttPort = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); this.PbShow = new System.Windows.Forms.PictureBox(); + this.CbIgnoreGracePeriod = new System.Windows.Forms.CheckBox(); this.CbEnableMqtt = new System.Windows.Forms.CheckBox(); this.LblMqttDisabledWarning = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.NumMqttPort)).BeginInit(); @@ -460,51 +461,68 @@ private void InitializeComponent() this.LblMqttDisabledWarning.Text = Languages.ConfigMqtt_LblMqttDisabledWarning; this.LblMqttDisabledWarning.Visible = false; // + // CbIgnoreGracePeriod + // + this.CbIgnoreGracePeriod.AccessibleDescription = "Ignore grace period after waking up from hibernation."; + this.CbIgnoreGracePeriod.AccessibleName = "Ignore grace period"; + this.CbIgnoreGracePeriod.AccessibleRole = AccessibleRole.CheckButton; + this.CbIgnoreGracePeriod.AutoSize = true; + this.CbIgnoreGracePeriod.Checked = false; + this.CbIgnoreGracePeriod.CheckState = CheckState.Unchecked; + this.CbIgnoreGracePeriod.Font = new Font("Segoe UI", 10F); + this.CbIgnoreGracePeriod.Location = new Point(59, 612); + this.CbIgnoreGracePeriod.Name = "CbIgnoreGracePeriod"; + this.CbIgnoreGracePeriod.Size = new Size(354, 23); + this.CbIgnoreGracePeriod.TabIndex = 103; + this.CbIgnoreGracePeriod.Text = Languages.ConfigMqtt_CbIgnoreGracePeriod; + this.CbIgnoreGracePeriod.UseVisualStyleBackColor = true; + this.CbIgnoreGracePeriod.CheckedChanged += new System.EventHandler(this.CbIgnoreGracePeriod_CheckedChanged); + // // ConfigMqtt // this.AccessibleDescription = "Panel containing the MQTT client configuration."; this.AccessibleName = "MQTT"; - this.AccessibleRole = System.Windows.Forms.AccessibleRole.Pane; - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); - this.Controls.Add(this.LblMqttDisabledWarning); - this.Controls.Add(this.CbEnableMqtt); - this.Controls.Add(this.PbShow); - this.Controls.Add(this.NumMqttPort); - this.Controls.Add(this.LblTip2); - this.Controls.Add(this.TbMqttClientId); - this.Controls.Add(this.LblClientId); - this.Controls.Add(this.LblTip3); - this.Controls.Add(this.TbMqttClientCertificate); - this.Controls.Add(this.LblClientCert); - this.Controls.Add(this.TbMqttRootCertificate); - this.Controls.Add(this.LblRootCert); - this.Controls.Add(this.CbUseRetainFlag); - this.Controls.Add(this.CbAllowUntrustedCertificates); - this.Controls.Add(this.BtnMqttClearConfig); - this.Controls.Add(this.LblTip1); - this.Controls.Add(this.LblInfo1); - this.Controls.Add(this.TbMqttDiscoveryPrefix); - this.Controls.Add(this.LblDiscoPrefix); - this.Controls.Add(this.TbMqttPassword); - this.Controls.Add(this.TbMqttUsername); - this.Controls.Add(this.TbMqttAddress); - this.Controls.Add(this.CbMqttTls); - this.Controls.Add(this.LblBrokerPassword); - this.Controls.Add(this.LblBrokerUsername); - this.Controls.Add(this.LblBrokerPort); - this.Controls.Add(this.LblBrokerIp); - this.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.Margin = new System.Windows.Forms.Padding(4); + this.AccessibleRole = AccessibleRole.Pane; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = AutoScaleMode.Dpi; + this.BackColor = Color.FromArgb(45, 45, 48); + this.Controls.Add(CbIgnoreGracePeriod); + this.Controls.Add(LblMqttDisabledWarning); + this.Controls.Add(CbEnableMqtt); + this.Controls.Add(PbShow); + this.Controls.Add(NumMqttPort); + this.Controls.Add(LblTip2); + this.Controls.Add(TbMqttClientId); + this.Controls.Add(LblClientId); + this.Controls.Add(LblTip3); + this.Controls.Add(TbMqttClientCertificate); + this.Controls.Add(LblClientCert); + this.Controls.Add(TbMqttRootCertificate); + this.Controls.Add(LblRootCert); + this.Controls.Add(CbUseRetainFlag); + this.Controls.Add(CbAllowUntrustedCertificates); + this.Controls.Add(BtnMqttClearConfig); + this.Controls.Add(LblTip1); + this.Controls.Add(LblInfo1); + this.Controls.Add(TbMqttDiscoveryPrefix); + this.Controls.Add(LblDiscoPrefix); + this.Controls.Add(TbMqttPassword); + this.Controls.Add(TbMqttUsername); + this.Controls.Add(TbMqttAddress); + this.Controls.Add(CbMqttTls); + this.Controls.Add(LblBrokerPassword); + this.Controls.Add(LblBrokerUsername); + this.Controls.Add(LblBrokerPort); + this.Controls.Add(LblBrokerIp); + this.ForeColor = Color.FromArgb(241, 241, 241); + this.Margin = new Padding(4); this.Name = "ConfigMqtt"; - this.Size = new System.Drawing.Size(700, 614); - this.Load += new System.EventHandler(this.ConfigMqtt_Load); - ((System.ComponentModel.ISupportInitialize)(this.NumMqttPort)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.PbShow)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - + this.Size = new Size(700, 658); + this.Load += ConfigMqtt_Load; + ((System.ComponentModel.ISupportInitialize)NumMqttPort).EndInit(); + ((System.ComponentModel.ISupportInitialize)PbShow).EndInit(); + ResumeLayout(false); + PerformLayout(); } #endregion @@ -536,5 +554,6 @@ private void InitializeComponent() private PictureBox PbShow; internal CheckBox CbEnableMqtt; private Label LblMqttDisabledWarning; + internal CheckBox CbIgnoreGracePeriod; } } diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs index c39635d7..83354e26 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs @@ -60,5 +60,10 @@ private void CbEnableMqtt_CheckedChanged(object sender, EventArgs e) { LblMqttDisabledWarning.Visible = CbEnableMqtt.CheckState != CheckState.Checked; } + + private void CbIgnoreGracePeriod_CheckedChanged(object sender, EventArgs e) + { + + } } } diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx index c037b6da..1435c3aa 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx @@ -1,17 +1,17 @@  - @@ -117,11 +117,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Commands and sensors use MQTT, as well as notifications and media player functions when using the new integration. + +Please provide credentials for your broker, if you're using the HA Mosquitto addon, you can probably use the preset address. + +Note: these settings (excluding the Client ID) will also be applied to the satellite service. + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wAAADsABataJCQAAAZZJREFUSEvtk61TAlEUxTcQDAajwWAgGgwG/gCCgUAwGA0GgsFIZMZANBgMBgOR + vgAADr4B6kKxwAAAAZZJREFUSEvtk61TAlEUxTcQDAajwWAgGgwG/gCCgUAwGA0GgsFIZMZANBgMBgOR YDQYNliAhdkgMwQCwUAgGAgGgv7Oeh/zdmdRscqZObPv3Y9z37v3bbDBBgkGg0Gx1+uV+/3+mdjtdo/Z H5j7bwjDcAuxOozhRx6jKBrzvYrjeMfSfgdOd0rixBf7jhSawRqHKphEPnRtEp6yAmswgiWTSwNHlVPM XTDrB/js9qtIzAjeebYF+5rJfoGTX3oBUw1RdtahZ88lYmOLLWnt7DyMa9mz4nGn09lLHEBBnm8V2xYe diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs index dedf1cf2..c11841ec 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs @@ -323,6 +323,7 @@ private void LoadSettings() _mqtt.TbMqttClientCertificate.Text = Variables.AppSettings.MqttClientCertificate; _mqtt.CbAllowUntrustedCertificates.CheckState = Variables.AppSettings.MqttAllowUntrustedCertificates ? CheckState.Checked : CheckState.Unchecked; _mqtt.CbUseRetainFlag.CheckState = Variables.AppSettings.MqttUseRetainFlag ? CheckState.Checked : CheckState.Unchecked; + _mqtt.CbIgnoreGracePeriod.CheckState = Variables.AppSettings.MqttIgnoreGracePeriod ? CheckState.Checked : CheckState.Unchecked; // updates _updates.CbUpdates.CheckState = Variables.AppSettings.CheckForUpdates ? CheckState.Checked : CheckState.Unchecked; @@ -425,6 +426,7 @@ private async Task StoreSettingsAsync() Variables.AppSettings.MqttClientCertificate = _mqtt.TbMqttClientCertificate.Text; Variables.AppSettings.MqttAllowUntrustedCertificates = _mqtt.CbAllowUntrustedCertificates.CheckState == CheckState.Checked; Variables.AppSettings.MqttUseRetainFlag = _mqtt.CbUseRetainFlag.CheckState == CheckState.Checked; + Variables.AppSettings.MqttIgnoreGracePeriod = _mqtt.CbIgnoreGracePeriod.CheckState == CheckState.Checked; // mqtt -> service await SettingsManager.SendMqttSettingsToServiceAsync(); diff --git a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs index ff56e52f..3e99155b 100644 --- a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs +++ b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs @@ -14,6 +14,7 @@ using HASS.Agent.Resources.Localization; using HASS.Agent.Settings; using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; using HASS.Agent.Shared.Models.HomeAssistant; using HASS.Agent.Shared.Mqtt; using MQTTnet; @@ -130,10 +131,14 @@ private async Task OnMqttDisconnected(MqttClientDisconnectedEventArgs arg) Variables.MainForm?.SetMqttStatus(ComponentStatus.Connecting); + var gracePeriod = Variables.AppSettings.DisconnectedGracePeriodSeconds; + // give the connection the grace period to recover var runningTimer = Stopwatch.StartNew(); - while (runningTimer.Elapsed.TotalSeconds < Variables.AppSettings.DisconnectedGracePeriodSeconds) + while (runningTimer.Elapsed.TotalSeconds < gracePeriod) { + await Task.Delay(TimeSpan.FromSeconds(5)); + if (IsConnected()) { _isReady = true; @@ -143,12 +148,20 @@ private async Task OnMqttDisconnected(MqttClientDisconnectedEventArgs arg) _status = MqttStatus.Connected; Variables.MainForm?.SetMqttStatus(ComponentStatus.Ok); - Log.Information("[MQTT] Connected"); + Log.Information("[MQTT] Reconnected from disconnection"); return; } - await Task.Delay(TimeSpan.FromSeconds(5)); + if (Variables.AppSettings.MqttIgnoreGracePeriod) + { + var lastResumed = SystemStateManager.LastEventOccurrence.TryGetValue(SystemStateEvent.Resume, out var lastResumeEventDate); + if (lastResumed && DateTime.Now < lastResumeEventDate.AddSeconds(gracePeriod)) + { + Log.Information("[MQTT] System resumed less than {gracePeriod} seconds ago, ignoring grace period on disconnection"); + break; + } + } } // nope, call it @@ -172,10 +185,14 @@ private async Task OnMqttConnectionFailed(ConnectingFailedEventArgs arg) { Variables.MainForm?.SetMqttStatus(ComponentStatus.Connecting); + var gracePeriod = Variables.AppSettings.DisconnectedGracePeriodSeconds; + // give the connection the grace period to recover var runningTimer = Stopwatch.StartNew(); - while (runningTimer.Elapsed.TotalSeconds < Variables.AppSettings.DisconnectedGracePeriodSeconds) + while (runningTimer.Elapsed.TotalSeconds < gracePeriod) { + await Task.Delay(TimeSpan.FromSeconds(5)); + if (IsConnected()) { // recovered @@ -184,12 +201,20 @@ private async Task OnMqttConnectionFailed(ConnectingFailedEventArgs arg) _status = MqttStatus.Connected; Variables.MainForm?.SetMqttStatus(ComponentStatus.Ok); - Log.Information("[MQTT] Connected"); + Log.Information("[MQTT] Reconnected from failed connection"); return; } - await Task.Delay(TimeSpan.FromSeconds(5)); + if (Variables.AppSettings.MqttIgnoreGracePeriod) + { + var lastResumed = SystemStateManager.LastEventOccurrence.TryGetValue(SystemStateEvent.Resume, out var lastResumeEventDate); + if (lastResumed && DateTime.Now < lastResumeEventDate.AddSeconds(gracePeriod)) + { + Log.Information("[MQTT] System resumed more than {gracePeriod} seconds ago, ignoring grace period on connection failed"); + break; + } + } } // nope, call it diff --git a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs index ca4c4ded..22fbfb1e 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs @@ -12,7 +12,7 @@ internal static class SystemStateManager /// The pointer to unregister the monitor power notifications /// internal static IntPtr UnRegPowerNotify { get; set; } = IntPtr.Zero; - + /// /// Notes the last time something happened to the system's state, eg. user logged on, session locked, etc /// @@ -21,7 +21,21 @@ internal static class SystemStateManager /// /// Contains the last event that happened to the system, eg. user logged on, session locked, etc /// - internal static SystemStateEvent LastSystemStateEvent { get; private set; } = SystemStateEvent.ApplicationStarted; + private static SystemStateEvent s_lastSystemStateEvent = SystemStateEvent.ApplicationStarted; + internal static SystemStateEvent LastSystemStateEvent + { + get => s_lastSystemStateEvent; + private set + { + s_lastSystemStateEvent = value; + LastEventOccurrence[s_lastSystemStateEvent] = DateTime.Now; + } + } + + /// + /// Contains the key value pair with SystemStateEvent and the last time it occurred + /// + public static Dictionary LastEventOccurrence = new(); /// /// Contains the last event that happened to the monitors, eg. power on @@ -33,6 +47,7 @@ internal static class SystemStateManager /// internal static async void Initialize() { + LastSystemStateEvent = SystemStateEvent.ApplicationStarted; await Task.Run(Monitor); } @@ -67,7 +82,7 @@ internal static void ProcessSessionEnd() Log.Information("[SYSTEMSTATE] Session ending: system shutting down"); Task.Run(() => HelperFunctions.ShutdownAsync(TimeSpan.Zero)); LastSystemStateEvent = SystemStateEvent.SystemShutdown; - + } else { diff --git a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs index caccd39e..5222a21c 100644 --- a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs +++ b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs @@ -78,5 +78,7 @@ public AppSettings() public bool MqttUseRetainFlag { get; set; } = true; public string MqttRootCertificate { get; set; } = string.Empty; public string MqttClientCertificate { get; set; } = string.Empty; + + public bool MqttIgnoreGracePeriod { get; set; } = false; } } diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index b84f6070..0a90d4a8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -2362,6 +2362,15 @@ internal static string ConfigMqtt_CbEnableMqtt { } } + /// + /// Looks up a localized string similar to Ignore grace period after waking up from hibernation. + /// + internal static string ConfigMqtt_CbIgnoreGracePeriod { + get { + return ResourceManager.GetString("ConfigMqtt_CbIgnoreGracePeriod", resourceCulture); + } + } + /// /// Looks up a localized string similar to &TLS. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index fbd3e43b..d93caf19 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3488,4 +3488,7 @@ Die Bildschirmnummer hängt von der Systemkonfiguration ab – beginnt bei 0.Legt den Standard-Audioeingang für das System fest (einschließlich des Standard-Kommunikationsgeräts). Als Payload benötigen Sie den Namen des Audiogeräts. + + Ignorieren Sie die Schonfrist nach dem Aufwachen aus dem Ruhezustand + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 7be618f1..8b21c62b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3365,4 +3365,7 @@ Screen number depends on system configuration - starts at 0. Sets the default audio input for the system (including default communication device). Requires audio device name as a payload. + + Ignore grace period after waking up from hibernation + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 7aafaaa8..1c609964 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3364,4 +3364,7 @@ El número de pantalla depende de la configuración del sistema: comienza en 0.< Establece la entrada de audio predeterminada para el sistema (incluido el dispositivo de comunicación predeterminado). Necesita el nombre del dispositivo de audio como carga útil. + + Ignorar el período de gracia después de despertar de la hibernación + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 62fb1f9c..4f459b6e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3397,4 +3397,7 @@ Le numéro d'écran dépend de la configuration du système - commence à 0.Définit l'entrée audio par défaut du système (y compris le périphérique de communication par défaut). Vous avez besoin du nom du périphérique audio comme charge utile. + + Ignorer la période de grâce après la sortie de l'hibernation + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index a56c0472..3556e82b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3385,4 +3385,7 @@ Schermnummer is afhankelijk van de systeemconfiguratie - begint bij 0. Stelt de standaardaudio-invoer in voor het systeem (inclusief het standaardcommunicatieapparaat). U hebt de naam van het audioapparaat nodig als payload. + + Negeer de respijtperiode na het ontwaken uit de winterslaap + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index a68dbf65..e888467f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3474,4 +3474,7 @@ Numer ekranu zależy od konfiguracji systemu – zaczyna się od 0. Ustawia domyślne wejście audio dla systemu (w tym domyślne urządzenie komunikacyjne). Wymaga nazwy urządzenia audio jako ładunku. + + Ignoruj okres karencji po przebudzeniu ze stanu hibernacji + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index ff0267af..cc835f66 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3410,4 +3410,7 @@ O número da tela depende da configuração do sistema – começa em 0. Define a entrada de áudio padrão para o sistema (incluindo o dispositivo de comunicação padrão). Você precisa do nome do dispositivo de áudio como carga útil. + + Ignorar o período de carência após sair da hibernação + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 47ff6ae3..fa71846e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3377,4 +3377,7 @@ Screen number depends on system configuration - starts at 0. Sets the default audio input for the system (including default communication device). Requires audio device name as a payload. + + Ignore grace period after waking up from hibernation + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 9a34e67d..4eb99872 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3433,4 +3433,7 @@ Home Assistant. Устанавливает аудиовход по умолчанию для системы (включая устройство связи по умолчанию). В качестве полезной нагрузки требуется имя аудиоустройства. + + Игнорировать льготный период после выхода из спящего режима + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 970518e6..732c2f7e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3513,4 +3513,7 @@ Zahteva ime zvočne naprave kot tovor. Nastavi privzeti zvočni vhod za sistem (vključno s privzeto komunikacijsko napravo). Ime zvočne naprave potrebujete kot obremenitev. + + Po prebujanju iz mirovanja prezrite obdobje odloga + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index a9138f11..dea9fd38 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2976,4 +2976,7 @@ Ekran numarası sistem konfigürasyonuna bağlıdır - 0'dan başlar. Sistem için varsayılan ses girişini ayarlar (varsayılan iletişim cihazı dahil). Yük olarak ses cihazı adına ihtiyacınız vardır. + + Hazırda bekletme modundan uyandıktan sonra ek süreyi göz ardı et + \ No newline at end of file From dc6b04e02b64f846ede5e573d5974817e9053408 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:41:16 +0100 Subject: [PATCH 11/45] Feature: modern/transparent tray icon option (#55) This PR adds modern transparent tray icon option --- .../Configuration/ConfigTrayIcon.Designer.cs | 528 +++++++++--------- .../Configuration/ConfigTrayIcon.resx | 62 +- .../HASS.Agent/Forms/Configuration.cs | 2 + src/HASS.Agent/HASS.Agent/Forms/Main.cs | 17 +- src/HASS.Agent/HASS.Agent/Forms/Main.resx | 156 ++++-- .../HASS.Agent/Models/Config/AppSettings.cs | 1 + .../Localization/Languages.Designer.cs | 9 + .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 3 + .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3 + .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + .../HASS.Agent/Resources/modern-tray-icon.ico | Bin 0 -> 52111 bytes 19 files changed, 491 insertions(+), 317 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent/Resources/modern-tray-icon.ico diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs index 53fdff2f..8fec92e9 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs @@ -30,320 +30,318 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.LblInfo1 = new System.Windows.Forms.Label(); - this.CbDefaultMenu = new System.Windows.Forms.CheckBox(); - this.CbShowWebView = new System.Windows.Forms.CheckBox(); - this.TbWebViewUrl = new System.Windows.Forms.TextBox(); - this.LblWebViewUrl = new System.Windows.Forms.Label(); - this.LblX = new System.Windows.Forms.Label(); - this.LblWebViewSize = new System.Windows.Forms.Label(); - this.BtnShowWebViewPreview = new Syncfusion.WinForms.Controls.SfButton(); - this.NumWebViewWidth = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); - this.NumWebViewHeight = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); - this.BtnWebViewReset = new Syncfusion.WinForms.Controls.SfButton(); - this.CbWebViewKeepLoaded = new System.Windows.Forms.CheckBox(); - this.LblInfo2 = new System.Windows.Forms.Label(); - this.CbWebViewShowMenuOnLeftClick = new System.Windows.Forms.CheckBox(); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewWidth)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewHeight)).BeginInit(); - this.SuspendLayout(); + LblInfo1 = new Label(); + CbUseModernIcon = new CheckBox(); + CbDefaultMenu = new CheckBox(); + CbShowWebView = new CheckBox(); + TbWebViewUrl = new TextBox(); + LblWebViewUrl = new Label(); + LblX = new Label(); + LblWebViewSize = new Label(); + BtnShowWebViewPreview = new Syncfusion.WinForms.Controls.SfButton(); + NumWebViewWidth = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); + NumWebViewHeight = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); + BtnWebViewReset = new Syncfusion.WinForms.Controls.SfButton(); + CbWebViewKeepLoaded = new CheckBox(); + LblInfo2 = new Label(); + CbWebViewShowMenuOnLeftClick = new CheckBox(); + ((System.ComponentModel.ISupportInitialize)NumWebViewWidth).BeginInit(); + ((System.ComponentModel.ISupportInitialize)NumWebViewHeight).BeginInit(); + SuspendLayout(); // // LblInfo1 // - this.LblInfo1.AccessibleDescription = "Tray icon information."; - this.LblInfo1.AccessibleName = "Information"; - this.LblInfo1.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblInfo1.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblInfo1.Location = new System.Drawing.Point(70, 36); - this.LblInfo1.Name = "LblInfo1"; - this.LblInfo1.Size = new System.Drawing.Size(541, 78); - this.LblInfo1.TabIndex = 31; - this.LblInfo1.Text = Languages.ConfigTrayIcon_LblInfo1; + LblInfo1.AccessibleDescription = "Tray icon information."; + LblInfo1.AccessibleName = "Information"; + LblInfo1.AccessibleRole = AccessibleRole.StaticText; + LblInfo1.Font = new Font("Segoe UI", 10F); + LblInfo1.Location = new Point(70, 102); + LblInfo1.Name = "LblInfo1"; + LblInfo1.Size = new Size(541, 36); + LblInfo1.TabIndex = 31; + LblInfo1.Text = "Control the behaviour of the tray icon when it is right-clicked."; + // + // CbUseModernIcon + // + CbUseModernIcon.AccessibleDescription = "If enabled, modern white and transparent icon will be used"; + CbUseModernIcon.AccessibleName = "Use modern icon"; + CbUseModernIcon.AccessibleRole = AccessibleRole.CheckButton; + CbUseModernIcon.AutoSize = true; + CbUseModernIcon.Font = new Font("Segoe UI", 10F); + CbUseModernIcon.Location = new Point(70, 42); + CbUseModernIcon.Name = "CbUseModernIcon"; + CbUseModernIcon.Size = new Size(160, 23); + CbUseModernIcon.TabIndex = 50; + CbUseModernIcon.Text = Languages.ConfigTrayIcon_CbUseModernIcon; + CbUseModernIcon.UseVisualStyleBackColor = true; // // CbDefaultMenu // - this.CbDefaultMenu.AccessibleDescription = "If enabled, right clicking the system tray icon will show the default menu."; - this.CbDefaultMenu.AccessibleName = "Show default menu"; - this.CbDefaultMenu.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbDefaultMenu.AutoSize = true; - this.CbDefaultMenu.Checked = true; - this.CbDefaultMenu.CheckState = System.Windows.Forms.CheckState.Checked; - this.CbDefaultMenu.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbDefaultMenu.Location = new System.Drawing.Point(70, 117); - this.CbDefaultMenu.Name = "CbDefaultMenu"; - this.CbDefaultMenu.Size = new System.Drawing.Size(145, 23); - this.CbDefaultMenu.TabIndex = 0; - this.CbDefaultMenu.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_CbDefaultMenu; - this.CbDefaultMenu.UseVisualStyleBackColor = true; - this.CbDefaultMenu.CheckedChanged += new System.EventHandler(this.CbDefaultMenu_CheckedChanged); + CbDefaultMenu.AccessibleDescription = "If enabled, right clicking the system tray icon will show the default menu."; + CbDefaultMenu.AccessibleName = "Show default menu"; + CbDefaultMenu.AccessibleRole = AccessibleRole.CheckButton; + CbDefaultMenu.AutoSize = true; + CbDefaultMenu.Checked = true; + CbDefaultMenu.CheckState = CheckState.Checked; + CbDefaultMenu.Font = new Font("Segoe UI", 10F); + CbDefaultMenu.Location = new Point(70, 141); + CbDefaultMenu.Name = "CbDefaultMenu"; + CbDefaultMenu.Size = new Size(149, 23); + CbDefaultMenu.TabIndex = 0; + CbDefaultMenu.Text = Languages.ConfigTrayIcon_CbDefaultMenu; + CbDefaultMenu.UseVisualStyleBackColor = true; + CbDefaultMenu.CheckedChanged += CbDefaultMenu_CheckedChanged; // // CbShowWebView // - this.CbShowWebView.AccessibleDescription = "If enabled, right clicking the system tray icon will show a webview with the url " + - "you provide."; - this.CbShowWebView.AccessibleName = "Show webview"; - this.CbShowWebView.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbShowWebView.AutoSize = true; - this.CbShowWebView.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbShowWebView.Location = new System.Drawing.Point(70, 209); - this.CbShowWebView.Name = "CbShowWebView"; - this.CbShowWebView.Size = new System.Drawing.Size(116, 23); - this.CbShowWebView.TabIndex = 1; - this.CbShowWebView.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_CbShowWebView; - this.CbShowWebView.UseVisualStyleBackColor = true; - this.CbShowWebView.CheckedChanged += new System.EventHandler(this.CbShowWebView_CheckedChanged); + CbShowWebView.AccessibleDescription = "If enabled, right clicking the system tray icon will show a webview with the url you provide."; + CbShowWebView.AccessibleName = "Show webview"; + CbShowWebView.AccessibleRole = AccessibleRole.CheckButton; + CbShowWebView.AutoSize = true; + CbShowWebView.Font = new Font("Segoe UI", 10F); + CbShowWebView.Location = new Point(70, 209); + CbShowWebView.Name = "CbShowWebView"; + CbShowWebView.Size = new Size(121, 23); + CbShowWebView.TabIndex = 1; + CbShowWebView.Text = Languages.ConfigTrayIcon_CbShowWebView; + CbShowWebView.UseVisualStyleBackColor = true; + CbShowWebView.CheckedChanged += CbShowWebView_CheckedChanged; // // TbWebViewUrl // - this.TbWebViewUrl.AccessibleDescription = "The URL to show. Defaults to the Home Assistant API\'s URL."; - this.TbWebViewUrl.AccessibleName = "URL"; - this.TbWebViewUrl.AccessibleRole = System.Windows.Forms.AccessibleRole.Text; - this.TbWebViewUrl.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.TbWebViewUrl.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.TbWebViewUrl.Enabled = false; - this.TbWebViewUrl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.TbWebViewUrl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.TbWebViewUrl.Location = new System.Drawing.Point(90, 280); - this.TbWebViewUrl.Name = "TbWebViewUrl"; - this.TbWebViewUrl.Size = new System.Drawing.Size(521, 25); - this.TbWebViewUrl.TabIndex = 2; + TbWebViewUrl.AccessibleDescription = "The URL to show. Defaults to the Home Assistant API's URL."; + TbWebViewUrl.AccessibleName = "URL"; + TbWebViewUrl.AccessibleRole = AccessibleRole.Text; + TbWebViewUrl.BackColor = Color.FromArgb(63, 63, 70); + TbWebViewUrl.BorderStyle = BorderStyle.FixedSingle; + TbWebViewUrl.Enabled = false; + TbWebViewUrl.Font = new Font("Segoe UI", 10F); + TbWebViewUrl.ForeColor = Color.FromArgb(241, 241, 241); + TbWebViewUrl.Location = new Point(90, 280); + TbWebViewUrl.Name = "TbWebViewUrl"; + TbWebViewUrl.Size = new Size(521, 25); + TbWebViewUrl.TabIndex = 2; // // LblWebViewUrl // - this.LblWebViewUrl.AccessibleDescription = "URL textbox description"; - this.LblWebViewUrl.AccessibleName = "URL info"; - this.LblWebViewUrl.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblWebViewUrl.AutoSize = true; - this.LblWebViewUrl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblWebViewUrl.Location = new System.Drawing.Point(87, 258); - this.LblWebViewUrl.Name = "LblWebViewUrl"; - this.LblWebViewUrl.Size = new System.Drawing.Size(349, 19); - this.LblWebViewUrl.TabIndex = 49; - this.LblWebViewUrl.Text = Languages.ConfigTrayIcon_LblWebViewUrl; + LblWebViewUrl.AccessibleDescription = "URL textbox description"; + LblWebViewUrl.AccessibleName = "URL info"; + LblWebViewUrl.AccessibleRole = AccessibleRole.StaticText; + LblWebViewUrl.AutoSize = true; + LblWebViewUrl.Font = new Font("Segoe UI", 10F); + LblWebViewUrl.Location = new Point(87, 258); + LblWebViewUrl.Name = "LblWebViewUrl"; + LblWebViewUrl.Size = new Size(415, 19); + LblWebViewUrl.TabIndex = 49; + LblWebViewUrl.Text = "&WebView URL (For instance, your Home Assistant Dashboard URL)"; // // LblX // - this.LblX.AccessibleDescription = "Shows X, meaning \'by\' in this context."; - this.LblX.AccessibleName = "X info"; - this.LblX.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblX.AutoSize = true; - this.LblX.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblX.Location = new System.Drawing.Point(184, 350); - this.LblX.Name = "LblX"; - this.LblX.Size = new System.Drawing.Size(17, 19); - this.LblX.TabIndex = 53; - this.LblX.Text = "X"; + LblX.AccessibleDescription = "Shows X, meaning 'by' in this context."; + LblX.AccessibleName = "X info"; + LblX.AccessibleRole = AccessibleRole.StaticText; + LblX.AutoSize = true; + LblX.Font = new Font("Segoe UI", 10F); + LblX.Location = new Point(184, 350); + LblX.Name = "LblX"; + LblX.Size = new Size(17, 19); + LblX.TabIndex = 53; + LblX.Text = "X"; // // LblWebViewSize // - this.LblWebViewSize.AccessibleDescription = "Size description."; - this.LblWebViewSize.AccessibleName = "Size info"; - this.LblWebViewSize.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblWebViewSize.AutoSize = true; - this.LblWebViewSize.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblWebViewSize.Location = new System.Drawing.Point(85, 326); - this.LblWebViewSize.Name = "LblWebViewSize"; - this.LblWebViewSize.Size = new System.Drawing.Size(31, 19); - this.LblWebViewSize.TabIndex = 51; - this.LblWebViewSize.Text = Languages.ConfigTrayIcon_LblWebViewSize; + LblWebViewSize.AccessibleDescription = "Size description."; + LblWebViewSize.AccessibleName = "Size info"; + LblWebViewSize.AccessibleRole = AccessibleRole.StaticText; + LblWebViewSize.AutoSize = true; + LblWebViewSize.Font = new Font("Segoe UI", 10F); + LblWebViewSize.Location = new Point(85, 326); + LblWebViewSize.Name = "LblWebViewSize"; + LblWebViewSize.Size = new Size(58, 19); + LblWebViewSize.TabIndex = 51; + LblWebViewSize.Text = "Size (px)"; // // BtnShowWebViewPreview // - this.BtnShowWebViewPreview.AccessibleDescription = "Shows the webview, using the currently configured values."; - this.BtnShowWebViewPreview.AccessibleName = "Webview preview"; - this.BtnShowWebViewPreview.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton; - this.BtnShowWebViewPreview.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Enabled = false; - this.BtnShowWebViewPreview.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.BtnShowWebViewPreview.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Location = new System.Drawing.Point(405, 348); - this.BtnShowWebViewPreview.Name = "BtnShowWebViewPreview"; - this.BtnShowWebViewPreview.Size = new System.Drawing.Size(206, 26); - this.BtnShowWebViewPreview.Style.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Style.FocusedBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Style.FocusedForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Style.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Style.HoverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Style.HoverForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Style.PressedForeColor = System.Drawing.Color.Black; - this.BtnShowWebViewPreview.TabIndex = 6; - this.BtnShowWebViewPreview.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_BtnShowWebViewPreview; - this.BtnShowWebViewPreview.UseVisualStyleBackColor = false; - this.BtnShowWebViewPreview.Click += new System.EventHandler(this.BtnShowWebViewPreview_Click); + BtnShowWebViewPreview.AccessibleDescription = "Shows the webview, using the currently configured values."; + BtnShowWebViewPreview.AccessibleName = "Webview preview"; + BtnShowWebViewPreview.AccessibleRole = AccessibleRole.PushButton; + BtnShowWebViewPreview.BackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Enabled = false; + BtnShowWebViewPreview.Font = new Font("Segoe UI", 10F); + BtnShowWebViewPreview.ForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Location = new Point(405, 348); + BtnShowWebViewPreview.Name = "BtnShowWebViewPreview"; + BtnShowWebViewPreview.Size = new Size(206, 26); + BtnShowWebViewPreview.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Style.PressedForeColor = Color.Black; + BtnShowWebViewPreview.TabIndex = 6; + BtnShowWebViewPreview.Text = Languages.ConfigTrayIcon_BtnShowWebViewPreview; + BtnShowWebViewPreview.UseVisualStyleBackColor = false; + BtnShowWebViewPreview.Click += BtnShowWebViewPreview_Click; // // NumWebViewWidth // - this.NumWebViewWidth.AccessibleDescription = "The width of the webview. Only accepts numeric values."; - this.NumWebViewWidth.AccessibleName = "Width"; - this.NumWebViewWidth.AccessibleRole = System.Windows.Forms.AccessibleRole.Text; - this.NumWebViewWidth.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.NumWebViewWidth.BeforeTouchSize = new System.Drawing.Size(83, 25); - this.NumWebViewWidth.Border3DStyle = System.Windows.Forms.Border3DStyle.Flat; - this.NumWebViewWidth.BorderColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewWidth.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.NumWebViewWidth.Enabled = false; - this.NumWebViewWidth.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.NumWebViewWidth.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.NumWebViewWidth.Location = new System.Drawing.Point(87, 348); - this.NumWebViewWidth.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.NumWebViewWidth.MaxLength = 10; - this.NumWebViewWidth.MetroColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewWidth.Name = "NumWebViewWidth"; - this.NumWebViewWidth.Size = new System.Drawing.Size(83, 25); - this.NumWebViewWidth.TabIndex = 3; - this.NumWebViewWidth.ThemeName = "Metro"; - this.NumWebViewWidth.Value = new decimal(new int[] { - 700, - 0, - 0, - 0}); - this.NumWebViewWidth.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; + NumWebViewWidth.AccessibleDescription = "The width of the webview. Only accepts numeric values."; + NumWebViewWidth.AccessibleName = "Width"; + NumWebViewWidth.AccessibleRole = AccessibleRole.Text; + NumWebViewWidth.BackColor = Color.FromArgb(63, 63, 70); + NumWebViewWidth.BeforeTouchSize = new Size(83, 25); + NumWebViewWidth.Border3DStyle = Border3DStyle.Flat; + NumWebViewWidth.BorderColor = SystemColors.WindowFrame; + NumWebViewWidth.BorderStyle = BorderStyle.FixedSingle; + NumWebViewWidth.Enabled = false; + NumWebViewWidth.Font = new Font("Segoe UI", 10F); + NumWebViewWidth.ForeColor = Color.FromArgb(241, 241, 241); + NumWebViewWidth.Location = new Point(87, 348); + NumWebViewWidth.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + NumWebViewWidth.MaxLength = 10; + NumWebViewWidth.MetroColor = SystemColors.WindowFrame; + NumWebViewWidth.Name = "NumWebViewWidth"; + NumWebViewWidth.Size = new Size(83, 25); + NumWebViewWidth.TabIndex = 3; + NumWebViewWidth.ThemeName = "Metro"; + NumWebViewWidth.Value = new decimal(new int[] { 700, 0, 0, 0 }); + NumWebViewWidth.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; // // NumWebViewHeight // - this.NumWebViewHeight.AccessibleDescription = "The height of the webview. Only accepts numeric values."; - this.NumWebViewHeight.AccessibleName = "Height"; - this.NumWebViewHeight.AccessibleRole = System.Windows.Forms.AccessibleRole.Text; - this.NumWebViewHeight.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.NumWebViewHeight.BeforeTouchSize = new System.Drawing.Size(83, 25); - this.NumWebViewHeight.Border3DStyle = System.Windows.Forms.Border3DStyle.Flat; - this.NumWebViewHeight.BorderColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewHeight.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.NumWebViewHeight.Enabled = false; - this.NumWebViewHeight.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.NumWebViewHeight.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.NumWebViewHeight.Location = new System.Drawing.Point(218, 348); - this.NumWebViewHeight.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.NumWebViewHeight.MaxLength = 10; - this.NumWebViewHeight.MetroColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewHeight.Name = "NumWebViewHeight"; - this.NumWebViewHeight.Size = new System.Drawing.Size(83, 25); - this.NumWebViewHeight.TabIndex = 4; - this.NumWebViewHeight.ThemeName = "Metro"; - this.NumWebViewHeight.Value = new decimal(new int[] { - 560, - 0, - 0, - 0}); - this.NumWebViewHeight.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; + NumWebViewHeight.AccessibleDescription = "The height of the webview. Only accepts numeric values."; + NumWebViewHeight.AccessibleName = "Height"; + NumWebViewHeight.AccessibleRole = AccessibleRole.Text; + NumWebViewHeight.BackColor = Color.FromArgb(63, 63, 70); + NumWebViewHeight.BeforeTouchSize = new Size(83, 25); + NumWebViewHeight.Border3DStyle = Border3DStyle.Flat; + NumWebViewHeight.BorderColor = SystemColors.WindowFrame; + NumWebViewHeight.BorderStyle = BorderStyle.FixedSingle; + NumWebViewHeight.Enabled = false; + NumWebViewHeight.Font = new Font("Segoe UI", 10F); + NumWebViewHeight.ForeColor = Color.FromArgb(241, 241, 241); + NumWebViewHeight.Location = new Point(218, 348); + NumWebViewHeight.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + NumWebViewHeight.MaxLength = 10; + NumWebViewHeight.MetroColor = SystemColors.WindowFrame; + NumWebViewHeight.Name = "NumWebViewHeight"; + NumWebViewHeight.Size = new Size(83, 25); + NumWebViewHeight.TabIndex = 4; + NumWebViewHeight.ThemeName = "Metro"; + NumWebViewHeight.Value = new decimal(new int[] { 560, 0, 0, 0 }); + NumWebViewHeight.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; // // BtnWebViewReset // - this.BtnWebViewReset.AccessibleDescription = "Resets the width and height values to their defaults."; - this.BtnWebViewReset.AccessibleName = "Reset webview"; - this.BtnWebViewReset.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton; - this.BtnWebViewReset.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Enabled = false; - this.BtnWebViewReset.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.BtnWebViewReset.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.ImageSize = new System.Drawing.Size(24, 24); - this.BtnWebViewReset.Location = new System.Drawing.Point(317, 348); - this.BtnWebViewReset.Name = "BtnWebViewReset"; - this.BtnWebViewReset.Size = new System.Drawing.Size(51, 26); - this.BtnWebViewReset.Style.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Style.FocusedBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Style.FocusedForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.Style.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.Style.HoverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Style.HoverForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.Style.Image = global::HASS.Agent.Properties.Resources.reset_24; - this.BtnWebViewReset.Style.PressedForeColor = System.Drawing.Color.Black; - this.BtnWebViewReset.TabIndex = 5; - this.BtnWebViewReset.TextImageRelation = System.Windows.Forms.TextImageRelation.Overlay; - this.BtnWebViewReset.UseVisualStyleBackColor = false; - this.BtnWebViewReset.Click += new System.EventHandler(this.BtnWebViewReset_Click); + BtnWebViewReset.AccessibleDescription = "Resets the width and height values to their defaults."; + BtnWebViewReset.AccessibleName = "Reset webview"; + BtnWebViewReset.AccessibleRole = AccessibleRole.PushButton; + BtnWebViewReset.BackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Enabled = false; + BtnWebViewReset.Font = new Font("Segoe UI", 10F); + BtnWebViewReset.ForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.ImageSize = new Size(24, 24); + BtnWebViewReset.Location = new Point(317, 348); + BtnWebViewReset.Name = "BtnWebViewReset"; + BtnWebViewReset.Size = new Size(51, 26); + BtnWebViewReset.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.Style.Image = Properties.Resources.reset_24; + BtnWebViewReset.Style.PressedForeColor = Color.Black; + BtnWebViewReset.TabIndex = 5; + BtnWebViewReset.TextImageRelation = TextImageRelation.Overlay; + BtnWebViewReset.UseVisualStyleBackColor = false; + BtnWebViewReset.Click += BtnWebViewReset_Click; // // CbWebViewKeepLoaded // - this.CbWebViewKeepLoaded.AccessibleDescription = "Keeps the webview loaded in the background, resulting in faster loading when invo" + - "ked."; - this.CbWebViewKeepLoaded.AccessibleName = "Background loading"; - this.CbWebViewKeepLoaded.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbWebViewKeepLoaded.AutoSize = true; - this.CbWebViewKeepLoaded.Checked = true; - this.CbWebViewKeepLoaded.CheckState = System.Windows.Forms.CheckState.Checked; - this.CbWebViewKeepLoaded.Enabled = false; - this.CbWebViewKeepLoaded.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbWebViewKeepLoaded.Location = new System.Drawing.Point(90, 405); - this.CbWebViewKeepLoaded.Name = "CbWebViewKeepLoaded"; - this.CbWebViewKeepLoaded.Size = new System.Drawing.Size(252, 23); - this.CbWebViewKeepLoaded.TabIndex = 7; - this.CbWebViewKeepLoaded.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_CbWebViewKeepLoaded; - this.CbWebViewKeepLoaded.UseVisualStyleBackColor = true; + CbWebViewKeepLoaded.AccessibleDescription = "Keeps the webview loaded in the background, resulting in faster loading when invoked."; + CbWebViewKeepLoaded.AccessibleName = "Background loading"; + CbWebViewKeepLoaded.AccessibleRole = AccessibleRole.CheckButton; + CbWebViewKeepLoaded.AutoSize = true; + CbWebViewKeepLoaded.Checked = true; + CbWebViewKeepLoaded.CheckState = CheckState.Checked; + CbWebViewKeepLoaded.Enabled = false; + CbWebViewKeepLoaded.Font = new Font("Segoe UI", 10F); + CbWebViewKeepLoaded.Location = new Point(90, 405); + CbWebViewKeepLoaded.Name = "CbWebViewKeepLoaded"; + CbWebViewKeepLoaded.Size = new Size(253, 23); + CbWebViewKeepLoaded.TabIndex = 7; + CbWebViewKeepLoaded.Text = Languages.ConfigTrayIcon_CbWebViewKeepLoaded; + CbWebViewKeepLoaded.UseVisualStyleBackColor = true; // // LblInfo2 // - this.LblInfo2.AccessibleDescription = "Background loading information."; - this.LblInfo2.AccessibleName = "Background loading info"; - this.LblInfo2.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblInfo2.AutoSize = true; - this.LblInfo2.Enabled = false; - this.LblInfo2.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblInfo2.Location = new System.Drawing.Point(107, 435); - this.LblInfo2.Name = "LblInfo2"; - this.LblInfo2.Size = new System.Drawing.Size(320, 19); - this.LblInfo2.TabIndex = 76; - this.LblInfo2.Text = Languages.ConfigTrayIcon_LblInfo2; + LblInfo2.AccessibleDescription = "Background loading information."; + LblInfo2.AccessibleName = "Background loading info"; + LblInfo2.AccessibleRole = AccessibleRole.StaticText; + LblInfo2.AutoSize = true; + LblInfo2.Enabled = false; + LblInfo2.Font = new Font("Segoe UI", 10F); + LblInfo2.Location = new Point(107, 435); + LblInfo2.Name = "LblInfo2"; + LblInfo2.Size = new Size(330, 19); + LblInfo2.TabIndex = 76; + LblInfo2.Text = "(This uses extra resources, but reduces loading time.)"; // // CbWebViewShowMenuOnLeftClick // - this.CbWebViewShowMenuOnLeftClick.AccessibleDescription = "If enabled, left clicking the system tray icon will show the default menu."; - this.CbWebViewShowMenuOnLeftClick.AccessibleName = "Show default menu on left click"; - this.CbWebViewShowMenuOnLeftClick.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbWebViewShowMenuOnLeftClick.AutoSize = true; - this.CbWebViewShowMenuOnLeftClick.Enabled = false; - this.CbWebViewShowMenuOnLeftClick.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbWebViewShowMenuOnLeftClick.Location = new System.Drawing.Point(90, 487); - this.CbWebViewShowMenuOnLeftClick.Name = "CbWebViewShowMenuOnLeftClick"; - this.CbWebViewShowMenuOnLeftClick.Size = new System.Drawing.Size(304, 23); - this.CbWebViewShowMenuOnLeftClick.TabIndex = 77; - this.CbWebViewShowMenuOnLeftClick.Text = Languages.ConfigTrayIcon_CbWebViewShowMenuOnLeftClick; - this.CbWebViewShowMenuOnLeftClick.UseVisualStyleBackColor = true; + CbWebViewShowMenuOnLeftClick.AccessibleDescription = "If enabled, left clicking the system tray icon will show the default menu."; + CbWebViewShowMenuOnLeftClick.AccessibleName = "Show default menu on left click"; + CbWebViewShowMenuOnLeftClick.AccessibleRole = AccessibleRole.CheckButton; + CbWebViewShowMenuOnLeftClick.AutoSize = true; + CbWebViewShowMenuOnLeftClick.Enabled = false; + CbWebViewShowMenuOnLeftClick.Font = new Font("Segoe UI", 10F); + CbWebViewShowMenuOnLeftClick.Location = new Point(90, 487); + CbWebViewShowMenuOnLeftClick.Name = "CbWebViewShowMenuOnLeftClick"; + CbWebViewShowMenuOnLeftClick.Size = new Size(265, 23); + CbWebViewShowMenuOnLeftClick.TabIndex = 77; + CbWebViewShowMenuOnLeftClick.Text = Languages.ConfigTrayIcon_CbWebViewShowMenuOnLeftClick; + CbWebViewShowMenuOnLeftClick.UseVisualStyleBackColor = true; // // ConfigTrayIcon // - this.AccessibleDescription = "Panel containing the tray icon configuration."; - this.AccessibleName = "Tray icon"; - this.AccessibleRole = System.Windows.Forms.AccessibleRole.Pane; - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); - this.Controls.Add(this.CbWebViewShowMenuOnLeftClick); - this.Controls.Add(this.LblInfo2); - this.Controls.Add(this.CbWebViewKeepLoaded); - this.Controls.Add(this.BtnWebViewReset); - this.Controls.Add(this.NumWebViewHeight); - this.Controls.Add(this.NumWebViewWidth); - this.Controls.Add(this.BtnShowWebViewPreview); - this.Controls.Add(this.LblX); - this.Controls.Add(this.LblWebViewSize); - this.Controls.Add(this.TbWebViewUrl); - this.Controls.Add(this.LblWebViewUrl); - this.Controls.Add(this.CbShowWebView); - this.Controls.Add(this.LblInfo1); - this.Controls.Add(this.CbDefaultMenu); - this.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.Margin = new System.Windows.Forms.Padding(4); - this.Name = "ConfigTrayIcon"; - this.Size = new System.Drawing.Size(700, 544); - this.Load += new System.EventHandler(this.ConfigTrayIcon_Load); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewWidth)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewHeight)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - + AccessibleDescription = "Panel containing the tray icon configuration."; + AccessibleName = "Tray icon"; + AccessibleRole = AccessibleRole.Pane; + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + BackColor = Color.FromArgb(45, 45, 48); + Controls.Add(CbWebViewShowMenuOnLeftClick); + Controls.Add(LblInfo2); + Controls.Add(CbWebViewKeepLoaded); + Controls.Add(BtnWebViewReset); + Controls.Add(NumWebViewHeight); + Controls.Add(NumWebViewWidth); + Controls.Add(BtnShowWebViewPreview); + Controls.Add(LblX); + Controls.Add(LblWebViewSize); + Controls.Add(TbWebViewUrl); + Controls.Add(LblWebViewUrl); + Controls.Add(CbShowWebView); + Controls.Add(LblInfo1); + Controls.Add(CbDefaultMenu); + Controls.Add(CbUseModernIcon); + ForeColor = Color.FromArgb(241, 241, 241); + Margin = new Padding(4); + Name = "ConfigTrayIcon"; + Size = new Size(700, 544); + Load += ConfigTrayIcon_Load; + ((System.ComponentModel.ISupportInitialize)NumWebViewWidth).EndInit(); + ((System.ComponentModel.ISupportInitialize)NumWebViewHeight).EndInit(); + ResumeLayout(false); + PerformLayout(); } #endregion private System.Windows.Forms.Label LblInfo1; + internal System.Windows.Forms.CheckBox CbUseModernIcon; internal System.Windows.Forms.CheckBox CbDefaultMenu; internal CheckBox CbShowWebView; internal TextBox TbWebViewUrl; diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx index f298a7be..af32865e 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx @@ -1,4 +1,64 @@ - + + + diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs index c11841ec..6c25679f 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs @@ -352,6 +352,7 @@ private void LoadSettings() _mediaPlayer.CbEnableMediaPlayer.CheckState = Variables.AppSettings.MediaPlayerEnabled ? CheckState.Checked : CheckState.Unchecked; // tray icon + _trayIcon.CbUseModernIcon.CheckState = Variables.AppSettings.TrayIconUseModern ? CheckState.Checked : CheckState.Unchecked; _trayIcon.CbDefaultMenu.CheckState = Variables.AppSettings.TrayIconShowDefaultMenu ? CheckState.Checked : CheckState.Unchecked; _trayIcon.CbShowWebView.CheckState = Variables.AppSettings.TrayIconShowWebView ? CheckState.Checked : CheckState.Unchecked; _trayIcon.NumWebViewWidth.Value = Variables.AppSettings.TrayIconWebViewWidth; @@ -458,6 +459,7 @@ private async Task StoreSettingsAsync() Variables.AppSettings.MediaPlayerEnabled = _mediaPlayer.CbEnableMediaPlayer.CheckState == CheckState.Checked; // tray icon + Variables.AppSettings.TrayIconUseModern = _trayIcon.CbUseModernIcon.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconShowDefaultMenu = _trayIcon.CbDefaultMenu.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconShowWebView = _trayIcon.CbShowWebView.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconWebViewWidth = (int)_trayIcon.NumWebViewWidth.Value; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.cs b/src/HASS.Agent/HASS.Agent/Forms/Main.cs index 3eb10a0f..7e971535 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.cs @@ -89,11 +89,11 @@ private async void Main_Load(object sender, EventArgs e) // core components initialization - required for loading the entities await RadioManager.Initialize(); await InternalDeviceSensorsManager.Initialize(); - InitializeHardwareManager(); - InitializeVirtualDesktopManager(); + InitializeHardwareManager(); + InitializeVirtualDesktopManager(); - // load entities - var loaded = await SettingsManager.LoadEntitiesAsync(); + // load entities + var loaded = await SettingsManager.LoadEntitiesAsync(); if (!loaded) { MessageBoxAdv.Show(this, Languages.Main_Load_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); @@ -245,8 +245,15 @@ private void CheckDpiScalingFactor() MessageBoxAdv.Show(this, Languages.Main_CheckDpiScalingFactor_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } - private static void ProcessTrayIcon() + private void ProcessTrayIcon() { + if (Variables.AppSettings.TrayIconUseModern) + { + var icon = (Icon)new System.Resources.ResourceManager(typeof(Main)).GetObject("ModernNotifyIcon"); + if (icon != null) + NotifyIcon.Icon = icon; + } + // are we set to show the webview and keep it loaded? if (!Variables.AppSettings.TrayIconShowWebView) return; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.resx b/src/HASS.Agent/HASS.Agent/Forms/Main.resx index 3676f237..df700854 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.resx +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.resx @@ -1,4 +1,64 @@ - + + + @@ -57,17 +117,17 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + 17, 17 - - + + 125, 17 - - + iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAnRJREFUWEftlt+HVVEUx/vXIiIi5ikiIiKGISKip+glIiIiehoieomYmhpNv9TU + wAAADsABataJCQAAAnRJREFUWEftlt+HVVEUx/vXIiIi5ikiIiKGISKip+glIiIiehoieomYmhpNv9TU ZDI1PzI1mn7o53TPvefcc85qPvtY2nvffebuY+41ZBZf99599t7rs9daZ6+7a/dES7ZTOwD/D8C+yZbc +tiVuV+FjD5rB+eENBAAnM/+KCQvRdbapSS5RENsGcB2fuZVR0amEllpFQbi2JP+EFsC2HvXda7jQKx3 S5n6kjvzQ2oMwIn33Km+n51LBTv3OnXmXH/fNeMX3rjjITUCYMO0EPmWlibHhx+2zemJAmDMUed8+utD @@ -84,7 +144,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi + wAAADsABataJCQAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi nSqoFT+oYLVa60et0qlfnSSTSebW35tcTKZp5mWy0MUcOMy8l5d3z7vv3JsHXdMVuUt2BHQE3H8BL1c9 2bwMZfY0SH1elJkCni26EtRFJn/UpH/Nk545J3VdEWYKGNvzZfV3IH3Lrpy4dRn56ieeP5xJrm+HLTPw YsWVxV+BPF9yE88elxwjjuzE5/PSyoQI4ffpvCPv9n15te7JmXdzNxGGt6v/vGNLKwHKNzt+FDIJfNKc @@ -99,7 +159,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAnlJREFUWEftlu1LFUEUxv3XAiEIAkEQhCAIAiEQBEEQgsBPgV+CIAiCIAgEQYhA + wAAADsABataJCQAAAnlJREFUWEftlu1LFUEUxv3XAiEIAkEQhCAIAiEQBEEQgsBPgV+CIAiCIAgEQYhA CDQrM+3N0lDKtCxT8jWQfd97T/1m73inZXbv3BvRh+4DD3d3Z2bnmXOec/Z2nJry5F+yLaAtwFnA1ZVI wJZftY63ylIBV5ZDub0Rq2t+wU6QCTj/LJAHO4mcmbGvdWWhgL7ngaRVtacs7KdynGQ3PJvbS8VP1a3M fE+t611ZKKBz2pOXB7VdCoCY/leBdb0rG6ZAY2wzlp5ZX0behSoNYOmoYl3XDK0Cbn6Mldl0mPMbaUOC @@ -116,7 +176,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi + wAAADsABataJCQAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi nSqoFT+oYLVa60et0qlfnSSTSebW35tcTKZp5mWy0MUcOMy8l5d3z7vv3JsHXdMVuUt2BHQE3H8BL1c9 2bwMZfY0SH1elJkCni26EtRFJn/UpH/Nk545J3VdEWYKGNvzZfV3IH3Lrpy4dRn56ieeP5xJrm+HLTPw YsWVxV+BPF9yE88elxwjjuzE5/PSyoQI4ffpvCPv9n15te7JmXdzNxGGt6v/vGNLKwHKNzt+FDIJfNKc @@ -131,7 +191,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAe9JREFUWEftlttHRFEUxvvXIiIiIiIiIiIiIiIiIhE9RfTUU0QiopruN92bmm6j + wAAADsABataJCQAAAe9JREFUWEftlttHRFEUxvvXIiIiIiIiIiIiIiIiIhE9RfTUU0QiopruN92bmm6j y9Qo0mU6tzkzs/PtzjTn7LP2zJ4xGTEf38vZa9b57b3WrH0qKhc0VkqXAcoA/w+gY8/gFp/Xreisddv/ PJeVAZA89Jlk0KuV4i90r0/exvlaRE+x3mPTs5bNSgB9JyZLpHj+Xx2/JVn7rsEa13U2dh1nesJZcDR+ EydziVYCGAhaTlp1TYSLCFC1qDHr5/SVVbvsLZHMygDvcaEGOYTSULlEKwGgqUTNPdm8NOiPqXvbd0LD @@ -145,7 +205,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAwJJREFUWEftlttrE1EQxv3XBEEQBEHwqVAoCIIgCAXBJ0EoCH0RBEEQBF8FwRdB + wAAADsABataJCQAAAwJJREFUWEftlttrE1EQxv3XBEEQBEHwqVAoCIIgCAXBJ0EoCH0RBEEQBF8FwRdB UKsttbZqvdW7FVQsrdRbm2Q3u8m0v92OTs6Z3cQHKUIHPpKdnD3zzTcz52TX7psN2U7sEPi/COybaMjY Qio3ljJ5+r0jS61ugYWfHZlYzmX8ZSoH7jbdd6swEAECX1psS9qRHvvR7spq2t16Ki3ffLz6KRuYSF8C R2ZbspKUQSBw5WMmxx62onWH77fk8oe2rGXlWj5PPE6idSFqCZx+nvzOGpmH78WBQxyaasr017x4BzXO @@ -164,7 +224,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAkVJREFUWEftlt9LFUEUx/vXhCDwKQiCQOgpCIJAEIKeBJ+CEAKfAkEIAkEQfAkC + wAAADsABataJCQAAAkVJREFUWEftlt9LFUEUx/vXhCDwKQiCQOgpCIJAEIKeBJ+CEAKfAkEIAkEQfAkC S/wVZv7IpMwfpSVZlhbK7t79cXe6n7NMzO6Ou7qtSeCBL5d77pk7n5k5c85caBt11FnqHOD/Bbg66aon X0K15TTVykFT9a366uIze2yRKgEw+a8gVtjMj0ggsKc7oTW+SJUAWDl2Y8b743u8GYivc76Rii1TJYBt N1bT36OU78qEKwAP14OUv0yVANYOm7Ltpu/mS08AyAXTX6ZKAKwSe/QxUJfHXZn81V6k/FYqXJtyrWOO @@ -180,7 +240,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAbxJREFUWEftlu9HBEEYx/vXIiIiehUR/QHR24heRfQyIiJ6Fb2MiKur01X0Q5eK + wAAADsABataJCQAAAbxJREFUWEftlu9HBEEYx/vXIiIiehUR/QHR24heRfQyIiJ6Fb2MiKur01X0Q5eK ri6lurq6iuv25+w+9Z01uRuzu7PuUrTLhzMzZj7P7LPPcx2dmTr9JqlAKpAKJBbo3TRotuhQ5pFRseZR 2fRp74XR0o1DQztm09qu9ToNbBtNYzLaAths+tymqu1T4c2juUuHxgsWjR5afHyt7BLzicv054JDcTge ea9GtAVWH1we7dixpZwHODhXYVSxfH4bbRNYvHbozvC/I4tj+TaQHd41WxcYOTDp3QkiUs2HcfTKKP/M @@ -221,7 +281,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAXxJREFUWEftlUFLw0AQhf1rgidPglevnvwHXr2K4NWrIAiC4MWTUiwUigdRClUQ + wAAADsABataJCQAAAXxJREFUWEftlUFLw0AQhf1rgidPglevnvwHXr2K4NWrIAiC4MWTUiwUigdRClUQ tVoUq6igSdMkzdiXbMgmHc10Gy2FPHhQkunMt5Od3ZnZI4sm6RKgBBgZYLnepb17j2ovPrXtgFpWP/yN Z3jH/ec3iwEWqzZVOz7l6fDRC2O5HJxFAHPHFl2891WJfNVffTYPZxHAzp2rUif69AI6aHth6/EZstq8 6rG5shYBnL2lW//UDWjhJGnzfMWim680BDqm5/jJIoAPN1BpI21du0MxG5c99TaSPWDOxnAWb8I8r547 @@ -233,7 +293,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAJtJREFUWEdjcNz79f9A4lEHjDpg6Dog5ti3/yff/Pl//t2f/6knv2NVQwwm2wFr + wAAADsABataJCQAAAJtJREFUWEdjcNz79f9A4lEHjDpg6Dog5ti3/yff/Pl//t2f/6knv2NVQwwm2wFr Hv3+DwN7X/zBqoYYTLYDJt78CbX+//9lD35hVUMMJtsB7vu//m++8uN/57WfYDY2NcTgoZsIqYXJdgAo 4T3//g+MByQRgrIfDIDY2NQQg0cdMOqAUQeMOmDUAaMOINsB1MKjDhh1wAA74Ot/AF92BBjcG70TAAAA AElFTkSuQmCC @@ -242,7 +302,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAg5JREFUWEftls8rBVEUx/1ryspKKStlpaxsla2yslRWSikrZakUCT1RChGR3+Vn + wAAADsABataJCQAAAg5JREFUWEftls8rBVEUx/1ryspKKStlpaxsla2yslRWSikrZakUCT1RChGR3+Vn Epr33ng/Dh+6GveeMzPPj6R867uZuXfO55577rnT1DwbyW/6H+BvAbTORzK4HcvMRUU272pyUaq/evu+ JvNXVRnaiaVtoajOtZwLgMBjh08S1yRT1brI1FklN0gmQM9qSa7LL19tUI+VuvStl9VvJp0KMLBVzrVq S2RjeC9Wv+1sArDyrwRPqn/DzoQKwJ5/Ju2WilWRjiW9JlQACs4SFX8Sham5jetSuHmJZGj6vBLEwQEA @@ -283,7 +343,7 @@ iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAmFJREFUSEutlStwU1EQhiMQCAQCgUAgEBHIioqKCiQCUYmojEAgEBERnYlAIBCR + wAAADsABataJCQAAAmFJREFUSEutlStwU1EQhiMQCAQCgUAgEBHIioqKCiQCUYmojEAgEBERnYlAIBCR yMqKiIoKRERF3hNBZioqIiIqKioiKiLa7zvsCW24hEC6M/+ce/bx7949r9K6MhgMdvr9/jHjiPHzeDx+ EqbNBdJtMAcXoAVuTBbmzQWyb2CWqyZB3SSdTudFcthUIDsC01ar9cg5ySom6PV6r5PDpgLhnoSMTXAA Lpn/CPPDCIRViGeRqM1YDtP6QmANXEmyhDn64263uzUajZ6G+78JJPtBdgiZbVgAXZ3xHPgHOxGyvrgb @@ -299,7 +359,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAA9xJREFUaEPVmi1Q3FAUhREIBAKBqKhAICoQCAQCgahAIBAVCAQCgahAICoQzCAq + wAAADsABataJCQAAA9xJREFUaEPVmi1Q3FAUhREIBAKBqKhAICoQCAQCgahAIBAVCAQCgahAICoQzCAq KhAIRAUCgahAIBAIBKKiAoGoQCAQCAQCgWBm6Xf2HXZnySZ5+WGy/WbuJPvevSd33ybvLztUF61WaxZb wbawn9gZdo3dYH+xc+wI23x5efmMjTh0MCCxe5KKBv9FhzYP+YyHtOLhC2w4vHlIZtZ5RUPMvsObh2TW nFc0xJw7vHlI5ofzioaYO4c3B3mMkMgm9hDSKgZxB9ik5eoF/TnETzguYcMu7kDdMnatRKqAxjN2wOkH @@ -322,7 +382,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAABTdJREFUaEPVmS1QHTEQxxEIBKICUVGBqKhAIBAVFQgEEoFAIBAViIoKZAUzFQgE + wAAADsABataJCQAABTdJREFUaEPVmS1QHTEQxxEIBKICUVGBqKhAIBAVFQgEEoFAIBAViIoKZAUzFQgE ogKBQCAQCERFBQKBqKioQCAQiIqKioqKis7Q/v65vZBccveO93Hv9T+zk2R3k93cy8du3tRjcX9/f/k3 AO0/Jvo/gMO35nuIGRNPDnD0DY7NWtNDX7zwOcK8iT3q+ncCjB/IK8rPFE+NPaW6+FWgt2Qq0pmmfWb8 K4puJ1E6X4L2DfSc6gy065gVIN+neALNUv/omAba3U0CY/rZE8D/Af20ZhbIf0O5/SHZmZkYLbD1FGPf @@ -350,7 +410,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAABH1JREFUaEPNmS9Q3UAQhxEIBAKBQFQgEAgEAoFAIBAIBAKJQCAqKxAIBDMIBAJR + wAAADsABataJCQAABH1JREFUaEPNmS9Q3UAQhxEIBAKBQFQgEAgEAoFAIBAIBAKJQCAqKxAIBDMIBAJR gUBUICoQCAQCgahAVCAqKioQiIoKBKKCGej3u+w7Eu4uSSEvL9/MTpLdzd6+vNv7kwyleH5+Hn16etpC LpBfyB9k3czdhuSnlTTHAujmzaW7kOcEid5mKQeMm1t3IfkzS1ZPXN1mT0+ey0lz6S4kOpWlniXPYdpM riaQDWQXWTV1tyDpHZJzcL5launXkN9mkm3NTN2CxI4sR7EgHcdV9I9ScLxBviAf3A0VcMuonbaDkrNE @@ -375,7 +435,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAuVJREFUaEPVma9zE0EUxyMqEMiKigpERUUlojKiAlFRgUAgIpEV/QOYQVZUVCAQ + wAAADsABataJCQAAAuVJREFUaEPVma9zE0EUxyMqEMiKigpERUUlojKiAlFRgUAgIpEV/QOYQVZUVCAQ CGRFRAWiAhGBQCIQFREREUgkMwmf9/aba0M2vST3YzefmTd3+31vd793c7m92XSaYjqddieTySVxRwyJ AXGFfqaSPMHgc4x+4rgU8n0Ou+qSD5gy87/cZQnUjYl9dc2D/+887c/EKacviBPOr4m/ngTOv6prevDT DbYCmHur1Byk7LdRXAT0lEoLpi5lyO+85CjkP6jUam8kpwUjd/Jkpk4lRyF/pFKrvZecFoyM5MlMlf44 @@ -393,7 +453,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAABpVJREFUaEPFmi9QHTsUxhEIBKICUYGoQCAqEE9UVCAQCASiAlGBqKh4oqICUcEM + wAAADsABataJCQAABpVJREFUaEPFmi9QHTsUxhEIBKICUYGoQCAqEE9UVCAQCASiAlGBqKh4oqICUcEM ouIJBAKBQCAqKhAIBAKBqEAgKioQFYgnKioQCGagvy97NpvsbrJ7917e+2bObHLOl7PJ5t9J7p2aFB4e Hl49Pj5u8dxFviIXyE+Tb8gxsod8greMTFvR/wdUYIbKrCIHyA35kUCZX8gR8obsrLn9b8ALN4dUOgV8 /UY+kHzaXuEFy7zoyr01Azh3yDVybvIDuTVzElbmjb1ucsD3LI6/FK9pAtsNsktyBXlmxRrANou8hruD @@ -427,7 +487,7 @@ iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAARtJREFUSEvdlK8SAVEUxjcIgiCKgigIHkUQBEEQPMQ2QfAIoiCIHkAQBA8gegRB + wAAADsABataJCQAAARtJREFUSEvdlK8SAVEUxjcIgiCKgigIHkUQBEEQPMQ2QfAIoiCIHkAQBA8gegRB MLPrd/d8u2OMsbhrBr+ZM+d+97vn8++u4P+J47gWRdGEWlF7lVtPqYaOvQfhISEn+l2cR411/DUYHCkn F84ONfYczJQYOtu4gV5TA5Z9+sZ2DfSRVtZ4Pgw0bdRA72kl2Qnsbc010G1Z+XC4wUyYFrorK4O9iQtO QfdkFQOBS2UnoDuy/CGvQmB2u1i736su2x8C5xZtoGey/CFsqtwEtLsAVdnvQ4i7urfvfEf5Pc0pd8JX @@ -437,28 +497,28 @@ - iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAIAAAC1JZyVAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAASpSURBVEhLvZZ/TJVVGMe/cEEQLlyueH/B5d7Lr4v8CgPM - CqqJWoRrkIpglBrKMMdyLU3+sc1kkFbKlmlruYhlscw23FyL6cC2XI7Vao1loFCKFiQ/TQZFnJ7z9hrv - fe4F2xidfcbe7+U833Pec57nnBfiyv8B1/ME1zPSDfETxM8QVxXoucerz8xw7UGn4jsBIRTGIW5BDEOM - QIxBTN75vU/pyWI94XqaLohe6dt+Jqiu2rgu35adHpPgdDqiXS67M83tWJ0b/fyzpk/eDpPdRiEueTlo - 4HqaaxA38fR6C5ACuDMyHEUFti0bzJVlpm2l5tIiyyM50ZGmOCA12e3q69CJIS8HDVxPc0MO4451AEuB - xI0bLAdfNjbWh508qm86oj9aZygvtVRsMp9+KwRIO/CiUa4ec9DAtQqtGEGRkzj1jv6ZddY4u8vfL4HG - ozdTSAxekLitxEx9bGbXE6ussjOlCUUxKwWuVS7LXfn60wUt74fKvKKdH4D4FeK6kma0Gb9B/C4nMT7m - 9/B90QE6d+dXATJHKJBZKXAtoa5/ovXkQiBZ2ZglNP0IY1yK2/Fgtn1lbtTKnKgHsuxLEh36MNob+WZK - n6Qvm4MpkLspcC2hgphCVmqM3Rbb2RH48TH9nu2L1xdYc7PtlGAJLidBDySL11irn4tsPh7a+X2gKTJu - +T12uXS+6olrCa1MP2iOBStsMuy/kbvMDiRQoAxnhr6HoQqfwI5NJkohXUBCkttRkBdFcv+uRW/uM7x7 - IJw48oqhZrexaoupcLWtcI0tOd4JpFc+ZZK1TOHMcMa9+UXW9vG68N2Vi9c+bo0IpT2gDUjyhHYlQR9O - /5Kbd6zOIF+FAn1lAdcqPyp1I9DeHHT2RAht7ORlv6Fv/a9fCOg5H9Ddht7zGLyISTpjJpAcb8lMi5VL - R+k3w1nA9TT07gLxzljAJZeCipxcKK1vKs9jqeKvnUK8IcRes2Vp/kMGyhpZN8zkDlxPQzEC5cWLgAxx - LUb0BYtuvbiaLvqrxFib0DT4F+woVRKBOWjg2gOBmp2UcvePDI2IqX4xOaQaa9rAwDDN49Vdcxum8TUa - xnWpa1B19WodHV2A88PDcxlmEq0ndEBEa1u76urVzp67CIR/8ZFOXj8sXAPXHtzGDy00jK7xg89UV6/2 - XsMZwL/znE4ecSxcA9ceDGL4Gz9atZraRtXVq+3b30AdRr/zk0nIwjVw7QHdvj1kgortlLi+W3nF6zQR - mf10frNwDVx7QBfoAEx6rMqvVl29Wt6jeyzh8r1lZxaugWsPaI7juNeNpLStqqtXS0wpz0pSvkZ8HWX/ - wrUHVKFTKFqBUONa1dWrhUQUPZknu81yBBBccwSqymh3clRXrwbkVZbcpWgIrjkCDbVBgKGp6bRqrGmf - t1wALKfqdXMeph/jN3RZGQYgNnP5C8UbD5ZtPrR5a31J2cH0zCo6/wsfWyhu+/u8yrRwzaEb4RamegNr - XwpalgKXGbYImMJgNcjUOLRXJ0Z1Ms2oGwv0hGsfkAVdAbQs9DlBnzh0C1Al0t8/lB/pWrrbGATXs0Hf - EvRJTfXxD7NmMIPreYLreeEK/gbPMONd+jRQGwAAAABJRU5ErkJggg== + iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAIAAAC1JZyVAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL + EQAACxEBf2RfkQAABKlJREFUSEu9ln9MlVUYx79wQRAuXK54f8Hl3suvi/wKA8wKqolahGuQimCUGsow + x3ItTf6xzWSQVsqWaWu5iGWxzDbcXIvpwLZcjtVqjWWgUIoWJD9NBkWcnvP2Gu997gXbGJ19xt7v5Tzf + c95znuecF+LK/wHX8wTXM9IN8RPEzxBXFei5x6vPzHDtQafiOwEhFMYhbkEMQ4xAjEFM3vm9T+nJYj3h + epouiF7p234mqK7auC7flp0ek+B0OqJdLrszze1YnRv9/LOmT94Ok91GIS55OWjgepprEDfx9HoLkAK4 + MzIcRQW2LRvMlWWmbaXm0iLLIznRkaY4IDXZ7err0IkhLwcNXE9zQw7jjnUAS4HEjRssB182NtaHnTyq + bzqiP1pnKC+1VGwyn34rBEg78KJRrh5z0MC1Cq0YQZGTOPWO/pl11ji7y98vgcajN1NIDF6QuK3ETH1s + ZtcTq6yyM6UJRTErBa5VLstd+frTBS3vh8q8op0fgPgV4rqSZrQZv0H8LicxPub38H3RATp351cBMkco + kFkpcC2hrn+i9eRCIFnZmCU0/QhjXIrb8WC2fWVu1MqcqAey7EsSHfow2hv5ZkqfpC+bgymQuylwLaGC + mEJWaozdFtvZEfjxMf2e7YvXF1hzs+2UYAkuJ0EPJIvXWKufi2w+Htr5faApMm75PXa5dL7qiWsJrUw/ + aI4FK2wy7L+Ru8wOJFCgDGeGvoehCp/Ajk0mSiFdQEKS21GQF0Vy/65Fb+4zvHsgnDjyiqFmt7Fqi6lw + ta1wjS053gmkVz5lkrVM4cxwxr35Rdb28brw3ZWL1z5ujQilPaANSPKEdiVBH07/kpt3rM4gX4UCfWUB + 1yo/KnUj0N4cdPZECG3s5GW/oW/9r18I6Dkf0N2G3vMYvIhJOmMmkBxvyUyLlUtH6TfDWcD1NPTuAvHO + WMAll4KKnFworW8qz2Op4q+dQrwhxF6zZWn+QwbKGlk3zOQOXE9DMQLlxYuADHEtRvQFi269uJou+qvE + WJvQNPgX7ChVEoE5aODaA4GanZRy948MjYipfjE5pBpr2sDAMM3j1V1zG6bxNRrGdalrUHX1ah0dXYDz + w8NzGWYSrSd0QERrW7vq6tXOnrsIhH/xkU5ePyxcA9ce3MYPLTSMrvGDz1RXr/ZewxnAv/OcTh5xLFwD + 1x4MYvgbP1q1mtpG1dWr7dvfQB1Gv/OTScjCNXDtAd2+PWSCiu2UuL5becXrNBGZ/XR+s3ANXHtAF+gA + THqsyq9WXb1a3qN7LOHyvWVnFq6Baw9ojuO4142ktK2qq1dLTCnPSlK+RnwdZf/CtQdUoVMoWoFQ41rV + 1auFRBQ9mSe7zXIEEFxzBKrKaHdyVFevBuRVltylaAiuOQINtUGAoanptGqsaZ+3XAAsp+p1cx6mH+M3 + dFkZBiA2c/kLxRsPlm0+tHlrfUnZwfTMKjr/Cx9bKG77+7zKtHDNoRvhFqZ6A2tfClqWApcZtgiYwmA1 + yNQ4tFcnRnUyzagbC/SEax+QBV0BtCz0OUGfOHQLUCXS3z+UH+lautsYBNezQd8S9ElN9fEPs2Ywg+t5 + gut54Qr+Bs8w4136NFAbAAAAAElFTkSuQmCC @@ -487,4 +547,8 @@ NLGJMOvFeKEqY0ecbYgf8dDTDERBTzMQBX0DvaZvoNdMcQMi/wKHssFyJvHDuwAAAABJRU5ErkJggg== + + + ..\Resources\modern-tray-icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs index 5222a21c..ee404285 100644 --- a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs +++ b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs @@ -37,6 +37,7 @@ public AppSettings() public string TrayIconWebViewUrl { get; set; } = string.Empty; public bool TrayIconWebViewBackgroundLoading { get; set; } = false; public bool TrayIconWebViewShowMenuOnLeftClick { get; set; } = false; + public bool TrayIconUseModern { get; set; } = false; public string ServiceAuthId { get; set; } = string.Empty; diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index 0a90d4a8..b9cc36b9 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -3055,6 +3055,15 @@ internal static string ConfigTrayIcon_CbShowWebView { } } + /// + /// Looks up a localized string similar to Use modern tray icon. + /// + internal static string ConfigTrayIcon_CbUseModernIcon { + get { + return ResourceManager.GetString("ConfigTrayIcon_CbUseModernIcon", resourceCulture); + } + } + /// /// Looks up a localized string similar to &Keep page loaded in the background. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index d93caf19..e7b8bf5b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3491,4 +3491,7 @@ Als Payload benötigen Sie den Namen des Audiogeräts. Ignorieren Sie die Schonfrist nach dem Aufwachen aus dem Ruhezustand + + Verwenden Sie ein modernes Taskleistensymbol + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 8b21c62b..859e0535 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3368,4 +3368,7 @@ Requires audio device name as a payload. Ignore grace period after waking up from hibernation + + Use modern tray icon + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 1c609964..1c58dc96 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3367,4 +3367,7 @@ Necesita el nombre del dispositivo de audio como carga útil. Ignorar el período de gracia después de despertar de la hibernación + + Utilice el icono de bandeja moderno + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 4f459b6e..9570fa4f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3400,4 +3400,7 @@ Vous avez besoin du nom du périphérique audio comme charge utile. Ignorer la période de grâce après la sortie de l'hibernation + + Utiliser l'icône de la barre d'état moderne + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 3556e82b..9a8ec312 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3388,4 +3388,7 @@ U hebt de naam van het audioapparaat nodig als payload. Negeer de respijtperiode na het ontwaken uit de winterslaap + + Gebruik het moderne ladepictogram + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index e888467f..5e45586f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3477,4 +3477,7 @@ Wymaga nazwy urządzenia audio jako ładunku. Ignoruj okres karencji po przebudzeniu ze stanu hibernacji + + Użyj nowoczesnej ikony w zasobniku + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index cc835f66..c12f692d 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3413,4 +3413,7 @@ Você precisa do nome do dispositivo de áudio como carga útil. Ignorar o período de carência após sair da hibernação + + Use o ícone moderno da bandeja + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index fa71846e..5f839459 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3380,4 +3380,7 @@ Requires audio device name as a payload. Ignore grace period after waking up from hibernation + + Use modern tray icon + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 4eb99872..fd37d175 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3436,4 +3436,7 @@ Home Assistant. Игнорировать льготный период после выхода из спящего режима + + Использовать современный значок в трее + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 732c2f7e..3a216e73 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3516,4 +3516,7 @@ Ime zvočne naprave potrebujete kot obremenitev. Po prebujanju iz mirovanja prezrite obdobje odloga + + Uporabi sodobno ikono pladnja + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index dea9fd38..5796ca75 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2979,4 +2979,7 @@ Yük olarak ses cihazı adına ihtiyacınız vardır. Hazırda bekletme modundan uyandıktan sonra ek süreyi göz ardı et + + Modern tepsi simgesini kullan + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/modern-tray-icon.ico b/src/HASS.Agent/HASS.Agent/Resources/modern-tray-icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4bc498569af998717d123e342e879a1b7957a362 GIT binary patch literal 52111 zcmeFadsI``_At5w3L+}Cz7>hqR@}?}h)N#3T3SIc5cDWL)h6xfj*nl>!&;@?QiA1dgMq-Eb0-_)cfT>t9ruqr?jIj# zbhMdk&9&y7Yp(fPYp+8P9QdE)V*>tW5xcGt#ANudPopCv#*Uso8vb+a>bKsEB?xy3 z{PXcBH-LQOKk7&Lmq+$n>vITV+-3ZS(~%{6mmmsGCKDV?>?|FDm#IX-!G==}R zsbl3EU;StC(Yu+y+{s@!>!-Kf_8Rki<(s4FKXI2ZqG9$4gcXXpa3d@Y{uyM>fN+4~VINA1H6;FI8F#T#3L~Tk6)P(6Zhe zzXbk8+jjku-2L$HX3OLNcQy1=uO>skS5THoM0yl0RNgHM?QB~#)Z5uOZ%FW`_F+hi zw7k$q->EHE^-F8CcW-~#CTisia)>d2yH?S?u2v@2atN_3mYZ^9LDCdgZ!AC#B56Ot&Mv3!5t~cd#&Xs9>f7m%0rPGi4Mc3d$)UUKjKWNI(K6(7>k&hmR73QfjEJIz7 z#D960r4DvA^FOjvH73zAdthT|I6-Jh!w(!kpu_Ji z{od?@qFRC&j4uqB-u?KuA@?3*Sf0R^aWik{$-+TfK%?!K&PQE~x2fuMdOPv%iWaY{ z@{F;Bsdg~(&F}h$N=~|_!(izd0^{sygMMUNEFGDY?Zb3F6*}Q*|$RfLH0(v%Z1pgYLRvWEI|q4-Nd3s0G1?%ae*gHi4TF}d$3JY&rGv{2hB0;RQI%WL zw>Z|^Rglpx&^34wrN4Z#Xuj9bF6}R&e+f70uU0h%{7Mg!2a7-W#?G0^OTgz8DjNu* z^yi<_qRTdl`yZu^%u;m)+${J{uGnx@TPPNA>oP63Z6SGMw3)(dIfXl)9P6-jS;M&3 zj&<}p zu&w`(hjiCpQ5)|GZ{D^Bg^I>SXzxea$`2Xtp-L)aP zDdJ}TE`51PXtQ)-+fdh&!HfCVHtTn&7M2uRkB99#UKEXYdwiKqz5Ak^NJ`Nfdz2bTMf zAGDq4QNqG|_Cb!l@y)vu5AuX7A)+ng9lECeoYHXK77P-SNL%QL9rlH(|Dt)TWIefl z1Tmc!KF9WajVj}8iSK-#n5rD?=3KDz7^rGalib!#8%bQ1w+UHgkF4pQY+687yAYd0 z`d{d23v+G#sB4Ebqs{XBu8S=LM}G;TU^@={Wcy)@c9-?RlJgh0xObu@3jD5drfbT2 zX)<9-(zX&I1D)S1JL>N*9e839mV`B{GTM4Ijm4WD-cRn%ajSdq$Il%tv~BP=p4|kS zKH|MPQBo-PA47O|{^UNSTr_gXx@hUp#jei##hK<#L7Htq`*7f*jb9R$*QMx_*X zGY-@g-)$^uvp#9KDR{WGKhw<+Y7n+H-d}n#Z=<>Fm!n2^AT09N`)$9wp46>(BMv|M zJ$&eP$?l@ULgAB!@-S{po(=*rF5K%)*E7B^!TU*!ZaH z>zcZu8%qZs@O*6DoZBui;<%o_(*D|AM_zCv9@D>H9=u&5`CmrNyii=zXrY!4+A_2q zJut-0!pFTeTARMN;G>qhjR%CFhEq3&HkOwZ2F-D7snLsuMsoYU3^EdGu}{H|Vf`*W zJ!Z2nAo2?f1yW&K8@;`7M_#$1bA0h#y>>{f9~|iI5;nF9>wJt24>Joc)@AnV%-b#u zoV=Mc^gj4CB5&7|MUT~)RJAM7pZlNLLp^?fb_$HCgTH4OQ-<_{GMgsAGW4hU;@2Hp z>n)oHe#oBH+HB?h1s~n6d>GdJ zr?9Z_BX0Sf2bCRk*W#=TgAK-A$TBsUrYbJKV{GYcx!+tK(wY%gD6rM^KK@;l^XwTic88r8Tr(ecJ84_ZJfvxx6iy)$bjGFydmO zc@&Y}GSu6_EgbAc9r=WGPgXCX!4=_O`VM;c=clL9ktlu$cVfBmoDP}L)84pO=N_;AikgXD82tV$ zx_zSGD%DndV_;IjtdR#3%-;5&<6^mQat?_s_Fwf4R$}z~y5LdFPl=Rtj{<9vI)eGB zUZiu6RHsl<$IlXK@7(lAIumu3AWY#jXM$g&D8f!seM~|9h|+MCl4&GzD|yi_nzM%W z_Mag!+!r~W@n&E9PqEDFh$KfT#Bc`wh7!2moUJn9H0GE3zRrD>dIKfJV2L2I*VD_# z*PjzL+UH4$ELb+8y2h}bX(UQwxMMgsqs>9gPYIx|P=8AXdoe%L0rk1;b#%8~FiDXG z#E6t+m8*TEV=>$T;*PH6Ii|1aKa?w15~d3tZ~yrXwbw@yLFw(1$qu49Uh4VuVY?^- z4_VXQ(+`OFc2TUYN(zV&eRISx6Ci1Na^)1 zu}UGd%T&FI+B;RULX~HyVJDxfCcdC%q6sC4dZo2zPa^4yM>!U!Z4?BsyeaX$-EUox1En98M_I*eBjg7ClcKj43qOf4VEZ=AfIsW53y(VgR%j**qV^rmlua`I$p;V+XH?5zEvdyjW8F(WgI-s-V0NXT^bt7-mc(NEO% znHA!7h+qevZB{ctx%k7Uz%QGd5O^rM?W*4f=~AZW@fE4JF265$uu74(b!v^qE|oLX zX`fX}f4(d&XBhS(wTFQz((;C3V-lwH!blgUk!B9iWv6_<%ySp?xU-hBxWFLx%~PZ} zkc+^Blx4(Ipv;p2UZ*T>s}#L1Tqn7+}x0&F>~DhX8lSQ@^&rv%5y4#3`+}>?)J>l>KZjF4N(r5j zeJ04h!Bdu{?#G_Fb!FoNW2K9jo|6+xOgPPXWyZ+Eu^FBS1amer5SH+;R_E^;A#Y$d zp86f?*BL-~hlRSmyOLz$MIwE?;Oh@k0x!z7utY9}{SMs&9y3T5zXF490A7GTcSC%pU zadaoMbtRvYXWu~1pOF3@d9obFUa3b=Ur$HQFH*NZHY)rX;Ie981x|Clt}LdnCMcX4 z@=abuJ%dA* zriEVasR^>XA@6G6(+I4tEPJnSmDH~6m4+$EG&u}rw{q57N z5>znA8Pqm)uwWI_F}Kq9bL6onQaV2Otj2C67LhN#hGyivqQON=-ex+QK5brx3*KHu zU03@HmN3A+3g5#FHi)d}Eh(eH#9(v@W7cqKdg7#Zf(Dho2eD^;NzfV=2p;0VJ4j_$ zjb`G2Tw|BeS=~#EadO-dgNYe-VSG*PnF?Zak5Kx%F=Y#wK6|NPF~d&`Ro$K{Ww0e{ zFlUfLgF4mF{B3 zTcGrnU`M}`K}S>gq5TZk*W?G%aE|K=&D@*uTE?NMW2t{nK?)m`{@>#|V$b@6%{}{2 zW{^DJ-$3nRq?@!N_3Dlq zBXagiLHGKo(4P?o0q%ih@QZurD|@xTBBT1p-%kCX6&0G9rrshO*}$xV-=XIEeJiBF zrXi&TqWw-L_)i}#SdRm$+jM!mdzBdsa29{cx6!g3_Yl6>T~k-K2`7=PP5luE?AfTA z7uYBgGfMHiU@3#VlWsMEd0;H%)LP#yNOoU}Dj={*l*x45uUNAlU3V+r?4_wHW5Nr` zCThjx(SmM?QE3^~8LOR-)2N>lOk;-RQK^?MkiA$(CbFBFKS9bd0lS+*?PBH$T$`#! zf}qscnC(MDWdr`aPy43f!CeM8o$lV0N#`^3be;B1Mx|UT!{DKNG~&h*Tr4?hcxl?Ip~sk~B3*(1_1<*JbzQm@O))*w()MEx~!mAt{Nl&@+#9v51IKw>C%N)<*4raRQktL*fB zMa{481B1coOcH-ga&e2WBM4gKpMLb-0 ziKpB%CUAvxK2*0=9e1cF_CZhkhTk0}sFeVlJ~>Kz0?~A&`MG^6-3gakxuJm7Fw+av zYZD(QW{hsC6%7p!dO&t9`c|BD9@Nrp9Rn1U2;tB7l<$g75K~CDDuuIHrB6yp-E*mo z3gO&LG%wRsl*M7s^z8zV(RuO)CWl|@*jovU&c<1v&bLzTK4647TX5F{gNi1laQ0S3Cu81|-b&5AG+7YI%+pNWw$Bqg zr3_;OX2|oRR%XbR?pAb0YLuCe&z$Cqyy7SU}0go9H z_+b*01*d(3@16*nfu#Qi{4ic|a!iR7BM4?OSg+_QI}n+BMl({Ks`9*WOk^0I9K#x% zJS8f$7x{rS|LX~Z@tqt77#C_`fG*VO|2vTB31J1()8Ww>^lr+V5&KqUCl}F^mZg4l z3Bn4bXoVD~CLp^0$wT0V+>C;M!Jh<0aWhdHh9ust)iB-aCC$J8_CM4UcNE1yjfPPy z1_)Ap7WheX*KhxzF4%#N2&f)`BI<-42%O?XNX?E=`noIDti%d?u?Z!~$OQ>1CNE$| z0wtLe5&96t`!R%i3=lS7WNk%R4l;Ae5xv=IkJc__X#X;Z;TlD86eHGbzIg>35Y``Q z?s@k^I-i-G4JQ|z@eNte5B-IiXCYNOJkN~;?GhYv(}AFQ2vo12E**VCmopZJyrs}E zUX>X#lwQjWiE$?OL``46CRED|8AaO5E~x1kfitAz-oQW9^cykS*BH`qZ@{1A3VC)O zh8DQAJ%@q<5D2N$zmMYfdIq?M`t>O8P6p@*_tgl*ef$jYH1*d}FyNCJAesQ2Xr%7S zyv}&-LS}Y0uRzOz(FH?imTnru5n7JYsL%$2_8CrJ7F7E#V+<1&I*QG67#JEgXIQQ8 z@}+p3h%hjGIQ0StY&Y6VG<*om<_N}rj~2DI#;}`B#?-%3&wr64>zs&2xE-x2cAXME zr2^9?G{WwAN`J<@L2~->Yi*)cyTMy=F&1YA9WR*nJ=%V>?Aad&@e!y7b$=Oce^Z&r zJe*2SiJfv99SXV&_Xt^zebBY3e}4@EiQQY}LLcw58a$pSvKI?p`5wJgZKXk&{Y6kR zGbo7jW%N=_`$ad~W2kL*N)t*d(I28?bB|E;+6RqKJ^Wgg$agob%NvId67iern`ga; z{uyNA?Omf`qDb_s-=otR5Aw-_;XMw?${ zYd7k-f)k9G9`Mbt3en1P_m;Xwt)ve1wco%#t$-Q*_5cmCsv&VTgll-|`b9t0H2G^fSo zCdirmQKuhKDzbvp>#dv2M#M`#*DzdK~=ax(hrJm zE`tCcnxK60ReKt>R5DF>;Wuw;>Bz$g=BbWSV5H3V_1u~=uj4DFD>ap638txZ=59z9 z0L}C!^}=2=nd5pksIW95RH+`MR4(>nq1uBS>DI64^ZuD1D5A?RNzul>92gOm@UG zzOQ1OkCuT!%b=tz+KbfZdvfHRlb{Im3EI9}=M0k^X#b>k>_NjRuGDySCTbV;Y*5W{ zR7FSWu8hE>ur;Q0KX%EjHjH&(y-CIHVG16y-b>S|rEY_AgNLJO1hrxhVELSvo^4chwKVc@PZJJ3hxUAC!6OfyMG@vm4nhQGiGI>Kgq?h}ZiUuzS%gY4g zIS_D2%Bi*W5bWIj8+AS0zG8lFrLIuVqb{nA)B-p>s=fOvRn0anOI@ah0F7aMlq*T|AE z-FXBsN*~HHW@^r97O+?{Pxs^Nq^mK*zZt6yE?q6&nwdCe`bA*ikT zY*|LFvaI0vYH5Ts8dM9mK;W4JrIO2IC~vGu;DXK6t#qwEr+FP{KY^hXTqbbX0QF(& zGzW_j7}yHjP?sG0xl86d-87mi&B>$(Fyul*gc&M~cPl3Kb@i9zu1;`8`&xqB(eAQT zR5rganXkyvf&ucxq7ZrshZ)!zTQVkFK#v1kd0}6eZWkP@UD(4iCST)*=4QApRJZGy zwsK0U20QN=raK`CC6_+ac0m+|`TnIj0$&q&ezk*ZDfyBsQ=9-Fe)qGWDHwh@>|y#m z!M})Ye6uHJGP&n8ZuzG*o-B8(Q(q&>l@_rr0l z{T@VXXXh(5<1nyuX1p`TJYPhQxs*jd!SWBgtAbIB@+s9yWmy;|jOb9Drz?e0#QVBl z_qK$LPcG5D8`D&pAm^ zs++%)4ek&#{uA8L@ID1X(o<=1WsG(vMuat@;SI-NK?&h~GRGYv$Tus?f}Q8WAnYJt z>)j>SOxUdJb#o?>Ci99PQ787o-gxJrNaL({Oje&5npNKz6z8ohTjV^X%rXl{g2?j9 zV(OV4Cu!65AK6Z=-#drA@J<>X;&AeelBlETjqk$gf(gnCw<1C#oaNZFR>hrGRrUtf zBg9o@T<6(xs|-gE#%QNucxjY(x;d#iab970X)~GQ24^T&GN@gwpT}jBMCtO5E~b|5 zg=)w zsDf+TsoQ(TQ>CfvY~p9|-lq-s@mL4K(OQUwoR#QQtU2v)oOUt%aFRiS){O1{;R=6TK=_p_$PooQ5JaRsPD^K#`F@!LoO`8JP$735G ze?$-M{QxeZg2K6NaWZqb-pcchtscC#_ZF zwVbpTs=sN*NY`}4`W;cf+IB8yV`!XTO=h<*HAMVOF}5$9n&S$m>Az@p!I|4P%@;P< zY@0T~eNG4`-zs>QxK%B_$Iy`&z=w-OYn3N47-j@geoFk%(G*ZKXn=9i4+fs$y+-7J zZvCP2qUc=u2K}8ctihL^$msc$YAi?Ufg`@MZP7C$RvS2_eCzw1mP% zFOzxN0Ja8sR9=#E%l zHmll{Z6m*+2BArU;w6lH^K&@Y+yApS@-|j4vZFz?rcNl{qts{qKK8*{+6~|-)gZj= zNL5@QXiU?Df_6cq1@p-_agw_usTPXAI$dIf~T$@ZdoSfOB4E+k6T5~wVj2RA`|p91-+%M1``ehN%eU1NX` zbS=0S8MUmBQ2lHNlEF`bQL0}UU@hN_nXaAwjr8Gr)uJ*+xBz4*y>p_>ba4LTDN=w3 z))^K8oDpmHo13$1=1;GT(@r2d$zWs`r&G^-f83UJOFC$3mkX{n(uQI3tudz1A2gepYha^l4m2@yO{BWTia{}K*~1M;U7M~2*AuB; zR?*!JTlNxcSq&t9%=UsNTolO)z233=N)v$Bow(f9F~Cu@Q23>_g~c%)g|x_NxWCbs zeXwH@M?Ftr#L^TaWY}M?(8B%0O>$$tQxnYev`J9`49#bo-lk`uHDQ!9 zTG*IsD`dqREi-O(q9Q70C)c6qRE|d(bUu1S)N4}zha;F0dJbxpTT7mKjtg*$fn*~fk6;Cyr!%u22a)cChZeCcKb9Nu z+7m0pCd?5WLhOoZ8}>dZyeK+_H66LFR_1;%E8je*r#a-S&e55tI0-5Q^B9K#E2|PW zLKCz7P*Ce3{b70brt??_gyPUb*5r_{q+e-##fe66fMS-$JA4~+#OPqUV;t>`Q4Fe1 zQ@NF~m3)oH8*J`yv6T4f+8DfO& zCNn*xgvj*ph<|U!K$fvAW~CA9<*e$Mf~ia-70vy?dfO@-(H(AZLU;GD7>g^**-eVM zd>ts`y^aI~R&yKzsOlO)g*zUmi!jK3JNoVaiO)K=1P&pv-0Vifd z0*iDVZV@fH2dmW4MlaNH(1ev@p4L4*}~kYR99h#Y)fM2z ztA#WxWVs%t`IDe`Vl-g5m?Sf$NS59paG2!NJlGTvJCmsG3iD5=7|jn>etNJbLg zp|`u>oaM4^ADpPJY-!)oW;C59H_CNn%Gpd$-gUC~jqFQjgWSd!pL)7%3qK=K4vQaYRo& z!H6MK!P~%~8I?wNT=-Bj=R05f5qbchyU6rS^D8DT72pDq-kpfh10l(wZ8)SW={^SK zk{e}9L~ofV@BAzSYUj|Fd&GK#Gfx-FNsDWnhC!p4I`Lbp{WgY=jQ75JwIE7uA+y{e zLWF32$%P`(IMg$^*gYfrZT<5-{`3qN@Uq=-o9BU!IpbIm%?&ht(fpF>pmpKy75bT4 zs1V*%FehF~r+GWnuL#zt*V88vtL$tUUn65~Q6|ZFxoQtVg!&DtaU{?m0>=dT>p~YH z{qN9sT_a_|F0~15i;+f<;{BTxb7;(avtY#Ta>WdnEo9bMpz&5k*}W+KP$8h1E|VS4 z^~sxOrzhGTAln7h2}A85BatFy@tmFsg0N+k;yegeJK%~?R`gIfU`dAJxNn>0QDRA= z&22egX=Aijy>1AX&D02&-SheSagc)~j{y34a10M|E-v=P8r~VTQ1>TciEUdAMDA@< z<^`S|JPb`lSRywGjg}-&VVsK_y@g5~JUfWlF;SxQ^6N(O%%mE}*b`Vneabq^!z>Ht?35c{ckDvghMQl!7#rZy72ozDuz{=*zKPt~ zcfV{NpWbLa#106Cf$%7dwQ%zHTWf}ayRAK=7~nDM55vHuf{#8n%6}nvakj@GQHE_W z%7K^a$W)@dM(kSQAly5eJN+)7@`cYH+V-)Ni)FmmE`{mV5d*2Vxhov#eY>sdQH6X9 z(KDa^WfZIgl4+VjU48-gp1PdOdXbgTB1Q4vdlm^i3A4=jHf$P3MENlV@gKL5Gl~3_ zwk>G#82okg;_-vALZW9T{fh@!4FpO8$f7r8Gl;%Vtz*$*Aq+0~kN-@IcPBjP5FguJNMn;75@UjPAVN4qCx~{KXE`I>Fb5sLxn`V9|RXQFCUOpDhzgR@ff1#P_{V?>+*$8njF<-pgX=pRxLV z*9m7arg2o`R4^uJ%hVn6;ywBMMH&t!2g6;zu+I8UOdoW;6JuNAsLv36Q|ZNw@o8$X z9zxs8kTjkOpOvR;7~3;~PDbO3!1Qme#CNcZTtimdwy@|)O5WgCaiKSfsQ5NLgelC* zCn~%OyWk44`lv1|5DX@2m_$HTa$CsWG4;!B$A+;hd5^!sI~6C%tRS}IBr2Sn2RAe8 zV{GTyj=2}D-(fuFx-GHo5j%}f4X@`>K0U@Ye~-1Mh$TIV-d>!6*C&pWSv+>k2!&T( z2tTxiD3^)*S$TxKUGU?*3vfBJ?_GKv#;LXFGc8l_e%xM2A9U%-p?w`yMdFCOIOh&tuq=%;Ph_z_ zwfc3a;5-0~_eQp(UoG$Z?P+koGW(FIjpb-6d`O1Af`f%9mdu0l;_Y|%p(lwHrNN(F zq*Y{5U^iZc+(Pza+Qg{mS<2PhlAxVSuH2Ev)46+Lq~5~PpPn0%80pyx;%}M zy*AG5%kn>TmiG@vq&;ilQU^^I!Cf_L2BHH99B`69z=A+^~tYNC1Z zaMw!S7xu3Cv@hG$qY)L@?Pu3~Z=4N;!wxZcMo*JJqAeurNLM1E!6_i|ST3C5SmN{(R@0SUo&Oj$5AFgv#J(Vw+XCUx=4U#7YW?ww z552(knGwkd-<|T#Y0gvUHd)nIFtn_XQyJN*z4+XwTX3k(6qX%Rn_wj-_mlo()seKA zojN4G;MO};qFT-^xgpE34J7W0;tgGJ!;T2n^$!be869vfU+t3++Ae0u#Se!`l51>Da61K?-Yq>%#GXE)+oCh35M!eD|Z6519V0u!)kv(DZ{%4T)>)c;q z5+^*GV3jwz;NTuUwd$Ffm(zStg_#$G`F&Daa5<{~UNuHdbw+XqU<0J9q) z$VHfN6%7ac8REI0Kizxwl5Ed8>yf76b>C9SJkqc2g*~<#veL6EZet^OG=yy*CwAaSr%%+05-hw6-BMImS zheqKOSPFaM1mOT9yyLkihbhGJD-L+ddTV&Vfj9M79f#8r_j$tL&YKqNNY5yLbb5#zYw5uw7$j40#r zo_36}Al>RKlq*wt#U<#eZN{=V;YLQhJWO}0053KWV`RnASe0ZpM=%wMFt~7YdRP!b zQi?FYGrTv4C#fS3F)$f#)i4YzqySXcTW7(YBKwjKtkyF%p3Br;rW(KmzGm`6M1D?I z9E({L6W6uHH6rq|Q@ppCgb5?*fw%Rjb;M^@KPHvO7``V;Facp7Q}><@h;>x>drnb| z*S_onVi^_wmR0kjpANW2kD?(0qPcQrw_xrucs3Jfmdub9$3MlEGfQQfONHY}nJd!0 zT#vX!KrB}|j7;I)C7Lk5VN4rP$P`Nv21 z32EHZo{lf@FyZ?zz{8>&h@J~cT6}|& zI6!s`cf9w6nud#({+adG@Q98hC1ODw_aeKthp=t|9VPpy$|dI!y6$~$O;bIU|q7$R$V5{5|ha7=LpdU8+k_B;h* z!3Ke+c^})^fe)XUDh>kA@b*6iVi5>|=Xjqmz`?NKoqP0ejhhx-OQT?_w@B`9^A*~I zHr+J1VGC&Itv;JK3h)#7^iQm=`4RMqud9M?G8EoOFmAf8)XW96JEsRtyqJOqU6mSm zEJHXB9`dwI=~Xm%KwF6{to})y)E%CWfoCg#ND7Cr$UJP-yb~$#1u{)* z=m`INs7gKzRL0GXz2x@d7Pyk(J3t%XlJI*H@_f{4XcK_xJxw`_3gmUV*66Kn8`5IBF& zQJCl97{J|LlG_d+S}*REHH>1WMm>vGLYK;sNqSms@S!J_xc3k=qe3iE6SiC1*o^F2qK=N#l`zy~P7?#rrL_d3X!oKX1V zr3OmlmnaQjI6Gt=++>$=(puYW&$bv#(O1+(xVzp-Os#9pW6@#?)tA1cc#9*YZ!l>0 zNO;15Q3bUNRm^W%qhEMcmSflagVxqfcI`TzWcz9K95QtC!JrIAySn+tHyqk^R1ubh zLb_9@naXHaKPeo8YY1BMF6EW*vd(r-EV!DEx_qayHO-;Rb7Kq7Tv|Z+?x+h&V}`8d z8y7mZ)C5^rzcy#U-uOCv?YUcrr?2CM}~tGvI!}vNMCT_D#~M?(+oa8A>kJwuT>1lg0p=3*GT@ zb#?b53%p^3Yh&6)>)WSOrJ&}1Nw(ReTVyTQL#Y&Iw%o{qtor>yRe&}owor2EFtstK zIEc?M;1g$@QeCM*>*Pvu%i)V9ACKZoAA9WLrNB{hbIQ{ga7PGYvqK<1Y!9@-3j>4N5ko&l=whLVe6x$EF1Qas2leBXsjg_=Zya}3+_lxE^a0$Oj0r;d99 zzAa0A+NNkM|5o<|>3D&5pWE}Th}*PL`U)<9dNuV!MuA{I?BJ$lw(TAR)dr7Reg;E- z_8!s0+u_p9Fr8RFSUTaVv;Nz{#_e~m3*BQe{k7GNrZHkWj1>6x>cz1)KjA0>I)>vvIPk^T~jiftX%&rl| zH6=>tF=T#Ejm?M>)B>5gv9{SBcPb5&YZLI}#bEdP)1sE!anh|oN?Cm2TP_wwzNAFIkKrRam~I}){Y*WTzL8xM6os;sOd^wiFi`p6sC!OXU7w$-GLG| zQ30Rm!Vs53qP$^B4pJX(DHL2qN|xCkjrxWf`zy(Q~OI_`V$rEkqJEpy0ZPo+E<{BiaH>t@fS2o*o2PBRg8 z))Yx!y_88s0_PXc3?6s65oeyvu4_}cswVsI%@2Wdqay4d5`nJ&3QetLwn%M!)~luu zfsnPQ2K`)a@Xb?)u~w+gsyitRamU3Kj^OmmEC*R_7+9sMQ0GuTK--dp!Y-m;E!EoQ&PKi%U)=TifC?vJ& z->BlrCU4rXU1i@Q@EGC@kJqU`VaU00g=dIPxv-d>F8`ciRk{!D18o)7y(0qos={HJ z7^!t59we9{(DsYAaa^L!vsmVGMhIl_*Zp$vE~iLZhr6_OOM zF_IyL@U!7IMWffPaMgRzwn)dFM5M_rPut`HUbo^@ATae&s(6&7oDBY#gE@&mne1L# zVF+ThD8f};NvVEw>~gh%I^?Q}Gy8zfergTpnAX#oI~x7Fvc{VPIMvE zkEw_-wT?=?&rQ9&4l`2S+>ZR7tys}F~X%`$erT&eIPn8=|;K|xpTeQ1O7kr%+08ri|BEj z>!%Fw;MVclA-PSq3}Z#A!U)*PV56R=!@biJXSb}I&aPJUGndKj7g;wZ;O=4Pu?~KA~H>KFVb4yyM`XxoF zft1$3vB)hW(-Ua1E1`am>Ucw~pls}jO&eA739yN9H$Z$=9PL_NYuJt^W3<35c}V8D z)SndHgcpQApyy0apF)?hG%_Cpv?wey!FDBvm2q~)nS*XBdLf?|svlA|B5=8i2KA}2 zwvVY-ii{V)!oEzZT+8@oH}wY;q^^=SviMzzUKgzWmcm?oW>1r75fPbS4g=L{vu+=g z{dbX>MH45QM-Ya!=%U>hBYvkt9(@~BUPQhOT8VK9VIOl4pCto)6FzBd1MOck)#5Ifo+|4`lVh^TLtav! zW~zUWgAjD&L~Vj`KGht1)X4HQbuzQ-|+fezx0(=$DyaCy0&lVJ~uy zCl^Gqj*kf)`{m{(=<>s6agxFd(uUj4cEibu!W;lYJ``9=X~ z@+q;O3p@oo$a>7|lICQhw6e7qXjx&49+Cbb9eNd5K-Xvbo1lsaj4K%85Iu|Nd(Xiq z*_LU@I)rk2m1L@mX&W6nick;r!P?$bnBQPsds~8V6k#AOvkCZ+vSSvJr!WVwZFPzk zS7Ip9cz}Sf#<{zRuA0_P)FNaqMASnLEj!gXA!UzsHYep3@W=v5EoCkf_|Kj4eLG?Z!!DRSuS5T=MDFW z5yJm7M`bI#y?s79j5-x7G|!{Wu1-CuaRgj&?VpI*^5$``3M^$YLy55D9S)HO^5Kp2 zrr3fy7h-{2{7$#M+Xbd-N;)#}RW#%R)j;QXSL5;_52~*I`oQ;N)d z&e65OGor%74psiDHRv+ox7W&Z17}kV*Q8bR$+o4=9cqJG2*0D&jnIKC_QMXkw$Ov| z!_m-JWkC&uU!_&-Mod+R^HEx3D1V^4m5qCD#AHk&wj>&VhWpoKu`dj9a&^IEzgT^M zBb@WL$R&i7Qq-WaNOyCWgH zichcvE6hSdQl;~P=V7|P{rp;M}7dU)7C_Hd>%a zui&nQ1-~#;mw~A;>=P!Fyi)}a29=&^g$i$LeApM5q690olUK>|t`<<+kZ@$02#hHh zawP`Nif17xGP->6!D!3De)2Zva@(arYl!Txt>BD%hNzxh+VM^hzD1}P6 z6+p~Ta079>;Z)tR!UKz`Qlz&pI)ei*XpYU7H4sL5KZn?MMtlng%uSI_aw=wA?pAN3 z)|oN{t4vU8AtYl-ujxpTR_X65l($@&Du9OrnseqUwLZE0EKZ#~$8{(XmNY5Fb{sy) z*F0(t3eGNS<{ym97z^|XPg}3w{fKr$-`jJpn?uygtm7GmE!3Fynbbx{edW$-s$oBT zN74xekicYVApwuR-y4Yx3aABel^}Aly?k@WgYw@GAxKqB=iwaPZFhJYEo=i4gaL6% zP4~pjx?NZt`m>ss9JpF9d=QiInZNFWJ296ZmWcgO3!VGPcyWO%^@0Zp8Ap4`6VNXq zESBj9MH;yOFyWr=1T)?l-Wm*HZp3zenM1CJesN*&$Ut)r@(JHReEJBUgav8tj4q$u zRofl7RoQ!O8dd5_9F=ij!sWm{#+SFeS*e)`3YYmFeY?Gu%4{eP`nju46G%L#GVA;q;`Z*3ocEfe9!`bjwLTISOs7HYfEc1Vf~7Ru~3)^3ql^y3=Jge zXK%oFU7BAJp}=J0Q*0pu0UUDxUE*YeMG zkF@tYBYv0Z2j!x^L(O51%HNjQ@{wJqx)+C>?)K=HLxDb}TAYIYk|LxD&i$gxIn}k@ zeiu)7kIj?!j)B7~aXixxiZ{6l`k4=+JD&r#_=>{3r1knVBn4i_@F#lK(3{~o^x%$W zs_T-H>h>!NDnhxeu${d8JvE4ByswIbt`X`3RKy5$6SQvHez@_p$wx35eHXfyYDKs2 zowMDO`(+Jdh!D9L(j`IXtqAFRxhwTEK#SlBgx~=1mzV!e87{pnxOoc;Ii}@wB|O9u zj6Ur$))-5IigDs#UE4woC2=lzZN`ldM~6*D(RL{{OE4D6k@sRLwI$qk^3vZZ%?f!p z7oGsQ9`c(<`fL0#{?%u?z0uWbW&M{N#oF5Vp=0&`Xm0R3(0rh}PUAQBw#)HbpM19e z1S!1-b&$K$1Zl^i>RSqc%y`bnm2cBn<-b)cYzB^tJh3gKUCvTN4xgDsz zG@2^=8eDKnmFC}bDC4N~SLv7Gt%^?Ex4H59NwAkDK!75S9pYioTz|T`vARaHs4cFu za}u59c0^gk6wsl_tDZxZVM#Jt-eNgn4cM(CrTf0r+1~q5Al|i3deSH>43?}7RpZ9! zYi#!F`J{Os7CzzoZ5R3} zttln68tKhlrJvIGwtg%VM-0XoKh6HU*^FW^Q{EFPqqpfb`V>Z1rb&Vuh@p$<*{z!A{PtxPmqgR8!Lz3Ce_t<$sQ*{;$5iJub>>`}-LXP(jcv#pF_1 zS!qa-0fQiBislXEGMIp4beuYxP@Hjaa9|LXN`h|3OEI9LG8b;jNx3*MXh-Tu!|8Ws z5>sGIi+VB!}Da(+;FS1rebU$R0Oz6|deP~=r$H|NxP!iAzkWS&cOFf{}n|5|3 zE1KqM+9zAQY)?^7$jIV7sp-rCr1Y}+)f^PIH|pAPC%|FBy;8Xc6&I}?YQ4hWtIZd- zauu4U=5e~Y>h?^c^G+*_m3eN79{8}@OAys2Mz(Sm840V1m3zP*?lH!+jmK9SDa>R+(;>K3)2j> zjqnAv&B7bF5~VxXkT3V@Sf%QTHTN{t+PCGdF{P4YB@f2`7L*mbRF%Q?#&-ve(|ux= zG!xwv(^5vKG7oP-7pRF`Z%p@sKhWjuwio2?H9Ir9>85b%Dx)JWilI^NPWArF+{5UW znQxFcA@38Df>;;2N+si}V!NLnqm%qaz6Yhbm@hCYs39~Eq3cvTx$NquRcr}i>izuh zr);6l`!!L_w=!2Z)Q;($aZ)(IkuPPO>YOek{bPGY?lMy<^I%*HhY1xV(PnrV`uRp@ zQm(KGw&e@m=W5y?PK*qXqt#;po1ta%hkXbX48Q8A|yqL0<%+ z0e4?jeef?+&y>|C{hlC^(sxcCoG4Ny4@Mz2qsu%Xup|?<(f@^T_FQed&!H9JD_B>h zw(Odd!n1v8Qh%=RMdhn{0qyqPy8gGJROYTUrLs!1tGlL=up=Ye@HHRCtrewl#JP69 zpjaJ|A$GD&WgG#c4WHrmm~D_$ z2tY`)FTh83ejQ)P-Ky%^8M2`>2TSa5k7;LUyrMVqh@T%ZCB0cLNnEOm&3w6Yx-!{3 zB48;t%E9NO6hObv^P^LeKP)c$uqMYAd|(9^b`^t1=o{aq4_l+`oC>*WlH^j$MsQ zK$_NKb#FKohb<^{gW?W+dLNOMAjnXkMGCdMdqn0=0pYjl#~KD`bYZ>y{JQOmbFkKp zl}COZ(kIARGQ7fnv+rLf6?M0MxvGr1drj@tM>Y2X%V+YWPPFdcyAar5M~D1ewHU~bq3hqfb3?I zOZ|L2Tt#i#E(sYEG;Dw%gw&BK{N`N11%iH^jNI#c=9v{vht}vX(Kb&V0h;xf2K&9- z{pKyg8_vv6T zGrASwPcpiQUA z5}lXr0MXI^mDBjjy(G#pzO%}VS6FwWy(Yo__vnbyOQ^flb>?p~^4Y`|m|wpU`Z{chPu z{iYHQKwcnfAAzgsC*-l4qoma|Y%u3!r|q|LH>uqw^d`tWIz;+3syB}B6(k+)TFVLlvYEbZEU^9?>c{rFLb&m((kWnXt+SD4fa$ft+u(QJOQn+Vog^Bi7V(!50a;D z4_Osd@jBJltm3179tfp<+LB}&c@73h&HD{HE{%Wok91H9ycQAN`p&tGu31>doB%e@ z^IE?5G-efe^&`y|zqle70t?d5$ejI+IFV}MkOl`boa{xU8Fv*t=h<9F49Zn1LB2J31f3YPe1GfXD@B5Q&sf9#3Nlx zzKia85KQx?5hOE_n1X-~IWfh}v{WEc!*geSX- zIrB%A^N^*2{If)dXbz8C)m@>92oxEoqe(z?-P*7TeJ8tK`fh#CLzqjmX!;Wmsakx? zs`WR`5fEN)S)<1#1;+WePWHZlixIDqw}g6uZ*4UBywv$lOuT%@tc$h zwBSBnW5W%oLeJ)rl}mK7s;fk|tITgAp%0wR^E3>pba?3vNTI&_f05lUPVTm0lWH^V z1L8Hd9W537cGfzbv3I<61YBUqD2}5Tu_?%4d{&I$9M)NfN$1?Hc5_OJ8F0mr!HAl* zHg3Q#5upTuWeo?3cYuwuXvm0n1p8sgusL~@<1*`tqg~G&TGP3bh5{N}1Pw(npR1IZ zRRXo_L#&p4|1!_!+N!&rKD4qkipT&x{D;h>ATo*v*%sEPmzRlWNM};Z(2Tn@nKr3g z1tl>9F4VHPo`*^Qq{xZ?O-3Ccor1_<4GhNWC*elXHM?h-!il*kk+f;X+)Pqs>c%RKmDtBC^@|ypK&2)0%pQc3GqSqtQMK8Z6}C?G%XndfD?6uA zVTdBJNNj64l{^Q2lp+!m(A1fb*emz3%*A;w-uI@5)R^09+9-f_llw|%I57i`3C4_> z{M(3W80NqX0`f>_MMB#!a;0EjtgRMarTuAedH7VO0YWIS(k`2>3JPKeq`a{5VB)ayUJCc@JKQjngv`zP(k=?(nNTOv zKSyRRH4pG!RQu*vZ6SsnvkFHL(jFy$O;TCc0~Q2r7zlB~U>T!(?v3+}DZErI><`PU zwv*gtBx58rowoNv;jj0rCH=X}wDGIM{59tR^;ws?<@zURzolxyf~am*?P6q?IHx)L zA)%_hI9lJAWh*oQGN;}apD+%M_x4^{uM_Jn98SonzEyeIJR|@#{&%YOfYAE$qSZP- zH(2f*EquZn0~7S2uxUDJ>v#=wgc|&BzmxGr&PX#sJ;gs4krFa9Nk5akhO`i12j$R0 zSYW!rN!_UV6M+}es4Gj&VFH;ti`56C>Stst@N+XJ^!QVKgfUZ`kh0o5#EEpBAG{pJ zajQI6zPuzZjD1lh9QqmAXz)e6#PLN@gFDS&r;!A!6A5wB27wfmRz>6>ekddzU$Ctq zM>oW^X}X>y6D<9&4Xao)b^S$Cly91E7>K!G-&6uk1DoaL+`z&tb=H?)%F<53EsoU~ zEAeA(+$5c=l$x?!Xjwxr6VZjw8{Ed{9k?Av9Hv-DrOnjnQmzT}f+T&H);LVC8kfLI zxlag;Qxb8^o}tK+wJ|bZlA|OrV1X&aS-V1UsWY{UVrwaSrUufmPumbittVV(n-gnV zbdL{f8jK!8HlLgab%-IP7_RHxe1v$q_|LY;8nV}Em!$PWD$1?ny+H(LvMS?cPL15Z zw5_>>RMW7M+cbTj{8!x8UPSf1Gq zf6$#CUKZw3q|R!gexPgOd1AS3MGb*d#9xx>-bcH>KRo!&opi}2V1#q2D+I3Ia#>yN zH;ICfSy$d~27d;lS3eFY5*Iy?@ggg-#=BqNPS9@ibGZbWPv4u(_KpB5DjF?OW9PFyWF zrv*T}6V0lWc$piEZh%!Tkh-x(CUnl~GKT{4E}F1ao8eAs0^4TWDx1m&Q&e79s$@^o z^lI5*cz{1D4wr6WyhJFjoARz%_7F;IspoHz;!bn5!z=Nn@CPeS80WFpo#j0Rfz9@+ zAGS(g41pAa9GiuPLJB^5!iYM8kYYqsj zvGtRGL{PKO-o#3SWTMh&8PVuLHQ9~WY!|g`D(P8Rw6YoS%(alCh1#}Jnon%bj6~~T zlVk5E!N66NHPw=z&KM(6QBR>&oj+iBovW7xU16W3XcGpS{GoF-o~{=eMjW;4?)>viY8nff|?S0 zFZH4W8!q*tYQ5||;t7=E7AVb5J=y`Uss!0&$Q0R3>P8d4jk=M3qG27-D2|j}Qkp|z z^$O$;i)A<|^#l6AXVed!S@i=_`CBI0#DUP(*{a1eCm4^u7ccYMYRH=fxkCOmV&6J# zR31ghQ-mGi$Si7w1_OnW8E_pcJ>%a?;ogMnryr2_lbX*ZZd4TA)(ibflcI@7jz{FR zH%#YS#4z4-YfYcK;h76Sf?~xFT1wPm0UB!pQ#Nz7uu-X;-z; zjofm?LbzlZ3y;fRAk={Dqn2cTJ3EzdBTE{Q2zZKE*c&&npIA84bfM)Ozs+e#C-cNJ zoQgKd9wruk#$O{XFdgPFvGCjxKTopWJ2vSvm??ehMAKzrArPtq#KLKO39ERwJcP_o zq!GW^g2o7HO6}WkVC)j%2B3;*AEzF}AQZ!1%vG3&TxGg2R|x0E4=`!OzS0Ehu{WY~ zh}8?Ut$7sXedA}?lvWPiGa+n$wQ+BHP!Y)YTzLpuwV_;4qXH`X;@%#K2K)>5TfdVM!O=UGOLgp zh;d>Kgn%Wq_YaCfoL`9dPk!N3W?L3v{8GgBNPrx20g+z z2Yg8$B=L)g)1;@ddd+iKy|nW!*EG5sX7NLXG!xPPPwOKS;ADJR}`Q zENFxeEmbY0e!x*hu5rR6$nwS2B8_73iYq?RG(gfyE_J2iC^mno%H@7>a=z1193q~G zEpM3brcy5wWFJy5_M1{2tqcu8ll;%NE-Y?{o9yTWIENS!?=%%BBl;yr&qYII6JDvze= zj7xWxw*bWAjx@R7MISC|`$;Lh;KBR-7g>ref{|I)DExoNas!z10nGvE@;9loui4aW zodYx5O<0#n`!-094P_D${d|_S(B(IFUx$FG{enLO8xKBXsiqmAl!g7IYJ+~zQgwHn;5ma zDAha~HSF9U6JtI%-8fI8tx}%np!=)6kkKKMWSkC=H@AI#kvpBV!1WXiegqhi^)Y%v z5s0*D9g68(MFX-u&dY;jF9oKuo@B4@{www5vZj`)X$5==>&XXvF2Sa`r}%Fi1>uzNaCxTG3$$fk)&^6+g&Wr#(_KbQEYj67 zJqva?O`E{z@DSRA(?5`}axiIqw1ls8hA(EIapwU}&5)H%zaZJn&q`1Td*P3Wc^O1)G;b1OWd~^90K_NHBE{k>3ARQoPu77}Q|NVw zmT@{bOBfxLH#j}kIOsb0Mxt9Hs!LEw5&`qjkW4f?e;L8gcee7+I;ftqsVS>()M^Jcl$nhe(5jI-6#o?WADXr$|v+dpy-2!1B6xXkzh zA@>eaoi&9>5%YAPw9+_X@h8)^i}DMA01$}Ij>S5ku2OXe)b)_NJI6+n@9q^YDn4oqtw8%2|F*<+GcOJtsOJ6w zixB|Qe@qwxb_**IFybJO@BWsYJ^_5jeexvL{#bKQ1R@gpcMD%2gRbIa?|cV2VhV{I z?q5beS6WJ?gFHo(BP)q>+Xu&Oe!MA}-2MWD#5aBhSJ8$IiXFt2TgQ8u+YJt@tu|}> zbiO9NMA#qL*ign+TH-Q=q`yu$Pp55Xk{s~zPlzmA3qS3AKEtv^8XXd=$~pw$#73y% zc+=UPYgGr$rXDhmFW?YMKbBOvrakLL+s;0v@bO}uF@n%w*7Stf?#@N!#!Ij8wM<$A z3#X=I_Jo(&S7%21K1fFOmKYfY%eZm#wAQo|kz@-(8VN+GDQmb)GixPZTe3m%;-Vv- zlO(!+uP++%98KS#C?aQ9t*)I3M$C(!5Pz{2p6y$F|8+NMoP z9499)8Do0L5=D3aXTsnzb8|3BW_X82ns0SX+VObk$1k_kk7|12YjuZLU+le<@BCO) zdh8dG-UH?cuKbI^}yOy7!5ugsIZ6N^doS7@`H`|5>ZQJUs?a1VIQ(+n9E(ianA+IJ?{ zlIo9ox-5#7P3YA$3M6c@>beEnVtXWMwt?3Ju;>>`d+Mu=7oXun+qEV0WTqCSgfGM? zz_o@eSe9JZ5s|1zvcM+$-^3Lq&l0f+kwQ72SJ?HC>Mi7DeYfcg4&0 z!6Mwpnx2Bh${kUvvM4_70ZiUSC{<@&K>XVU#&WBs+Y_M= z-#%*#-b_9HlXR4N`e%M;_3@I0>#*e4v5>w4j-C#ft!{T>dVa=$iiw5KtvcGavIJ|JFv`peb)BIi5ypTU!pB?IsChx=dlo!W7%*XuGO5*SZ9s#iaeYvH4xFJKjJR!C{E8cc( z=MvusSgG*xAkLgq4x6l#?>rHfWIlYwrB^2tw3}ybKuhDz89gr26=Ge((gN7ibRY^x zH@ausTDQIKv!LhQ7m2J+CAy%~$K*fhv^~cC{1HQfG~XMBD8l=kxx$#{=Q>Kyr9Tw4 z<)O@Vdaq2d7k#R(C<$Mn&2u6=Gm&3}OvAohda+&AGV=P~c_Gh~8-{oVYiwNmu0Pps z?Of_Rqho33oQsIoWR1OHwobV-cTT}J>n-qA3pF`b8XKWsaWhKErRugoXBAu z4jz76F}{b+A8^Wkv(>9`&h!FtyTWCg$T|VS{dI6vl+a${Wp2q0H!FVdkdl~I!V#JbtgQh`ykZ7s_931a#Y^IpPif@ZtHUDrQ)E6 zjTI;0qg!J5?YHqED=um2@D>4Xr4t!aZH2H=)+UJBbELL=p(}jnhrH7HtRH|wR-n{m zongVJrkr~Syo7!2iN!DI#SGy zt-9{#4Vk=t0@lVPsupegHunkBqj_a!-;K%PIcx;0sA42 za&27xl)?nrHq0gf7@pjrsNpKX#c1nemU{WRz7pYrpI29wES{@#c8k)5oq^n8ooG9e zeU-zdxQL#Yxqzdm(Se^0@9>6}S$s+5v63LRa5J>sA(7=>iw#@(xL;>Y&IY zp1#FLd#~K$@3M6mSZ!B1(_OjeaLenta-;hu-lw9fWbskquJnBkl3H!gWX9cl{D5bA zmAr?m)(udVyNw076ZMo($*43vlNi;*e6SZB&E4Zv=MM_qem?&@&z;}ESABD=WDaZC zR!w(=;Wi)ce6Ff%<~dlCV|C`K^Cn6(jXS_Www4lOhh3ctW34mc6l=05q+5vT$rdlE z_x1VG%T?Nv*{rxuEz4 zuWA|M+xjYv=Xw5a&=KLM=_eb~L^17jkdp)S(EJ|6TaFTkbb5iL!xee zj%|O21Y6eRLEHmukno)?P|Er?swVw=x3B3@DkyCI@LOyX=I%r{Ym{lW2rfco^`d^@ zxXO{4w~l*0k7BNM@m4nP09JM>1QTb=S)#m-%Rx{{(t$$h=BW)mw6*j=EZRWsJ4-dCz_7tPy5-Y_}4b*oCWIa zqLyvQGjP=uw&yqAym@@{CHU+Q=Dx-Io{!*ti0I#W ziOHPzVZWA$!h62XT-|5(w_Fifm2h#)%_rX|p-QSGbNV39yE^QC?J2B#eSSo3}vfY02TA=K_oD z!BaR~{u?|y2{9)3Nkozf;hQ6+Pjhum_Ol+G`KJuFrSEsf6x|L}gCs9}6-+QkIaMTH- zU{U;aYH!vag?Z->*wS}tGPnhr{Z58KzWW(YISXm@y%95k z3`PN)7fTNyC&=`_?A@<%cQ(}V-A{4K!=|(_?T+;s zb&IHZzyc*5zJ*5CP^mP5B<72{RTCo1M{r3D?5p0qT2r#~ofzu_z&GB%Y~FBvmCCte zS@PqYQ8ao7cg&s49&Py+H{P&z;L6rt`3h>sEp5Pq$lZ{W!67WO+4=fY(zC3dVe{c1 z!lfxx&koVS$;ulz3d3Dg$B0YnR zb!L-!gEH2%%vs!MKZ_g+L;Tstb|I{$rbvD;y;!($Y=1Cl`169kdV4TPdidZu=gNBB z|44#M!VSOR*6lvbG-nPW3TaW9%Q~mCiV~$wnVVFzX#7?uM{p^k(JeSl*kqqFsfgU# zU~OT1&;E8(J+XF$*=2&I-hS4JQ?{F|E9%O7U0FR9!cDtQ8mFM1$f|%xxdC(YG@PT4 zZJv8Q4bFWULFkD;kSqgd_r)BXrtIdsk%{F_O!K_!o1{o)4w4(@SvSVEihb*6J^(?P zhWs^9*bhd_IGQFQDXQm4YNknNh(n}L3;H4j+@bhk{$nTPc)81Q z#k%aA=i%>X!xzdIpMt=pvALwaG`VKC{IWCmPj&ZEfb=gieiRBN+eDIg(NN2J{=8FL zMb~YP`x`&-R6h=U-74#PqPX1nFbT(vDwn$B4Ru_QRu;x_sp{?%Z3t&)Wt0fFj+b$o z|J|wWc-Jo+S8yV`^c}eOSa=tI2hnafDk!@9dpwGdVx zHAFmF`jMcjqU$;esm!i^2VQ1YNQQamcp)ca4$iW?&v#DY3=2)xgev4Ds!7&Ig=|uV zQX$xM4RQQS&PglvwF0j0fPMHMgE;g{f~4PkI2DqDT5>XO=4=SNWGcjLO}vO`WfE7# z)kdEocnz>2i(i%23zjEXaZ6!UWp?}XCBjb(J8XVOafWf&wBZM^vp887hHPE`xnK{w%6srP#Zek`Bs%mp5_D^}qEAhi_d1XDLjO9p`i&o2uI zGfd=@JvhWT%K~sT@O~Xw3PMKq}zIF#WItEwk!@S|KD@3w1@>%3)4muf1coot_;li?3zyi}qWeMCT z%=bYiDT3~M)d!ss_LmHjvnK27z+aqqo$T^0IBI;f757I|MQ*%{pk|qVDYAZiWY@Pr z9b@`YsygGVxA|?iq0(61ldt4<9qUp-LcDM9Y=auk!Gd$Wp|1Vv6mFA-_GVdTJI!TA z>4Q*;uWjh?WmBktv^I?4M9>KHaK&ZDS;Uqd(F+~`TN|fxTw!!`EsRBf z(v-b%cF-8pQ$WXw!!!pc)tr&Lp|v0J19!B1Jq)}7msD-W*E{(N>a$p4TQB%7&KiNN zB1?yRQ`Us1k+v#cEe$|$*Fq>}0-qx(^AX{oLJARyw!0hXk_VX!1@P+iXlgG4l*Sq9 zOD@P?^DD<4iEh3CC~Rit8vQ(PIGBxaL{dxKbO${G&&pc`9PaKZA|S5sGr1`6jIfl% z2~;SYIA8Fvc&%+ag#0v<^>^TguU28dIYJ>UsE|Fp%Y>v@eIwEgP3C1ouv?t`bg~JTqkBGGt_mZTcpOQ2 z+3>8Zl|B)Y+{?RV7~OoAmy5h*g0sczdJD zUHpjcB_}d~ks0K)J$$iN9$-+|vMo5-@;~Z~R+={g^ZrTRvHH)E>(JHKnEirn5n#Vg zgUxjKM69F;IxLr`Jvcq^fPAiiGpyzZhM(d?xwGcRTsZAkK^zcYT}2C?7JJau!OB@? z>pfg`o!iapA>isyz=cg^%#2>}%#nEOBgmq%VYsW>)a^T1+2x*u0A4Kix=N!<`lGh< zP7m8~<{No@tRuDN?%8B9B*Bpfr+Ph6JZJkKCu6)78F>;{4v#EeHLL>gr7{5AJRy>N zIK*$`X2vZD04h{Pg_uX33lnlSSvKmu>JYF?E18-aP>~`woZ_L_B2mJqm(PijD#Ka1 ze>x74&VtAQyeUXZ(toK;MI;>>Yh5fG4$oa+3$WOEk01z)Ya)sBp-BB#&|9)VFqkm9 zxMS6@Z|=@JlPZO+1!cy^po()(#w-XDFSb#>%52@}+->nWi!Ju7J>&4?5=5$Lj-WGw--DAB(fDRG~L~jBz+OUtsBgVOH*+G5h;2? z@<$&IEU#_+M$Q)mVRge2aE2+dcOpQ0Pv|0-a;6-*v_Z8cS$c66!~>O`9;^4+yh94n zpxih5aJnLDzs>5Znfa@U*cp?%)Xn2a4Gt@FHZl| z<`<>q`1rX&ygnt{bp0ctl@>fK3_<2f{rt_Y(jvURJ=iR`aaC8|_rB1Ygou6i^%?ow z=Mk-lUSIri)V1KvqAc!QnQ;*%j{YaPYHdb4LUDeiwta3| z^>_|rBukT((au{R8GNC1QcebH8?by03-7iIdcuB>;Mz@hd*Gx0 zJxMi9nQUJ5&~ijBVwwevhFTUyb-P&(H#jFnsp3;&dnQ<>+TPiV_!6vs&k>p7wz1(M zL%7t9M93moV3ugx$9oYHjn@Hq{hYCBCcGM+>ClegH<}LJ`_c>}JYiTV2S@#CO8M>B zP_jsP{0Q)lTN}S1IpkGj{o%yfBUyk*f8$2tWw{9Sy9A4edz}B#JqzbAFyf06x;-uH zr2cg{!gmh2Q6se}=gA(xD?owxEJ3#WS8Xl(%;RXv#Xb%4J>#9i*2!(xWVJvDup0Np zF4&R@9}wPIn(W)T0aK?1;fKEi`-9Fq^Mz}%G>5U|nr0j3NEg@PWWc#9<7B9^%ckUL zhwzHKChDGI`KZA^3Gw@6by=S0_fC=0w1i;%TmV1N9{2%pYhPi* zf;I%N5nLLmlAp?Oq=aPEk*$KXE=-qLU77NG5<e(KyR&B2Hpj@+}? z61qJsu~HeiY`S85T*NDFze^x?AOTpB+mOQ)4j0zS!NrS9=4sh|lhkkW1+Xhmh%!ii9%cKM58zl96(m6-~ZSo&0cQ=rS2xh%UPOd)jK3pVhkfRA zd?a_nlJHb2GTip8pfGyieZ&y4&q7uX=LKP(rXnT$=S~DXBYNi>QqZPqbM{mnDS@>G z9LQdUR8K>HN8Ai6(?dP-_A};g2ozXPic}>K+|(&j<6WSDT)^>>Z@-TnrljxK34+~1 zmUaf=&`dGxJc0Omo7dG+J`L}<;_{}Yh}LR4h0L|^oI?8Gx2q!J^aXl|u=@WzJRVU) z0M#i3y^}3yeGbMo*2T^&pI5HG&-b$DD52-!c)Fo9S_YFg^i?>ag3R3y(;M%X6%~8tsk0`Js$q&q_raId2tblv;Yy{a zt|P4Id^19j;V^{Dugcu*Pp?8^6d&$^*q=PAZG`RV130{&^b`8IX}vxq!FnBHg&c?Y z%x`5l)G2UXV*7Sl6;f{8)ATySL-IE`90{DIciZCpXI;lhtn={Rl8XODGLn8#V9U4W655ZyiquP5Wa%+$K{5K>Rn zo#=G|+Vi*F@{6L_cGi-?YJ1|uAkB~m&;-PiAArO8E_{^qnc;U_PuYfJP>a?N>kdnd!f`{I4MvDR^;EO#)$e^C68 z^z!w1si)pH%A9s- zdp%pV{a?VXQmbRP=yHv^qV0w>TZ-~oek2_j4Y^v;Z~ClegW`3j@NC0L3K(Xu#)S8S zlf-;wktu&6xw}Z{y^YT>!JE)I`mFD?{g5m9hRCG}*|`nNLbSe}&2T?lAIKCj@{Gb5 zuGE$cq2si44GV1FdLYx&0)&Jd42e?J!Zo`oCY;UwbniXx+5y*FIN%3U=nR~OsE-7V z;TW#zBRdwRZN%GvL&)=o*VK;rNZ8Jd?OO=0rq9UVrtM*$-Q=CU2{yJ&lvanVQ+!$!pUT!lM@T)p(! z%mjbngXS=oLIgm=kb)zOyl1p7EU)o-GisXU)i4gT-)<6S{G1FI7yBs2|1?vg3ln4o zIQWVWq8uyfScF(Gq#KwF&(*9CgtDKzCi1igae->wTHsUG^(=XzVBj3hDpNBS8I*k4 zL#z@wp1y@$14k5rT=*Fx`BcX%2xMe7qE|n&b)KUxiZ?XzDkq6{XgTl%K-L6((non} zuXuot3Uby&;T6D)@mJ2cvw=V^a%&>7(d|m36W-ECQ*~Z(grU$0GFU8k6C}BSmFUex zbL-RM&*k}%$f-zZ!C9d@VKn&RrtGzBn1BU82jc?%qVbR3HRV5T39|JnA<*F){}4C^ zd4K3DCtJ+q%uV`W@dT+C*(|UYTL4hdtB?(f51qPVtsaCw6JP+l7``vo;ki1m^~gc0 z5R|DKzk+A}cOVu7vEYYH2zd{Zvr_!H)PEL|y2C_mK{M%HaYe)ma*H}GM;4RYro%Vk zTtz#HQOfD&)~DD?0Afx!6|9y#0$lJ*KW6uKxtCyd@kX%hn4vM=fwgh|WlETCu%ENO zuH(181Fyq)OWxyCA0!)rbXqe1VNF?pT~Lw8&#;^fAZ`xwly~sE$T0ciXJoS!RP{cU zs_Xw6P73l1(PwitUNJRD7?6&Wq+}12=z`9VvnvKy<{`>69~%#Wb)+$q?TxPh1f)Jl z0+DkZ)4ww&L$Cy)*G#6U1>C*}F3`)BMEpM#j>>iaBd<1(1 z{gqx3(1EY$)so8IN7@jLn1Q@iF zdBeX5^DTuNa;(#|o=71(6yNz4a%~-MH(wcVAYIZzj{P>c#t@g&2Bn3-1uF2UJ;a5K z4J5h$22ny#b^OQMM>n>l@Bu9t(jXj4Ay|356jo~#2ZL3KH6ixP0Y zCKwry1*ByX-iuH;dgUFbsCz8}C*B%W%T-zAQzEk-zrY&Y_UXg>INu3X35N(&B@_fd zak@>gABLOzE{#k;Y9#rGux{y{tH{5m4jH8k2jw#n&sG>K*-vN$e(BR}kM6&XD?MoA zHu+k}+XD=MUd_a|;SI>aa#p^8J`za^#y@(_wq!pLmI;JY+$-x9wBkGg1j)(`B?FooVAgB`!5B!%TQxE_?3O^r?n8f+mX6tachJsIs^Pmu-$ zfhwc;>~!qhWNPD;a-}IhfXpjW1z@Gm!9F{iAO^e>KW00%pQ^TMWUgd`!ly7^-DRh?x8gTn8&QzFBzz75X1SHT=eN3`ug2O@Q)iES z@tFKcwj0Bad>Nh$h(ETUjmG%@$CH2RmXr=VH?(2?WS(GkQBXy0)_}6PXTs7?_Sh+Z6n8i z`Z;+!&K;FJhmDeAZ|L2vi@8%mUOBZ1S6_WVdn*A4_*nuA|7`Fp{Qil+&lQeX=1yZ=mMkM=^O z0X;(i2rHC(&mKvE)dXnWrz_Wb^~280Cm0K2nMnq2ZH)g-G9&A8RR_IIL(3GfUa*ttWa6Rp$lfS z?@vI2Ym-%vvWGK7{{JBGk9IEvM=*s0in(*>_+|(B&e~aX$R6kkL$3`0FYO2H>-onb z-o1cBLP5wkc?$sx?BR4>ktGn>Dky3hTY#fTY;3`8$P`MOS~fqQ6&ZNg-Zus2oRPLO z$aePi>LU?5E_l#oO8Fq_=4=ks19OOEy{$ZtpGb@mUZ=(LUP*x$%4$LjH@h-=HL#5jgE+g!jx*8&$ZgwD%Oqnq3n`R2% zgB|NX4-*w%?pxovnBp&BHLw+-Tw-?HhTy3@f>0MppL8^c2oXKT9>SC8c$lR43I}46 z^C*ga{9&EaerO5;DqRUKJ;birVj9Mh*@Z}AL^0-Y1F*KUiB1;tF2s<)ieMX;VVTM8 zEea4G#K8p^0*FL`)N$7oeP0vb`6$I%-(XiEVY>X?l>Zh{jR#*lD78gb3Flw|;W!XS z58Y(0~`~#hd zqo=UYJ7{7tycKvjTNO|nzWp43JYzt71 zs=a=qPzywN&W7kAJcQ#$WBtDgg(DiVoWZ0MvxnK5q8=Y)f2IIN(xKVIW>aeh$xpcjW+<}nIh2nBaifM(9R8^}EidgWln1lwYyM`B8hOelLn7dz zJu*`xW52S}wD?YSbk5SmWPMDgNbeZYj)dV)M&oS*h&v72yUv89=J+XpGcO*>S)b$I zu}mL77^i=fl^U{kxPMe<-)IQnE0OYnZb(A+zny?o17E}-)rMnNy;fK{QnN2+$=h zWfJ9o|MmYW^S^7Lzy8yf|8Cj;{g;_v!@A8yo-CB6pFq$s`+cZM^-a=@ae#ZBPeh(_ zhev?p>V@C8jB|^NtSa1r?7T}IpVbryyISVr0vz^3c8g56QuTmivcPxc?02rL%b`wk z^UDh}?~gB6b6j|{Wgf2|`|vb>$gNt%MPIz$p(S!1ulnADb;-7Mi79p5Q$n5KP*8%F%tJFYM;KeG2klVd;TLSFTqP@iviz>Az^ z1|Rs5Uv-tj8SfmhJ47dE`HmNVHH!~(EC_C7 zn)2>yMYDs_FjE@&`Z`;uLlAH|G5IgwiS3^6c!^N;=~to@x-n8OUj3DwpY2#|TuQXt z^6%ohpBN+ECw$>_iQTcw(q+`AK9ZWwI#gh|?+Oe5&Ced=Tfq9(D4~rm`=Xz->Cunt z`K+-qb=BrQo?()WwRClK{>qD5>ljZDsXad}i0x+JDBa4t^af`onQ=e?tjfo7dBhvT9yD18P$4kQ&{;RJ?&B0@B z+omm4-P1LVb=bm52hTOyhaE2wStR&(6;Ia}(a$r!^`BuHhf@mDJy%#zQkA{Q@y@cG zkCbce6ib1h@^fwKFDv=+**`URaa{9w_5S?34Q2N6milS)_yAruHZ+;9_=-plI?6O` z3`!spC=G(@3dg!F*-^xtg4kep=B&PNI zdFF113BidcNzW-n!<}P=9*B4Al$g86SS+3`wJ45{1dP4Bs?D>e*~TU+7xMnkV+~7x tq-AD@c>$ Date: Fri, 1 Mar 2024 16:07:29 +0100 Subject: [PATCH 12/45] updated description to better reflect sensor's functionality (#57) --- .../Resources/Localization/Languages.Designer.cs | 2 +- .../Resources/Localization/Languages.de.resx | 10 +++++----- .../Resources/Localization/Languages.en.resx | 10 +++++----- .../Resources/Localization/Languages.es.resx | 10 +++++----- .../Resources/Localization/Languages.fr.resx | 10 +++++----- .../Resources/Localization/Languages.nl.resx | 10 +++++----- .../Resources/Localization/Languages.pl.resx | 10 +++++----- .../Resources/Localization/Languages.pt-br.resx | 10 +++++----- .../HASS.Agent/Resources/Localization/Languages.resx | 2 +- .../Resources/Localization/Languages.ru.resx | 10 +++++----- .../Resources/Localization/Languages.sl.resx | 10 +++++----- .../Resources/Localization/Languages.tr.resx | 8 +++++--- 12 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index b9cc36b9..eb72a3ef 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -6034,7 +6034,7 @@ internal static string SensorsManager_MicrophoneActiveSensorDescription { } /// - /// Looks up a localized string similar to Provides the name of the process that's currently using the microphone. + /// Looks up a localized string similar to Provides the number of the processes that currently use the microphone - additionally provides name of the processes in the sensor's attributes. /// ///Note: if used in the satellite service, it won't detect userspace applications.. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index e7b8bf5b..65f05b22 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3252,11 +3252,6 @@ Zeigt nur Geräte an, die seit dem letzten Bericht gesehen wurden, d. h. Wenn de Stelle sicher, dass die Ortungsdienste von Windows aktiviert sind! Je nach Windows-Version finden Sie diese in der neuen Systemsteuerung -> „Datenschutz und Sicherheit“ -> „Standort“. - - - Stellt den Namen des Prozesses bereit, der das Mikrofon derzeit verwendet. - -Hinweis: Bei Verwendung im Satellitenservice werden keine Userspace-Anwendungen erkannt. Zeigt die letzte Änderung des Monitor-Energiezustands: @@ -3494,4 +3489,9 @@ Als Payload benötigen Sie den Namen des Audiogeräts. Verwenden Sie ein modernes Taskleistensymbol + + Gibt die Anzahl der Prozesse an, die derzeit das Mikrofon verwenden. Zusätzlich werden die Namen der Prozesse in den Attributen des Sensors angegeben. + +Hinweis: Bei Verwendung im Satellitendienst werden keine Userspace-Anwendungen erkannt. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 859e0535..35b55d94 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3130,11 +3130,6 @@ Only shows devices that were seen since the last report, ie. when the sensor pub Make sure Windows' location services are enabled! Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. - - - Provides the name of the process that's currently using the microphone. - -Note: if used in the satellite service, it won't detect userspace applications. Provides the last monitor power state change: @@ -3371,4 +3366,9 @@ Requires audio device name as a payload. Use modern tray icon + + Provides the number of the processes that currently use the microphone - additionally provides name of the processes in the sensor's attributes. + +Note: if used in the satellite service, it won't detect userspace applications. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 1c58dc96..b94b7fbd 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3129,11 +3129,6 @@ Sólo muestra los dispositivos que fueron vistos desde el último informe, es de Asegúrese de que los servicios de localización de Windows están activados. Dependiendo de su versión de Windows, esto se puede encontrar en el nuevo panel de control -> 'privacidad y seguridad' -> 'ubicación'. - - - Proporciona el nombre del proceso que está usando actualmente el micrófono. - -Nota: si se usa en el servicio de satélite, no detectará las aplicaciones del espacio de usuario. Proporciona el último cambio de estado de energía del monitor: @@ -3370,4 +3365,9 @@ Necesita el nombre del dispositivo de audio como carga útil. Utilice el icono de bandeja moderno + + Proporciona el número de procesos que utilizan actualmente el micrófono; además, proporciona el nombre de los procesos en los atributos del sensor. + +Nota: si se usa en el servicio satelital, no detectará aplicaciones del espacio de usuario. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 9570fa4f..6974fc0d 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3162,11 +3162,6 @@ Affiche uniquement les appareils qui ont été vus depuis le dernier rapport, c' Assurez-vous que les services de localisation de Windows sont activés ! Selon votre version de Windows, cela peut être trouvé dans le nouveau panneau de configuration -> 'confidentialité et sécurité' -> 'emplacement'. - - - Provides the name of the process that's currently using the microphone. - -Note: if used in the satellite service, it won't detect userspace applications. Provides the last monitor power state change: @@ -3403,4 +3398,9 @@ Vous avez besoin du nom du périphérique audio comme charge utile. Utiliser l'icône de la barre d'état moderne + + Fournit le nombre de processus qui utilisent actuellement le microphone - fournit en outre le nom des processus dans les attributs du capteur. + +Remarque : s'il est utilisé dans le service satellite, il ne détectera pas les applications de l'espace utilisateur. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 9a8ec312..b3718a8f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3151,11 +3151,6 @@ Toont alleen apparaten die zijn gezien sinds het laatste rapport, oftewel, zodra Verzeker dat Windows' localisatieservices ingeschakeld zijn! Afhankelijk van je Windows versie, kan dit gevonden worden in het nieuwe configuratiescherm -> 'privacy en beveiliging' -> 'locatie'. - - - Geeft de naam van het proces dat momenteel de microfoon gebruikt. - -Notitie: als hij in de satellietservice gebruikt wordt, zal hij geen gebruikerapplicaties detecteren. Geeft de laatste beeldscherm energiemodus status verandering: @@ -3391,4 +3386,9 @@ U hebt de naam van het audioapparaat nodig als payload. Gebruik het moderne ladepictogram + + Geeft het aantal processen weer die momenteel de microfoon gebruiken - geeft bovendien de naam van de processen in de attributen van de sensor. + +Opmerking: indien gebruikt in de satellietdienst, zal het geen gebruikersruimtetoepassingen detecteren. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 5e45586f..c0aa8938 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3239,11 +3239,6 @@ Pokazuje tylko te urządzenia które zgłaszały się w okresie od ostatniego ra Upewnij się że lokalizacja jest włączona w systemie Windows! W zalezności od Twojej wersji Windows'a opcje te możesz znaleźć w Ustawienia -> Prywatność i Zabezpieczenia -> Lokalizacja - - - Zwraca nazwę procesu który obecnie używa mikrofonu. - -Wskazówka: jeżeli korzystasz z usługi Satellite, nie będziesz miał informacji o aplikacji w przestrzeni użytkownika. Zwraca ostatnią zmianę stanu ekranu: @@ -3480,4 +3475,9 @@ Wymaga nazwy urządzenia audio jako ładunku. Użyj nowoczesnej ikony w zasobniku + + Podaje liczbę procesów aktualnie korzystających z mikrofonu - dodatkowo podaje nazwę procesów w atrybutach czujnika. + +Uwaga: jeśli jest używany w usłudze satelitarnej, nie wykryje aplikacji przestrzeni użytkownika. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index c12f692d..82742fe5 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -2250,11 +2250,6 @@ HassAgentStarted, Logoff, SystemShutdown, Resume, Suspend, ConsoleConnect, Conso Fornece um valor bool com base em se o microfone está sendo usado no momento. Fuzzy - - Fornece o nome do processo que está usando o microfone no momento. - -Observação: se usado no serviço satélite, ele não detectará aplicativos de espaço de usuário. - Fornece a última alteração do estado de energia do monitor: @@ -3416,4 +3411,9 @@ Você precisa do nome do dispositivo de áudio como carga útil. Use o ícone moderno da bandeja + + Fornece o número de processos que utilizam atualmente o microfone - fornece adicionalmente o nome dos processos nos atributos do sensor. + +Nota: se usado no serviço de satélite, não detectará aplicações no espaço do usuário. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 5f839459..a3d68008 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -2951,7 +2951,7 @@ Hidden, Maximized, Minimized, Normal and Unknown. Note: if used in the satellite service, it won't detect userspace applications. - Provides the name of the process that's currently using the microphone. + Provides the number of the processes that currently use the microphone - additionally provides name of the processes in the sensor's attributes. Note: if used in the satellite service, it won't detect userspace applications. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index fd37d175..730a6e59 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3197,11 +3197,6 @@ Home Assistant. Убедитесь, что службы определения местоположения Windows включены! В зависимости от вашей версии Windows, это можно найти в новой панели управления -> 'конфиденциальность и безопасность' -> 'местоположение'. - - - Указывает имя процесса, который в данный момент использует микрофон. - -Примечание: если он используется в спутниковой службе, он не будет обнаруживать приложения пользовательского пространства. Обеспечивает последнее изменение состояния питания монитора: @@ -3439,4 +3434,9 @@ Home Assistant. Использовать современный значок в трее + + Предоставляет количество процессов, которые в данный момент используют микрофон. Дополнительно указывается имя процесса в атрибутах датчика. + +Примечание. При использовании в спутниковой службе он не обнаружит приложения пользовательского пространства. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 3a216e73..1b8cf9ba 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -2328,11 +2328,6 @@ HassAgentStarted, Odjava, SystemShutdown, Resume, Suspend, ConsoleConnect, Conso Zagotavlja bool vrednost glede na to, ali se mikrofon trenutno uporablja. Fuzzy - - Vrne ime procesa, ki trenutno uporablja mikrofon. - -Opomba: če se uporablja v satelitski storitvi - Vrne zadnjo spremembo načina monitorja: @@ -3519,4 +3514,9 @@ Ime zvočne naprave potrebujete kot obremenitev. Uporabi sodobno ikono pladnja + + Zagotavlja število procesov, ki trenutno uporabljajo mikrofon - dodatno podaja ime procesov v atributih senzorja. + +Opomba: če se uporablja v satelitski storitvi, ne bo zaznal aplikacij uporabniškega prostora. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index 5796ca75..52515f4c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2764,9 +2764,6 @@ Daha fazla tuşa ve/veya CTRL gibi değiştiricilere ihtiyacınız varsa, Multip Geçerli enlem, boylam ve yüksekliğinizi virgülle ayrılmış bir değer olarak döndürür. Windows'un konum hizmetlerinin etkinleştirildiğinden emin olun! Windows sürümünüze bağlı olarak bu, yeni kontrol panelinde -> 'gizlilik ve güvenlik' -> 'konum'da bulunabilir. - - Şu anda mikrofonu kullanan işlemin adını sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - Son monitör güç durumu değişikliğini sağlar: Soluk, Güç Kapalı, Güç Açık ve Bilinmiyor. @@ -2982,4 +2979,9 @@ Yük olarak ses cihazı adına ihtiyacınız vardır. Modern tepsi simgesini kullan + + Halihazırda mikrofonu kullanan işlemlerin sayısını sağlar; ayrıca sensörün özniteliklerinde süreçlerin adını da sağlar. + +Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılamaz. + \ No newline at end of file From 3478aa0d4f4d9ada0cf53e0023889c4a054ab4e6 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:02:02 +0100 Subject: [PATCH 13/45] dependency bump (#56) --- .../HASS.Agent.Satellite.Service.csproj | 16 ++++++++-------- .../HASS.Agent.Shared/HASS.Agent.Shared.csproj | 10 +++++----- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 18 +++++++++--------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index 72f396fe..e9e2d70f 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -29,22 +29,22 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + - + diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index 504a0bcb..aa05de02 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -30,19 +30,19 @@ - + - + - + - + - + diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index bbaf5a51..d5bd7361 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -49,27 +49,27 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + WV2 - - - + + + - + - + - + From 2067440601ed1209803ba6749e5680d2a0b95de9 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:18:52 +0100 Subject: [PATCH 14/45] added note regarding HA 255 payload character limit (#58) --- .../HASS.Agent/Resources/Localization/Languages.Designer.cs | 1 + .../HASS.Agent/Resources/Localization/Languages.de.resx | 1 + .../HASS.Agent/Resources/Localization/Languages.en.resx | 1 + .../HASS.Agent/Resources/Localization/Languages.es.resx | 1 + .../HASS.Agent/Resources/Localization/Languages.fr.resx | 5 +++-- .../HASS.Agent/Resources/Localization/Languages.nl.resx | 5 +++-- .../HASS.Agent/Resources/Localization/Languages.pl.resx | 5 +++-- .../HASS.Agent/Resources/Localization/Languages.pt-br.resx | 1 + .../HASS.Agent/Resources/Localization/Languages.resx | 1 + .../HASS.Agent/Resources/Localization/Languages.ru.resx | 3 ++- .../HASS.Agent/Resources/Localization/Languages.sl.resx | 5 +++-- .../HASS.Agent/Resources/Localization/Languages.tr.resx | 5 ++++- 12 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index eb72a3ef..ffd2a2ed 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -6094,6 +6094,7 @@ internal static string SensorsManager_PerformanceCounterSensorDescription { /// /// Looks up a localized string similar to Returns the result of the provided Powershell command or script. + ///Note: please keep in mind that Home Assistant accepts payload up to 255 characters. /// ///Converts the outcome to text.. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index 65f05b22..ef8af3e2 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3260,6 +3260,7 @@ Gedimmt, PowerOff, PowerOn und Unbekannt. Gibt das Ergebnis des bereitgestellten Powershell-Befehls oder -Skripts zurück. +Hinweis: Bitte beachten Sie, dass Home Assistant Nutzdaten von bis zu 255 Zeichen akzeptiert. Konvertiert das Ergebnis in Text. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 35b55d94..2d827e9e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3138,6 +3138,7 @@ Dimmed, PowerOff, PowerOn and Unkown. Returns the result of the provided Powershell command or script. +Note: please keep in mind that Home Assistant accepts payload up to 255 characters. Converts the outcome to text. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index b94b7fbd..88070530 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3137,6 +3137,7 @@ Atenuado, Apagado, Encendido y Desconocido. Devuelve el resultado del comando o script de Powershell proporcionado. +Nota: tenga en cuenta que Home Assistant acepta una carga útil de hasta 255 caracteres. Convierte el resultado en texto. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 6974fc0d..f8295066 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3169,9 +3169,10 @@ Selon votre version de Windows, cela peut être trouvé dans le nouveau panneau Dimmed, PowerOff, PowerOn and Unkown. - Returns the result of the provided Powershell command or script. + Renvoie le résultat de la commande ou du script Powershell fourni. +Remarque : veuillez garder à l'esprit que Home Assistant accepte des charges utiles allant jusqu'à 255 caractères. -Converts the outcome to text. +Convertit le résultat en texte. Fournit des informations sur toutes les imprimantes installées et leurs files d'attente. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index b3718a8f..f810730d 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3158,9 +3158,10 @@ Afhankelijk van je Windows versie, kan dit gevonden worden in het nieuwe configu Gedimmed, Uitgeschakeld, Ingeschakeld en Onbekend - Geeft het resultaat van het opgegeven Powershell commando of script. + Retourneert het resultaat van de opgegeven Powershell-opdracht of -script. +Opmerking: houd er rekening mee dat Home Assistant een payload van maximaal 255 tekens accepteert. -Converteert de uitkomst naar text. +Converteert de uitkomst naar tekst. Geeft informatie over alle geïnstalleerde printers en hun wachtrijen. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index c0aa8938..8adea682 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3246,9 +3246,10 @@ W zalezności od Twojej wersji Windows'a opcje te możesz znaleźć w Ustawienia Przyciemnienie, Wyłączenie, Włączenie, Nieznany - Zwraca wynik poprzedniej komendy Powershell lub skryptu. + Zwraca wynik podanego polecenia lub skryptu programu PowerShell. +Uwaga: pamiętaj, że Home Assistant akceptuje ładunek o długości do 255 znaków. -Konwertuje wynik do tekstu. +Konwertuje wynik na tekst. Zwraca informacje na temat wszystkich zainstalowanych drukarek i ich kolejek. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 82742fe5..92c42b50 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -2276,6 +2276,7 @@ Você pode explorar os contadores através da ferramenta 'perfmon.exe' do Window Retorna o resultado do comando ou script do Powershell fornecido. +Observação: lembre-se de que o Home Assistant aceita carga útil de até 255 caracteres. Converte o resultado em texto. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index a3d68008..69340051 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -2925,6 +2925,7 @@ Dimmed, PowerOff, PowerOn and Unkown. Returns the result of the provided Powershell command or script. +Note: please keep in mind that Home Assistant accepts payload up to 255 characters. Converts the outcome to text. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 730a6e59..5ecfc113 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3204,7 +3204,8 @@ Home Assistant. Затемненный, Выключенный, Включенный и Неизвестный. - Возвращает результат предоставленной команды Powershell или сценария. + Возвращает результат предоставленной команды или сценария Powershell. +Примечание. Имейте в виду, что Home Assistant принимает полезную нагрузку длиной до 255 символов. Преобразует результат в текст. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 1b8cf9ba..b50418be 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -2353,9 +2353,10 @@ Primer: _Skupaj Številke lahko raziščete z orodjem Windows 'perfmon.exe'. - Vrne rezultat Powershell ukaza ali skripta. + Vrne rezultat podanega ukaza ali skripta Powershell. +Opomba: ne pozabite, da Home Assistant sprejme vsebino do 255 znakov. -Pretvori rezultat v tekst. +Pretvori rezultat v besedilo. Vrne informacijo o nameščenih tiskalnikih in čakalni vrsti za njih. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index 52515f4c..140a75ac 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2768,7 +2768,10 @@ Daha fazla tuşa ve/veya CTRL gibi değiştiricilere ihtiyacınız varsa, Multip Son monitör güç durumu değişikliğini sağlar: Soluk, Güç Kapalı, Güç Açık ve Bilinmiyor. - Sağlanan Powershell komutunun veya komut dosyasının sonucunu döndürür. Sonucu metne dönüştürür. + Sağlanan Powershell komutunun veya betiğinin sonucunu döndürür. +Not: Home Assistant'ın 255 karaktere kadar yükü kabul ettiğini lütfen unutmayın. + +Sonucu metne dönüştürür. Yüklü tüm yazıcılar ve sıraları hakkında bilgi sağlar. From 1f2a40900d2a6767a47c12463006a143725f7285 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:05:25 +0200 Subject: [PATCH 15/45] Feature: NFC tag scanning (#69) --- .../ConfigGeneral - Copy.de.resx | 14 ++ .../ConfigGeneral - Copy.en.resx | 14 ++ .../ConfigGeneral - Copy.es.resx | 14 ++ .../ConfigGeneral - Copy.fr.resx | 14 ++ .../ConfigGeneral - Copy.nl.resx | 14 ++ .../ConfigGeneral - Copy.pl.resx | 14 ++ .../ConfigGeneral - Copy.pt-br.resx | 14 ++ .../ConfigGeneral - Copy.ru.resx | 14 ++ .../ConfigGeneral - Copy.sl.resx | 14 ++ .../ConfigGeneral - Copy.tr.resx | 14 ++ .../Configuration/ConfigNFC.Designer.cs | 144 ++++++++++++++++++ .../Controls/Configuration/ConfigNFC.cs | 29 ++++ .../Controls/Configuration/ConfigNFC.resx | 124 +++++++++++++++ .../Forms/Configuration.Designer.cs | 20 +++ .../HASS.Agent/Forms/Configuration.cs | 10 ++ .../HASS.Agent/Forms/Configuration.resx | 62 +++++++- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 34 +++++ .../HASS.Agent/Managers/RadioManager.cs | 116 +++++++++++++- .../HASS.Agent/Models/Config/AppSettings.cs | 4 +- .../Localization/Languages.Designer.cs | 9 ++ .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 3 + .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3 + .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + .../HASS.Agent/Sensors/SensorsManager.cs | 20 +++ 32 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs create mode 100644 src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs new file mode 100644 index 00000000..c353bf48 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs @@ -0,0 +1,144 @@ +using HASS.Agent.Resources.Localization; + +namespace HASS.Agent.Controls.Configuration +{ + partial class ConfigNFC + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + var resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigNFC)); + LblInfo1 = new Label(); + LblSelectedScanner = new Label(); + CbNfcScanner = new ComboBox(); + PbLine1 = new PictureBox(); + CbEnableNfc = new CheckBox(); + ((System.ComponentModel.ISupportInitialize)PbLine1).BeginInit(); + SuspendLayout(); + // + // LblInfo1 + // + LblInfo1.AccessibleDescription = "NFC scanning ability description"; + LblInfo1.AccessibleName = "NFC information"; + LblInfo1.AccessibleRole = AccessibleRole.StaticText; + LblInfo1.AutoEllipsis = true; + LblInfo1.Font = new Font("Segoe UI", 10F); + LblInfo1.Location = new Point(70, 36); + LblInfo1.Name = "LblInfo1"; + LblInfo1.Size = new Size(583, 74); + LblInfo1.TabIndex = 57; + LblInfo1.Text = resources.GetString("LblInfo1.Text"); + // + // LblSelectedScanner + // + LblSelectedScanner.AccessibleDescription = "NFC Scanner"; + LblSelectedScanner.AccessibleName = "NFC Scanner"; + LblSelectedScanner.AccessibleRole = AccessibleRole.StaticText; + LblSelectedScanner.AutoSize = true; + LblSelectedScanner.Font = new Font("Segoe UI", 10F); + LblSelectedScanner.Location = new Point(70, 188); + LblSelectedScanner.Name = "LblSelectedScanner"; + LblSelectedScanner.Size = new Size(87, 19); + LblSelectedScanner.TabIndex = 65; + LblSelectedScanner.Text = "NFC &Scanner"; + // + // CbNfcScanner + // + CbNfcScanner.AccessibleDescription = "Selected NFC scanner"; + CbNfcScanner.AccessibleName = "Selected NFC scanner"; + CbNfcScanner.AccessibleRole = AccessibleRole.DropList; + CbNfcScanner.BackColor = Color.FromArgb(63, 63, 70); + CbNfcScanner.DrawMode = DrawMode.OwnerDrawFixed; + CbNfcScanner.DropDownHeight = 300; + CbNfcScanner.DropDownStyle = ComboBoxStyle.DropDownList; + CbNfcScanner.Font = new Font("Segoe UI", 9.75F); + CbNfcScanner.ForeColor = Color.FromArgb(241, 241, 241); + CbNfcScanner.FormattingEnabled = true; + CbNfcScanner.IntegralHeight = false; + CbNfcScanner.Location = new Point(73, 210); + CbNfcScanner.Name = "CbNfcScanner"; + CbNfcScanner.Size = new Size(358, 26); + CbNfcScanner.Sorted = true; + CbNfcScanner.TabIndex = 0; + // + // PbLine1 + // + PbLine1.AccessibleDescription = "Seperator line."; + PbLine1.AccessibleName = "Seperator"; + PbLine1.AccessibleRole = AccessibleRole.Graphic; + PbLine1.Image = Properties.Resources.line; + PbLine1.Location = new Point(73, 130); + PbLine1.Name = "PbLine1"; + PbLine1.Size = new Size(576, 1); + PbLine1.SizeMode = PictureBoxSizeMode.AutoSize; + PbLine1.TabIndex = 67; + PbLine1.TabStop = false; + // + // CbEnableNfc + // + CbEnableNfc.AccessibleDescription = "Enable NFC tag scanning"; + CbEnableNfc.AccessibleName = "Enable NFC"; + CbEnableNfc.AccessibleRole = AccessibleRole.CheckButton; + CbEnableNfc.AutoSize = true; + CbEnableNfc.Font = new Font("Segoe UI", 10F); + CbEnableNfc.Location = new Point(73, 150); + CbEnableNfc.Name = "CbEnableNfc"; + CbEnableNfc.Size = new Size(180, 23); + CbEnableNfc.TabIndex = 72; + CbEnableNfc.Text = "Enable &NFC tag scanning"; + CbEnableNfc.UseVisualStyleBackColor = true; + // + // ConfigNFC + // + AccessibleDescription = "Panel containing general configuration options."; + AccessibleName = "General configuration"; + AccessibleRole = AccessibleRole.Pane; + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + BackColor = Color.FromArgb(45, 45, 48); + Controls.Add(CbEnableNfc); + Controls.Add(PbLine1); + Controls.Add(CbNfcScanner); + Controls.Add(LblSelectedScanner); + Controls.Add(LblInfo1); + ForeColor = Color.FromArgb(241, 241, 241); + Margin = new Padding(4); + Name = "ConfigNFC"; + Size = new Size(700, 275); + Load += ConfigNFC_Load; + ((System.ComponentModel.ISupportInitialize)PbLine1).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private System.Windows.Forms.Label LblInfo1; + private Label LblSelectedScanner; + internal ComboBox CbNfcScanner; + private PictureBox PbLine1; + internal CheckBox CbEnableNfc; + } +} diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs new file mode 100644 index 00000000..f3ffd70d --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs @@ -0,0 +1,29 @@ +using System.Globalization; +using HASS.Agent.Functions; +using HASS.Agent.Managers; +using HASS.Agent.Resources.Localization; +using Syncfusion.Windows.Forms; + +namespace HASS.Agent.Controls.Configuration; + +public partial class ConfigNFC : UserControl +{ + public ConfigNFC() + { + InitializeComponent(); + + BindComboBoxTheme(); + } + + private void BindComboBoxTheme() => CbNfcScanner.DrawItem += ComboBoxTheme.DrawItem; + + private void ConfigNFC_Load(object sender, EventArgs e) + { + CbNfcScanner.Items.Add(Languages.SensorsMod_None); + + foreach (var nfcReaderName in RadioManager.AvailableNFCReaderNames) + CbNfcScanner.Items.Add(nfcReaderName); + + CbNfcScanner.SelectedItem = string.IsNullOrWhiteSpace(Variables.AppSettings.NfcSelectedScanner) ? Languages.SensorsMod_None : Variables.AppSettings.NfcSelectedScanner; + } +} diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx new file mode 100644 index 00000000..6b83a3b0 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + If your device is equipped with NFC scanner, you can enable the scanning functionality and select one of the available scanners. +This functionality works like the Home Assistant Compaion App NFC tag scanning. + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs index 0b47fbcc..150e1c3a 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs @@ -46,6 +46,7 @@ private void InitializeComponent() this.TabStartup = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); this.TabTrayIcon = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); this.TabUpdates = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); + this.TabNFC = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); this.BtnAbout = new Syncfusion.WinForms.Controls.SfButton(); this.BtnHelp = new Syncfusion.WinForms.Controls.SfButton(); this.BtnStore = new Syncfusion.WinForms.Controls.SfButton(); @@ -80,6 +81,7 @@ private void InitializeComponent() this.ConfigTabs.Controls.Add(this.TabStartup); this.ConfigTabs.Controls.Add(this.TabTrayIcon); this.ConfigTabs.Controls.Add(this.TabUpdates); + this.ConfigTabs.Controls.Add(this.TabNFC); this.ConfigTabs.Dock = System.Windows.Forms.DockStyle.Fill; this.ConfigTabs.FixedSingleBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); this.ConfigTabs.FocusOnTabClick = false; @@ -355,6 +357,23 @@ private void InitializeComponent() this.TabUpdates.Text = global::HASS.Agent.Resources.Localization.Languages.Configuration_TabUpdates; this.TabUpdates.ThemesEnabled = false; // + // TabNFC + // + this.TabNFC.AccessibleDescription = "Contains the \'NFC\' controls."; + this.TabNFC.AccessibleName = "NFC"; + this.TabNFC.AccessibleRole = System.Windows.Forms.AccessibleRole.PageTab; + this.TabNFC.Image = null; + this.TabNFC.ImageSize = new System.Drawing.Size(16, 16); + this.TabNFC.Location = new System.Drawing.Point(142, 2); + this.TabNFC.Name = "TabNFC"; + this.TabNFC.ShowCloseButton = true; + this.TabNFC.Size = new System.Drawing.Size(740, 544); + this.TabNFC.TabBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); + this.TabNFC.TabForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); + this.TabNFC.TabIndex = 15; + this.TabNFC.Text = Languages.Configuration_NFC; + this.TabNFC.ThemesEnabled = false; + // // BtnAbout // this.BtnAbout.AccessibleDescription = "Opens the about window."; @@ -523,6 +542,7 @@ private void InitializeComponent() private Syncfusion.Windows.Forms.Tools.TabPageAdv TablLocalApi; private Syncfusion.Windows.Forms.Tools.TabPageAdv TabMediaPlayer; private Syncfusion.Windows.Forms.Tools.TabPageAdv TabTrayIcon; + private Syncfusion.Windows.Forms.Tools.TabPageAdv TabNFC; } } diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs index 6c25679f..2f83e22a 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs @@ -36,6 +36,7 @@ public partial class Configuration : MetroForm private readonly ConfigLocalApi _localApi = new(); private readonly ConfigMediaPlayer _mediaPlayer = new(); private readonly ConfigTrayIcon _trayIcon = new(); + private readonly ConfigNFC _nfc = new(); private bool _initializing = true; @@ -67,6 +68,8 @@ private void Configuration_Load(object sender, EventArgs e) TablLocalApi.Controls.Add(_localApi); TabMediaPlayer.Controls.Add(_mediaPlayer); TabTrayIcon.Controls.Add(_trayIcon); + TabNFC.Controls.Add(_nfc); + // bind events BindEvents(); @@ -103,6 +106,7 @@ private void Configuration_FormClosing(object sender, FormClosingEventArgs e) _localApi.Dispose(); _mediaPlayer.Dispose(); _trayIcon.Dispose(); + _nfc.Dispose(); } private void BindEvents() @@ -361,6 +365,8 @@ private void LoadSettings() _trayIcon.CbWebViewKeepLoaded.CheckState = Variables.AppSettings.TrayIconWebViewBackgroundLoading ? CheckState.Checked : CheckState.Unchecked; _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState = Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick ? CheckState.Checked : CheckState.Unchecked; + _nfc.CbEnableNfc.CheckState = Variables.AppSettings.NfcScanningEnabled ? CheckState.Checked : CheckState.Unchecked; + // done _initializing = false; } @@ -468,6 +474,10 @@ private async Task StoreSettingsAsync() Variables.AppSettings.TrayIconWebViewBackgroundLoading = _trayIcon.CbWebViewKeepLoaded.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick = _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState == CheckState.Checked; + // nfc + Variables.AppSettings.NfcScanningEnabled = _nfc.CbEnableNfc.CheckState == CheckState.Checked; + Variables.AppSettings.NfcSelectedScanner = _nfc.CbNfcScanner.SelectedItem == null ? string.Empty : _nfc.CbNfcScanner.SelectedItem.ToString(); + // save to file SettingsManager.StoreAppSettings(); } diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx b/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx index d1f11844..9cfd6d5f 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx @@ -1,4 +1,64 @@ - + + + diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index d5bd7361..fb9c0c56 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -63,6 +63,7 @@ + @@ -91,6 +92,9 @@ + + UserControl + UserControl @@ -169,6 +173,36 @@ ConfigExternalTools.resx + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + ConfigGeneral.resx diff --git a/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs b/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs index 035aa8c9..c4792838 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs @@ -1,20 +1,52 @@ -using Serilog; +using MQTTnet; +using NdefLibrary.Ndef; +using Newtonsoft.Json; +using Serilog; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; using System.Text; using System.Threading.Tasks; +using Windows.Devices.Enumeration; using Windows.Devices.Radios; +using Windows.Devices.SmartCards; +using Windows.Networking.Proximity; namespace HASS.Agent.Managers { internal static class RadioManager { public static List AvailableRadio { get; private set; } = new(); - public static List AvailableRadioNames { get => AvailableRadio.Select(r => r.Name).ToList(); } + public static List AvailableRadioNames => AvailableRadio.Select(r => r.Name).ToList(); + + public static Dictionary AvailableNFCReader { get; private set; } = new(); + public static List AvailableNFCReaderNames => AvailableNFCReader.Keys.ToList(); + + private static long s_subscriptionId = -1; + private static ProximityDevice s_selectedNFCReader = null; + public static ProximityDevice SelectedNFCReader + { + get => s_selectedNFCReader; + set + { + if (s_selectedNFCReader == value) + return; + + if (s_selectedNFCReader != null && s_subscriptionId != -1) + { + s_selectedNFCReader.StopSubscribingForMessage(s_subscriptionId); + } + + s_selectedNFCReader = value; + s_subscriptionId = s_selectedNFCReader.SubscribeForMessage("NDEF", MessageReceivedHandler); + } + } public static async Task Initialize() { + Log.Debug("[RADIOMGR] Initialization started"); + var accessStatus = await Radio.RequestAccessAsync(); if (accessStatus == RadioAccessStatus.Allowed) { @@ -23,12 +55,88 @@ public static async Task Initialize() AvailableRadio.Add(radio); } - Log.Information("[RADIOMGR] Ready"); + Log.Information("[RADIOMGR] Radio management permission granted"); } else { - Log.Fatal("[RADIOMGR] No permission granted for Bluetooth radio management"); + Log.Fatal("[RADIOMGR] No permission granted for radio management - privacy settings may be restricting the access"); + } + + Log.Debug("[RADIOMGR] Enumerating proximity/NFC devices"); + try + { + var proximityDevices = await DeviceInformation.FindAllAsync(ProximityDevice.GetDeviceSelector()); + foreach (var device in proximityDevices) + { + var proximityReader = ProximityDevice.FromId(device.Id); + AvailableNFCReader.Add(device.Name, proximityReader); + } + } + catch + { + Log.Fatal("[RADIOMGR] Error initializing proximity/NFC devices"); + } + + if (!string.IsNullOrEmpty(Variables.AppSettings.NfcSelectedScanner)) + { + if (AvailableNFCReader.TryGetValue(Variables.AppSettings.NfcSelectedScanner, out var selectedScanner)) + { + Log.Debug("[RADIOMGR] Selected NFC reader: '{nfcScanner}'", Variables.AppSettings.NfcSelectedScanner); + SelectedNFCReader = selectedScanner; + } + else + { + Log.Warning("[RADIOMGR] Selected NFC reader: '{nfcScanner}' not available", Variables.AppSettings.NfcSelectedScanner); + } } + + Log.Information("[RADIOMGR] Ready"); + } + + private static void MessageReceivedHandler(ProximityDevice sender, ProximityMessage message) + { + if(!Variables.AppSettings.NfcScanningEnabled) + return; + + try + { + var rawMsg = message.Data.ToArray(); + var ndefMessage = NdefMessage.FromByteArray(rawMsg); + + foreach (var record in ndefMessage) + { + Log.Debug("Record type: " + Encoding.UTF8.GetString(record.Type, 0, record.Type.Length)); + if (record.CheckSpecializedType(false) == typeof(NdefUriRecord)) + { + var uriRecord = new NdefUriRecord(record); + Log.Debug($"URI: {uriRecord.Uri}"); + + if (uriRecord.Uri.StartsWith("https://www.home-assistant.io/tag/")) + { + var tagId = uriRecord.Uri.Split('/').LastOrDefault(); + if(string.IsNullOrWhiteSpace(tagId)) + return; + + var tagScannedMessage = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/devices/{Variables.DeviceConfig.Name}/tag_scanned") + .WithPayload(JsonConvert.SerializeObject(new + { + Time = DateTime.UtcNow.ToString("s"), + UID = tagId + })) + .Build(); + + Variables.MqttManager.PublishAsync(tagScannedMessage); + } + } + } + + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } } } diff --git a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs index ee404285..a7f705c7 100644 --- a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs +++ b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs @@ -79,7 +79,9 @@ public AppSettings() public bool MqttUseRetainFlag { get; set; } = true; public string MqttRootCertificate { get; set; } = string.Empty; public string MqttClientCertificate { get; set; } = string.Empty; - public bool MqttIgnoreGracePeriod { get; set; } = false; + + public bool NfcScanningEnabled { get; set; } = false; + public string NfcSelectedScanner { get; set; } = string.Empty; } } diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index ffd2a2ed..6aa0e753 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -3257,6 +3257,15 @@ internal static string Configuration_MessageBox_RestartManually { } } + /// + /// Looks up a localized string similar to NFC. + /// + internal static string Configuration_NFC { + get { + return ResourceManager.GetString("Configuration_NFC", resourceCulture); + } + } + /// /// Looks up a localized string similar to You've changed your device's name. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index ef8af3e2..667b8615 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3495,4 +3495,7 @@ Als Payload benötigen Sie den Namen des Audiogeräts. Hinweis: Bei Verwendung im Satellitendienst werden keine Userspace-Anwendungen erkannt. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 2d827e9e..9ad44e64 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3372,4 +3372,7 @@ Requires audio device name as a payload. Note: if used in the satellite service, it won't detect userspace applications. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 88070530..f4714e88 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3371,4 +3371,7 @@ Necesita el nombre del dispositivo de audio como carga útil. Nota: si se usa en el servicio satelital, no detectará aplicaciones del espacio de usuario. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index f8295066..a3c2f2f1 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3404,4 +3404,7 @@ Vous avez besoin du nom du périphérique audio comme charge utile. Remarque : s'il est utilisé dans le service satellite, il ne détectera pas les applications de l'espace utilisateur. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index f810730d..e87c832b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3392,4 +3392,7 @@ U hebt de naam van het audioapparaat nodig als payload. Opmerking: indien gebruikt in de satellietdienst, zal het geen gebruikersruimtetoepassingen detecteren. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 8adea682..a209df91 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3481,4 +3481,7 @@ Wymaga nazwy urządzenia audio jako ładunku. Uwaga: jeśli jest używany w usłudze satelitarnej, nie wykryje aplikacji przestrzeni użytkownika. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 92c42b50..04f553a7 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3417,4 +3417,7 @@ Você precisa do nome do dispositivo de áudio como carga útil. Nota: se usado no serviço de satélite, não detectará aplicações no espaço do usuário. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 69340051..a91866c5 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3384,4 +3384,7 @@ Requires audio device name as a payload. Use modern tray icon + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 5ecfc113..2e7a6713 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3440,4 +3440,7 @@ Home Assistant. Примечание. При использовании в спутниковой службе он не обнаружит приложения пользовательского пространства. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index b50418be..57350977 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3520,4 +3520,7 @@ Ime zvočne naprave potrebujete kot obremenitev. Opomba: če se uporablja v satelitski storitvi, ne bo zaznal aplikacij uporabniškega prostora. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index 140a75ac..21bd0270 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2987,4 +2987,7 @@ Yük olarak ses cihazı adına ihtiyacınız vardır. Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılamaz. + + NFC + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs index f8cc5b7a..1d8f2848 100644 --- a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs @@ -1,4 +1,5 @@ using HASS.Agent.Extensions; +using HASS.Agent.Managers; using HASS.Agent.Models.Internal; using HASS.Agent.Resources.Localization; using HASS.Agent.Settings; @@ -6,7 +7,10 @@ using HASS.Agent.Shared.Extensions; using HASS.Agent.Shared.Models.Config; using HASS.Agent.Shared.Models.HomeAssistant; +using MQTTnet; +using Newtonsoft.Json; using Serilog; +using static HASS.Agent.Shared.Functions.Inputs; namespace HASS.Agent.Sensors { @@ -123,6 +127,22 @@ private static async void Process() } } + //if (RadioManager.AvailableNFCReader.Any()) + if (true) + { + var tagScannerConfigMesage = new MqttApplicationMessageBuilder() + .WithTopic($"{Variables.AppSettings.MqttDiscoveryPrefix}/tag/{Variables.DeviceConfig.Name}/config") + .WithPayload(JsonConvert.SerializeObject(new + { + topic = $"hass.agent/devices/{Variables.DeviceConfig.Name}/tag_scanned", + value_template = "{{ value_json.UID }}" + })) + .WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag) + .Build(); + + await Variables.MqttManager.PublishAsync(tagScannerConfigMesage); + } + _discoveryPublished = true; } From 5e480f4f9570431f9cd50bc0d154725eb584d53f Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:10:39 +0200 Subject: [PATCH 16/45] Fix: close all subwindows when main one is closed (#70) --- src/HASS.Agent/HASS.Agent/Forms/Main.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.cs b/src/HASS.Agent/HASS.Agent/Forms/Main.cs index 7e971535..583c9197 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.cs @@ -344,7 +344,19 @@ private void Main_FormClosing(object sender, FormClosingEventArgs e) if (_isClosing) return; - Invoke(new MethodInvoker(Hide)); + Invoke(() => + { + HelperFunctions.GetForm("QuickActions")?.Close(); + HelperFunctions.GetForm("Configuration")?.Close(); + HelperFunctions.GetForm("QuickActionsConfig")?.Close(); + HelperFunctions.GetForm("SensorsConfig")?.Close(); + HelperFunctions.GetForm("ServiceConfig")?.Close(); + HelperFunctions.GetForm("CommandsConfig")?.Close(); + HelperFunctions.GetForm("Help")?.Close(); + HelperFunctions.GetForm("Donate")?.Close(); + + new MethodInvoker(Hide).Invoke(); + }); if (!Variables.ShuttingDown) { From 9e106747d4dee729707800079b1d0e7b3b5f1c21 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:29:36 +0200 Subject: [PATCH 17/45] Fix: device rename MQTT messages cleanup (#71) --- src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs index 3e99155b..8507183f 100644 --- a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs +++ b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs @@ -562,11 +562,38 @@ public async Task ClearDeviceConfigAsync() { if (string.IsNullOrEmpty(Variables.AppSettings.MqttDiscoveryPrefix)) Variables.AppSettings.MqttDiscoveryPrefix = "homeassistant"; - var messageBuilder = new MqttApplicationMessageBuilder() .WithTopic($"{Variables.AppSettings.MqttDiscoveryPrefix}/sensor/{Variables.DeviceConfig.Name}/availability") .WithPayload(Array.Empty()) - .WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag); + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/devices/{Variables.DeviceConfig.Name}") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/media_player/{Variables.DeviceConfig.Name}") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/media_player/{Variables.DeviceConfig.Name}/thumbnail") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/media_player/{Variables.DeviceConfig.Name}/state") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); } From 571c8fab980940190053a920e8acadd26374fadf Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Wed, 1 May 2024 22:58:48 +0200 Subject: [PATCH 18/45] Fix: added additional check for mixed german keyboard layouts (#74) This PR adds additional check for the german input language being installed on system. Should remediate cases where mixed language systems (English UI with German keyboard layout) do not present the notification regarding HotKey collision - #62 --- .../HASS.Agent/Functions/HelperFunctions.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs index b1f7bc85..877b4e57 100644 --- a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs +++ b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs @@ -546,13 +546,24 @@ internal static bool InputLanguageCheckDiffers(out bool knownToCollide, out stri var inputLanguage = InputLanguage.CurrentInputLanguage.Handle; // check for known OK languages - if (KnownOkInputLanguage.ContainsKey(inputLanguage)) return false; - + if (KnownOkInputLanguage.ContainsKey(inputLanguage)) + return false; + // check for known NOT OK languages - if (KnownNotOkInputLanguage.ContainsKey(inputLanguage)) + var germanLayoutDetected = false; + foreach(InputLanguage language in InputLanguage.InstalledInputLanguages) + { + if(language.Culture.TwoLetterISOLanguageName == "de") + { + germanLayoutDetected = true; + break; + } + } + + if (KnownNotOkInputLanguage.ContainsKey(inputLanguage) || germanLayoutDetected) { // get human-readable name - var langName = KnownNotOkInputLanguage[inputLanguage]; + var langName = germanLayoutDetected ? "German" : KnownNotOkInputLanguage[inputLanguage]; message = string.Format(Languages.HelperFunctions_InputLanguageCheckDiffers_ErrorMsg1, langName); return true; From b2fb3c9a582b5fda9c4ce7ab0f80ed44f0cca968 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 2 May 2024 11:16:54 +0200 Subject: [PATCH 19/45] Fix: attributes are now cleared if no process uses the microphone (#76) This PR makes the microphone process sensors attributes to be cleared when no process is using the microphone. --- .../SingleValue/MicrophoneProcessSensor.cs | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs index e2cbf944..7c92e3e3 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs @@ -13,21 +13,16 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; public class MicrophoneProcessSensor : AbstractSingleValueSensor { private const string DefaultName = "microphoneprocess"; - - private const string LastUsedTimeStop = "LastUsedTimeStop"; - public MicrophoneProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool useAttributes = true) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, useAttributes) - { - // - } + + private const string _lastUsedTimeStop = "LastUsedTimeStop"; + private const string _regKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"; + + public MicrophoneProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, true) { } private readonly Dictionary _processes = new(); private string _attributes = string.Empty; - public override string GetState() => MicrophoneProcess(); - public void SetAttributes(string value) => _attributes = string.IsNullOrWhiteSpace(value) ? "{}" : value; - public override string GetAttributes() => _attributes; - public override DiscoveryConfigModel GetAutoDiscoveryConfig() { if (Variables.MqttManager == null) @@ -49,40 +44,31 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability", - Icon = "mdi:microphone" + Icon = "mdi:microphone", + Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" }; - if (UseAttributes) - { - model.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes"; - } - return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(model); } private string MicrophoneProcess() { - const string regKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"; - _processes.Clear(); // first local machine - using (var key = Registry.LocalMachine.OpenSubKey(regKey)) + using (var key = Registry.LocalMachine.OpenSubKey(_regKey)) { CheckRegForMicrophoneInUse(key); } // then current user - using (var key = Registry.CurrentUser.OpenSubKey(regKey)) + using (var key = Registry.CurrentUser.OpenSubKey(_regKey)) { CheckRegForMicrophoneInUse(key); } // add processes as attributes - if (_processes.Count > 0) - { - _attributes = JsonConvert.SerializeObject(_processes, Formatting.Indented); - } + _attributes = _processes.Count > 0 ? JsonConvert.SerializeObject(_processes, Formatting.Indented) : "{}"; // return the count return _processes.Count.ToString(); @@ -109,13 +95,13 @@ private void CheckRegForMicrophoneInUse(RegistryKey key) foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames()) { using var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName); - if (subKey == null || !subKey.GetValueNames().Contains(LastUsedTimeStop)) + if (subKey == null || !subKey.GetValueNames().Contains(_lastUsedTimeStop)) { continue; } - var endTime = subKey.GetValue(LastUsedTimeStop) is long - ? (long)(subKey.GetValue(LastUsedTimeStop) ?? -1) + var endTime = subKey.GetValue(_lastUsedTimeStop) is long + ? (long)(subKey.GetValue(_lastUsedTimeStop) ?? -1) : -1; if (endTime <= 0) @@ -127,12 +113,12 @@ private void CheckRegForMicrophoneInUse(RegistryKey key) else { using var subKey = key.OpenSubKey(subKeyName); - if (subKey == null || !subKey.GetValueNames().Contains(LastUsedTimeStop)) + if (subKey == null || !subKey.GetValueNames().Contains(_lastUsedTimeStop)) { continue; } - var endTime = subKey.GetValue(LastUsedTimeStop) is long ? (long)(subKey.GetValue(LastUsedTimeStop) ?? -1) : -1; + var endTime = subKey.GetValue(_lastUsedTimeStop) is long ? (long)(subKey.GetValue(_lastUsedTimeStop) ?? -1) : -1; if (endTime <= 0) { _processes[SharedHelperFunctions.ParseRegWebcamMicApplicationName(subKey.Name)] = "on"; @@ -140,4 +126,7 @@ private void CheckRegForMicrophoneInUse(RegistryKey key) } } } + + public override string GetState() => MicrophoneProcess(); + public override string GetAttributes() => _attributes; } From 07a76e5e99afa87004c308c14b048f7649dbcd8a Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 2 May 2024 11:35:36 +0200 Subject: [PATCH 20/45] Feature: ActiveWindowSensor process name attribute (#77) This PR adds "ProcessName" attribute to the ActiveWindowSensor. --- .../SingleValue/ActiveWindowSensor.cs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs index 713a50e7..9751eef4 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using HASS.Agent.Shared.Models.HomeAssistant; +using Newtonsoft.Json; namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue { @@ -12,7 +14,9 @@ public class ActiveWindowSensor : AbstractSingleValueSensor { private const string DefaultName = "activewindow"; - public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id) { } + private string _processName = string.Empty; + + public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, true) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { @@ -31,16 +35,29 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Icon = "mdi:window-maximize", - Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability", + Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" }); } public override string GetState() { - return GetActiveWindowTitle(); + var windowHandle = GetForegroundWindow(); + + var returnValue = GetWindowThreadProcessId(windowHandle, out var processId); + if (returnValue != 0 && processId != 0) + { + using var process = Process.GetProcessById(Convert.ToInt32(processId)); + _processName = process.ProcessName ?? string.Empty; + } + + return GetWindowTitle(windowHandle); } - public override string GetAttributes() => string.Empty; + public override string GetAttributes() => JsonConvert.SerializeObject(new + { + processName = _processName + }); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); @@ -51,14 +68,16 @@ public override string GetState() [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder builder, int count); - private static string GetActiveWindowTitle() + [DllImport("user32.dll", SetLastError = true)] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + private static string GetWindowTitle(IntPtr windowHandle) { - var windowHandle = GetForegroundWindow(); var titleLength = GetWindowTextLength(windowHandle) + 1; var builder = new StringBuilder(titleLength); var windowTitle = GetWindowText(windowHandle, builder, titleLength) > 0 ? builder.ToString() : string.Empty; - return windowTitle.Length > 255 ? windowTitle[..255]: windowTitle; //Note(Amadeo): to make sure we don't exceed HA limitation of 255 payload length + return windowTitle.Length > 255 ? windowTitle[..255] : windowTitle; //Note(Amadeo): to make sure we don't exceed HA limitation of 255 payload length } } } From d5d84ae8912184aae8c8f80026cf362cb62b3eeb Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 2 May 2024 20:54:39 +0200 Subject: [PATCH 21/45] Fix: added additional checks for management object property or its value being null (#78) This PR adds additional checks for WMI sensor where one of the property values can be null. --- .../HomeAssistant/Sensors/WmiQuerySensor.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs index 62858511..2e829aae 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs @@ -31,14 +31,14 @@ public WmiQuerySensor(string query, string scope = "", bool applyRounding = fals ObjectQuery = new ObjectQuery(Query); // use either default or provided scope - var managementscope = !string.IsNullOrWhiteSpace(scope) - ? new ManagementScope(scope) + var managementscope = !string.IsNullOrWhiteSpace(scope) + ? new ManagementScope(scope) : new ManagementScope(@"\\localhost\"); // prepare searcher Searcher = new ManagementObjectSearcher(managementscope, ObjectQuery); } - + public void Dispose() => Searcher?.Dispose(); public override DiscoveryConfigModel GetAutoDiscoveryConfig() @@ -58,7 +58,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } - + public override string GetState() { using var collection = Searcher.Get(); @@ -68,12 +68,13 @@ public override string GetState() { try { - if (!string.IsNullOrEmpty(retValue)) continue; + if (!string.IsNullOrEmpty(retValue)) + continue; using var managementObject = (ManagementObject)managementBaseObject; foreach (var property in managementObject.Properties) { - retValue = property.Value.ToString(); + retValue = property?.Value?.ToString() ?? string.Empty; break; } } @@ -84,7 +85,10 @@ public override string GetState() } // optionally apply rounding - if (ApplyRounding && Round != null && double.TryParse(retValue, out var dblValue)) { retValue = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); } + if (ApplyRounding && Round != null && double.TryParse(retValue, out var dblValue)) + { + retValue = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); + } // done return retValue; From d8dd96a59b34e6080972d1ebf9351625813abad6 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 2 May 2024 23:00:53 +0200 Subject: [PATCH 22/45] Feature: advanced sensor settings (device_class, unit_of_measurement, state_class) (#79) This PR adds feature requested via #66 - ability to override device_class, unit_of_measurement and state_class for the sensors. Important Note Due to "tech debt" restrictions, despite advanced options being enabled for multiple value sensors (like audio sensor), the advanced settings will work only for single value sensors. --- .../Settings/StoredSensors.cs | 72 +- .../SingleValue/ActiveWindowSensor.cs | 2 +- .../SingleValue/CurrentVolumeSensor.cs | 2 +- .../GeneralSensors/SingleValue/DummySensor.cs | 2 +- .../SingleValue/GpuLoadSensor.cs | 2 +- .../SingleValue/GpuTemperatureSensor.cs | 2 +- .../SingleValue/LastActiveSensor.cs | 2 +- .../SingleValue/LastBootSensor.cs | 2 +- .../LastSystemStateChangeSensor.cs | 2 +- .../SingleValue/LoggedUserSensor.cs | 2 +- .../SingleValue/LoggedUsersSensor.cs | 2 +- .../SingleValue/MicrophoneActiveSensor.cs | 2 +- .../SingleValue/MicrophoneProcessSensor.cs | 7 +- .../SingleValue/NamedWindowSensor.cs | 2 +- .../SingleValue/ProcessActiveSensor.cs | 2 +- .../SingleValue/ScreenshotSensor.cs | 2 +- .../SingleValue/ServiceStateSensor.cs | 2 +- .../SingleValue/SessionStateSensor.cs | 2 +- .../UserNotificationStateSensor.cs | 2 +- .../SingleValue/WebcamActiveSensor.cs | 2 +- .../SingleValue/WebcamProcessSensor.cs | 2 +- .../SingleValue/WindowStateSensor.cs | 2 +- .../SingleValue/CpuLoadSensor.cs | 2 +- .../Sensors/PerformanceCounterSensor.cs | 2 +- .../HomeAssistant/Sensors/PowershellSensor.cs | 2 +- .../HomeAssistant/Sensors/WmiQuerySensor.cs | 13 +- .../SingleValue/CurrentClockSpeedSensor.cs | 2 +- .../SingleValue/MemoryUsageSensor.cs | 2 +- .../Models/Config/ConfiguredSensor.cs | 1 + .../AbstractSingleValueSensor.cs | 27 +- .../HomeAssistant/DiscoveryConfigModel.cs | 6 + .../Models/Internal/SensorAdvancedSettings.cs | 13 + .../AdvancedSensorSettings.Designer.cs | 209 ++ .../SensorConfig/AdvancedSensorSettings.cs | 58 + .../AdvancedSensorSettings.de.resx | 14 + .../AdvancedSensorSettings.es.resx | 14 + .../AdvancedSensorSettings.fr.resx | 14 + .../AdvancedSensorSettings.nl.resx | 14 + .../SensorConfig/AdvancedSensorSettings.resx | 147 ++ .../AdvancedSensorSettings.ru.resx | 14 + .../AdvancedSensorSettings.tr.resx | 14 + .../Forms/Sensors/SensorsMod.Designer.cs | 39 +- .../HASS.Agent/Forms/Sensors/SensorsMod.cs | 2048 +++++++++-------- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 21 + .../SingleValue/ActiveDesktopSensor.cs | 2 +- .../SingleValue/BluetoothDevicesSensor.cs | 2 +- .../SingleValue/BluetoothLeDevicesSensor.cs | 2 +- .../SingleValue/GeoLocationSensor.cs | 2 +- .../SingleValue/InternalDeviceSensor.cs | 2 +- .../SingleValue/MonitorPowerStateSensor.cs | 2 +- .../Localization/Languages.Designer.cs | 54 + .../Resources/Localization/Languages.de.resx | 9 + .../Resources/Localization/Languages.en.resx | 18 + .../Resources/Localization/Languages.es.resx | 9 + .../Resources/Localization/Languages.fr.resx | 9 + .../Resources/Localization/Languages.nl.resx | 9 + .../Resources/Localization/Languages.pl.resx | 9 + .../Localization/Languages.pt-br.resx | 9 + .../Resources/Localization/Languages.resx | 18 + .../Resources/Localization/Languages.ru.resx | 9 + .../Resources/Localization/Languages.sl.resx | 9 + .../Resources/Localization/Languages.tr.resx | 9 + .../HASS.Agent/Settings/StoredSensors.cs | 101 +- 63 files changed, 1944 insertions(+), 1135 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx create mode 100644 src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs index e2f2765d..5dfdb27a 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs @@ -90,82 +90,82 @@ await Task.Run(delegate switch (sensor.Type) { case SensorType.UserNotificationStateSensor: - abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.DummySensor: - abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentClockSpeedSensor: - abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.CpuLoadSensor: - abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.MemoryUsageSensor: - abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.ActiveWindowSensor: - abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.NamedWindowSensor: - abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastActiveSensor: - abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastSystemStateChangeSensor: - abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastBootSensor: - abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamActiveSensor: - abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneActiveSensor: - abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.SessionStateSensor: - abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentVolumeSensor: - abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuLoadSensor: - abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuTemperatureSensor: - abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WmiQuerySensor: - abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PerformanceCounterSensor: - abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ProcessActiveSensor: - abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ServiceStateSensor: - abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUsersSensor: - abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUserSensor: - abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PowershellSensor: - abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WindowStateSensor: - abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneProcessSensor: - abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamProcessSensor: - abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; default: Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured single-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); @@ -235,6 +235,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Query = wmiSensor.Query, ApplyRounding = wmiSensor.ApplyRounding, Round = wmiSensor.Round, + AdvancedSettings = wmiSensor.AdvancedSettings }; } @@ -248,7 +249,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = namedWindowSensor.EntityName, Type = type, UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, - WindowName = namedWindowSensor.WindowName + WindowName = namedWindowSensor.WindowName, + AdvancedSettings = namedWindowSensor.AdvancedSettings }; } @@ -267,6 +269,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Instance = performanceCounterSensor.InstanceName, ApplyRounding = performanceCounterSensor.ApplyRounding, Round = performanceCounterSensor.Round, + AdvancedSettings = performanceCounterSensor.AdvancedSettings }; } @@ -280,7 +283,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = processActiveSensor.EntityName, Type = type, UpdateInterval = processActiveSensor.UpdateIntervalSeconds, - Query = processActiveSensor.ProcessName + Query = processActiveSensor.ProcessName, + AdvancedSettings = processActiveSensor.AdvancedSettings }; } @@ -294,7 +298,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = serviceStateSensor.EntityName, Type = type, UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, - Query = serviceStateSensor.ServiceName + Query = serviceStateSensor.ServiceName, + AdvancedSettings = serviceStateSensor.AdvancedSettings }; } @@ -310,7 +315,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract UpdateInterval = powershellSensor.UpdateIntervalSeconds, Query = powershellSensor.Command, ApplyRounding = powershellSensor.ApplyRounding, - Round = powershellSensor.Round + Round = powershellSensor.Round, + AdvancedSettings = powershellSensor.AdvancedSettings }; } @@ -324,7 +330,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = windowStateSensor.EntityName, Type = type, UpdateInterval = windowStateSensor.UpdateIntervalSeconds, - Query = windowStateSensor.ProcessName + Query = windowStateSensor.ProcessName, + AdvancedSettings = windowStateSensor.AdvancedSettings }; } @@ -337,7 +344,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract EntityName = sensor.EntityName, Name = sensor.EntityName, Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds + UpdateInterval = sensor.UpdateIntervalSeconds, + AdvancedSettings = sensor.AdvancedSettings }; } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs index 9751eef4..849a1a28 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs @@ -16,7 +16,7 @@ public class ActiveWindowSensor : AbstractSingleValueSensor private string _processName = string.Empty; - public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, true) { } + public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs index 7481c3e9..b327c891 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs @@ -12,7 +12,7 @@ public class CurrentVolumeSensor : AbstractSingleValueSensor { private const string DefaultName = "currentvolume"; - public CurrentVolumeSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id) { } + public CurrentVolumeSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs index 767f17ca..2e298eab 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs @@ -9,7 +9,7 @@ public class DummySensor : AbstractSingleValueSensor { private const string DefaultName = "dummy"; - public DummySensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 5, id) { } + public DummySensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 5, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs index 1e075b67..16d1ce0b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs @@ -14,7 +14,7 @@ public class GpuLoadSensor : AbstractSingleValueSensor private const string DefaultName = "gpuload"; private readonly IHardware _gpu; - public GpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public GpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { _gpu = HardwareManager.Hardware.FirstOrDefault( h => h.HardwareType == HardwareType.GpuAmd || diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs index 574f1429..96a209ea 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs @@ -14,7 +14,7 @@ public class GpuTemperatureSensor : AbstractSingleValueSensor private const string DefaultName = "gputemperature"; private readonly IHardware _gpu; - public GpuTemperatureSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public GpuTemperatureSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { _gpu = HardwareManager.Hardware.FirstOrDefault( h => h.HardwareType == HardwareType.GpuAmd || diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs index 80473337..bd67b9d6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs @@ -23,7 +23,7 @@ public class LastActiveSensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int Round { get; private set; } - public LastActiveSensor(bool updateOnResume, int? updateOnResumeTimeWindow, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public LastActiveSensor(bool updateOnResume, int? updateOnResumeTimeWindow, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { ApplyRounding = updateOnResume; Round = updateOnResumeTimeWindow ?? 30; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs index 2f24b0a2..c8e6429c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs @@ -13,7 +13,7 @@ public class LastBootSensor : AbstractSingleValueSensor { private const string DefaultName = "lastboot"; - public LastBootSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public LastBootSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs index 1e24dbf9..482c0fad 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs @@ -10,7 +10,7 @@ public class LastSystemStateChangeSensor : AbstractSingleValueSensor { private const string DefaultName = "lastsystemstatechange"; - public LastSystemStateChangeSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public LastSystemStateChangeSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs index 487377d8..ba14ba5b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs @@ -10,7 +10,7 @@ public class LoggedUserSensor : AbstractSingleValueSensor { private const string DefaultName = "loggeduser"; - public LoggedUserSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public LoggedUserSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs index d0940f9e..e10c958e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs @@ -18,7 +18,7 @@ public class LoggedUsersSensor : AbstractSingleValueSensor { private const string DefaultName = "loggedusers"; - public LoggedUsersSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) { } + public LoggedUsersSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs index 4198a9fe..5d630ef6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs @@ -11,7 +11,7 @@ public class MicrophoneActiveSensor : AbstractSingleValueSensor { private const string DefaultName = "microphoneactive"; - public MicrophoneActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public MicrophoneActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { Domain = "binary_sensor"; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs index 7c92e3e3..0b57d35c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs @@ -16,8 +16,11 @@ public class MicrophoneProcessSensor : AbstractSingleValueSensor private const string _lastUsedTimeStop = "LastUsedTimeStop"; private const string _regKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"; - - public MicrophoneProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, true) { } + + public MicrophoneProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, true, advancedSettings: advancedSettings) + { + // + } private readonly Dictionary _processes = new(); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs index 2d18cb25..b3f494c7 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs @@ -12,7 +12,7 @@ public class NamedWindowSensor : AbstractSingleValueSensor private const string DefaultName = "namedwindow"; public string WindowName { get; protected set; } - public NamedWindowSensor(string windowName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public NamedWindowSensor(string windowName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { Domain = "binary_sensor"; WindowName = windowName; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs index 731f05a4..594e2c5d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs @@ -13,7 +13,7 @@ public class ProcessActiveSensor : AbstractSingleValueSensor private const string DefaultName = "processactive"; public string ProcessName { get; protected set; } - public ProcessActiveSensor(string processName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public ProcessActiveSensor(string processName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { ProcessName = processName; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs index 0fc98195..f3305b50 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs @@ -21,7 +21,7 @@ public class ScreenshotSensor : AbstractSingleValueSensor public int ScreenIndex; - public ScreenshotSensor(string screenIndex = "0", int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id) + public ScreenshotSensor(string screenIndex = "0", int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { ScreenIndex = int.TryParse(screenIndex, out var parsedScreenIndex) ? parsedScreenIndex : 0; Domain = "camera"; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs index 0b11de55..f3655606 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs @@ -12,7 +12,7 @@ public class ServiceStateSensor : AbstractSingleValueSensor private const string DefaultName = "servicestate"; public string ServiceName { get; protected set; } - public ServiceStateSensor(string serviceName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public ServiceStateSensor(string serviceName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { ServiceName = serviceName; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs index 71671e8d..9f698e0e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs @@ -10,7 +10,7 @@ public class SessionStateSensor : AbstractSingleValueSensor { private const string DefaultName = "sessionstate"; - public SessionStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public SessionStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs index 6567af7c..ee66f883 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs @@ -11,7 +11,7 @@ public class UserNotificationStateSensor : AbstractSingleValueSensor { private const string DefaultName = "notificationstate"; - public UserNotificationStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public UserNotificationStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs index f1729cf9..372f8a1c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs @@ -11,7 +11,7 @@ public class WebcamActiveSensor : AbstractSingleValueSensor { private const string DefaultName = "webcamactive"; - public WebcamActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public WebcamActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { Domain = "binary_sensor"; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs index 6f98ca4e..c698f90c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs @@ -14,7 +14,7 @@ public class WebcamProcessSensor : AbstractSingleValueSensor { private const string DefaultName = "webcamprocess"; - public WebcamProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool useAttributes = true) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, useAttributes) + public WebcamProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, true, advancedSettings: advancedSettings) { // } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs index 17ba6c70..b07eccc6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs @@ -15,7 +15,7 @@ public class WindowStateSensor : AbstractSingleValueSensor private const string DefaultName = "windowstate"; public string ProcessName { get; protected set; } - public WindowStateSensor(string processName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public WindowStateSensor(string processName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { ProcessName = processName; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs index f7ab6dbd..d55b4664 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs @@ -9,7 +9,7 @@ public class CpuLoadSensor : PerformanceCounterSensor { private const string DefaultName = "cpuload"; - public CpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null) : base("Processor", "% Processor Time", "_Total", applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id) { } + public CpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null, string advancedSettings = default) : base("Processor", "% Processor Time", "_Total", applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs index 3c429b8d..2aa2f9d1 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs @@ -22,7 +22,7 @@ public class PerformanceCounterSensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int? Round { get; private set; } - public PerformanceCounterSensor(string categoryName, string counterName, string instanceName, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public PerformanceCounterSensor(string categoryName, string counterName, string instanceName, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { CategoryName = categoryName; CounterName = counterName; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs index ccbd2859..832f7e66 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs @@ -18,7 +18,7 @@ public class PowershellSensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int? Round { get; private set; } - public PowershellSensor(string command, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public PowershellSensor(string command, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { Command = command; ApplyRounding = applyRounding; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs index 2e829aae..14bf596d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Management; using HASS.Agent.Shared.Models.HomeAssistant; +using HASS.Agent.Shared.Models.Internal; +using Newtonsoft.Json; namespace HASS.Agent.Shared.HomeAssistant.Sensors { @@ -17,15 +19,18 @@ public class WmiQuerySensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int? Round { get; private set; } + public string AdvancedSettings { get; private set; } + protected readonly ObjectQuery ObjectQuery; protected readonly ManagementObjectSearcher Searcher; - public WmiQuerySensor(string query, string scope = "", bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public WmiQuerySensor(string query, string scope = "", bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, false, advancedSettings) { Query = query; Scope = scope; ApplyRounding = applyRounding; Round = round; + AdvancedSettings = advancedSettings; // prepare query ObjectQuery = new ObjectQuery(Query); @@ -43,10 +48,12 @@ public WmiQuerySensor(string query, string scope = "", bool applyRounding = fals public override DiscoveryConfigModel GetAutoDiscoveryConfig() { - if (Variables.MqttManager == null) return null; + if (Variables.MqttManager == null) + return null; var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); - if (deviceConfig == null) return null; + if (deviceConfig == null) + return null; return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs index b431dc14..82e34202 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs @@ -17,7 +17,7 @@ public class CurrentClockSpeedSensor : WmiQuerySensor private protected DateTime LastFetched = DateTime.MinValue; private protected string LastValue = string.Empty; - public CurrentClockSpeedSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null) : base(string.Empty, string.Empty, applyRounding, round, updateInterval ?? 300, entityName ?? DefaultName, name ?? null, id) + public CurrentClockSpeedSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null, string advancedSettings = default) : base(string.Empty, string.Empty, applyRounding, round, updateInterval ?? 300, entityName ?? DefaultName, name ?? null, id, advancedSettings: advancedSettings) => _managementObject = new ManagementObject("Win32_Processor.DeviceID='CPU0'"); public override DiscoveryConfigModel GetAutoDiscoveryConfig() diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs index fa87542b..77b61ba6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs @@ -11,7 +11,7 @@ public class MemoryUsageSensor : WmiQuerySensor { private const string DefaultName = "memoryusage"; - public MemoryUsageSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null) : base("SELECT FreePhysicalMemory,TotalVisibleMemorySize FROM Win32_OperatingSystem", string.Empty, applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id) { } + public MemoryUsageSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null, string advancedSettings = default) : base("SELECT FreePhysicalMemory,TotalVisibleMemorySize FROM Win32_OperatingSystem", string.Empty, applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs index 1ee66b4e..13bc204b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs @@ -26,6 +26,7 @@ public class ConfiguredSensor public bool IgnoreAvailability { get; set; } = false; public bool ApplyRounding { get; set; } = false; public int? Round { get; set; } + public string AdvancedSettings { get; set; } = string.Empty; public static ConfiguredSensor FromLAB02(ConfiguredSensorLAB02 oldConfig) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs index 25e41799..eb7ee64c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; +using HASS.Agent.Shared.Models.Internal; using MQTTnet; +using Newtonsoft.Json; using Serilog; namespace HASS.Agent.Shared.Models.HomeAssistant; @@ -10,13 +12,17 @@ namespace HASS.Agent.Shared.Models.HomeAssistant; /// public abstract class AbstractSingleValueSensor : AbstractDiscoverable { + private SensorAdvancedSettings _advancedInfo; + public int UpdateIntervalSeconds { get; protected set; } public DateTime? LastUpdated { get; protected set; } public string PreviousPublishedState { get; protected set; } = string.Empty; public string PreviousPublishedAttributes { get; protected set; } = string.Empty; - protected AbstractSingleValueSensor(string entityName, string name, int updateIntervalSeconds = 10, string id = default, bool useAttributes = false) + public string AdvancedSettings { get; private set; } + + protected AbstractSingleValueSensor(string entityName, string name, int updateIntervalSeconds = 10, string id = default, bool useAttributes = false, string advancedSettings = default) { Id = id == null || id == Guid.Empty.ToString() ? Guid.NewGuid().ToString() : id; EntityName = entityName; @@ -24,13 +30,30 @@ protected AbstractSingleValueSensor(string entityName, string name, int updateIn UpdateIntervalSeconds = updateIntervalSeconds; Domain = "sensor"; UseAttributes = useAttributes; + + if (!string.IsNullOrWhiteSpace(advancedSettings)) + { + _advancedInfo = JsonConvert.DeserializeObject(advancedSettings); + } } protected SensorDiscoveryConfigModel AutoDiscoveryConfigModel; protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config) { AutoDiscoveryConfigModel = config; - return config; + + // overwrite with advanced settings + if (_advancedInfo != null) + { + if (!string.IsNullOrWhiteSpace(_advancedInfo.DeviceClass)) + AutoDiscoveryConfigModel.Device_class = _advancedInfo.DeviceClass; + if (!string.IsNullOrWhiteSpace(_advancedInfo.UnitOfMeasurement)) + AutoDiscoveryConfigModel.Unit_of_measurement = _advancedInfo.UnitOfMeasurement; + if (!string.IsNullOrWhiteSpace(_advancedInfo.StateClass)) + AutoDiscoveryConfigModel.State_class = _advancedInfo.StateClass; + } + + return AutoDiscoveryConfigModel; } public override void ClearAutoDiscoveryConfig() => AutoDiscoveryConfigModel = null; diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs index 4a98233b..8d2cd9f5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs @@ -52,6 +52,12 @@ public class SensorDiscoveryConfigModel : DiscoveryConfigModel /// public string Device_class { get; set; } + /// + /// (Optional) The state class of the sensor. See https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes for options. + /// + /// + public string State_class { get; set; } + /// /// (Optional) Defines the number of seconds after the sensor’s state expires, if it’s not updated. After expiry, the sensor’s state becomes unavailable. Defaults to 0 in hass. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs new file mode 100644 index 00000000..77aed989 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Models.Internal; +public class SensorAdvancedSettings +{ + public string DeviceClass { get; set; } + public string UnitOfMeasurement { get; set; } + public string StateClass { get; set; } +} diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs new file mode 100644 index 00000000..0f922ff9 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs @@ -0,0 +1,209 @@ + +using HASS.Agent.Resources.Localization; + +namespace HASS.Agent.Forms.Commands.CommandConfig +{ + partial class AdvancedSensorSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + var resources = new System.ComponentModel.ComponentResourceManager(typeof(AdvancedSensorSettings)); + BtnSave = new Syncfusion.WinForms.Controls.SfButton(); + LblInfo1 = new Label(); + LblDeviceClass = new Label(); + TbDeviceClass = new TextBox(); + LblUnitOfMeasurement = new Label(); + TbUnitOfMeasurement = new TextBox(); + LblStateClass = new Label(); + TbStateClass = new TextBox(); + SuspendLayout(); + // + // BtnSave + // + BtnSave.AccessibleDescription = "Saves and closes the window."; + BtnSave.AccessibleName = "Save"; + BtnSave.AccessibleRole = AccessibleRole.PushButton; + BtnSave.BackColor = Color.FromArgb(63, 63, 70); + BtnSave.Dock = DockStyle.Bottom; + BtnSave.Font = new Font("Segoe UI", 10F); + BtnSave.ForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Location = new Point(0, 299); + BtnSave.Name = "BtnSave"; + BtnSave.Size = new Size(349, 37); + BtnSave.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnSave.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnSave.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnSave.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Style.PressedForeColor = Color.Black; + BtnSave.TabIndex = 8; + BtnSave.Text = Languages.WebViewCommandConfig_BtnSave; + BtnSave.UseVisualStyleBackColor = false; + BtnSave.Click += BtnSave_Click; + // + // LblInfo1 + // + LblInfo1.AccessibleDescription = "Warning regarding advanced settings"; + LblInfo1.AccessibleName = "Advanced settings warning"; + LblInfo1.AccessibleRole = AccessibleRole.StaticText; + LblInfo1.Font = new Font("Segoe UI", 10F); + LblInfo1.Location = new Point(12, 13); + LblInfo1.Name = "LblInfo1"; + LblInfo1.Size = new Size(325, 45); + LblInfo1.TabIndex = 3; + LblInfo1.Text = Languages.AdvancedSensorConfig_Warning; + LblInfo1.TextAlign = ContentAlignment.MiddleCenter; + // + // LblDeviceClass + // + LblDeviceClass.AccessibleDescription = "Device class textbox"; + LblDeviceClass.AccessibleName = "Device class"; + LblDeviceClass.AccessibleRole = AccessibleRole.StaticText; + LblDeviceClass.AutoSize = true; + LblDeviceClass.Font = new Font("Segoe UI", 10F); + LblDeviceClass.Location = new Point(34, 81); + LblDeviceClass.Name = "LblDeviceClass"; + LblDeviceClass.Size = new Size(81, 19); + LblDeviceClass.TabIndex = 5; + LblDeviceClass.Text = Languages.AdvancedSensorConfig_LblDeviceClass; + // + // TbDeviceClass + // + TbDeviceClass.AccessibleDescription = "Device class of the sensor"; + TbDeviceClass.AccessibleName = "URL"; + TbDeviceClass.AccessibleRole = AccessibleRole.Text; + TbDeviceClass.BackColor = Color.FromArgb(63, 63, 70); + TbDeviceClass.BorderStyle = BorderStyle.FixedSingle; + TbDeviceClass.Font = new Font("Segoe UI", 10F); + TbDeviceClass.ForeColor = Color.FromArgb(241, 241, 241); + TbDeviceClass.Location = new Point(34, 103); + TbDeviceClass.Name = "TbDeviceClass"; + TbDeviceClass.Size = new Size(282, 25); + TbDeviceClass.TabIndex = 0; + // + // LblUnitOfMeasurement + // + LblUnitOfMeasurement.AccessibleDescription = "Unit of measurement textbox"; + LblUnitOfMeasurement.AccessibleName = "Unit of measurement"; + LblUnitOfMeasurement.AccessibleRole = AccessibleRole.StaticText; + LblUnitOfMeasurement.AutoSize = true; + LblUnitOfMeasurement.Font = new Font("Segoe UI", 10F); + LblUnitOfMeasurement.Location = new Point(34, 143); + LblUnitOfMeasurement.Name = "LblUnitOfMeasurement"; + LblUnitOfMeasurement.Size = new Size(139, 19); + LblUnitOfMeasurement.TabIndex = 10; + LblUnitOfMeasurement.Text = Languages.AdvancedSensorConfig_LblUnitOfMeasurement; + // + // TbUnitOfMeasurement + // + TbUnitOfMeasurement.AccessibleDescription = "Device class of the sensor"; + TbUnitOfMeasurement.AccessibleName = "URL"; + TbUnitOfMeasurement.AccessibleRole = AccessibleRole.Text; + TbUnitOfMeasurement.BackColor = Color.FromArgb(63, 63, 70); + TbUnitOfMeasurement.BorderStyle = BorderStyle.FixedSingle; + TbUnitOfMeasurement.Font = new Font("Segoe UI", 10F); + TbUnitOfMeasurement.ForeColor = Color.FromArgb(241, 241, 241); + TbUnitOfMeasurement.Location = new Point(34, 165); + TbUnitOfMeasurement.Name = "TbUnitOfMeasurement"; + TbUnitOfMeasurement.Size = new Size(282, 25); + TbUnitOfMeasurement.TabIndex = 9; + // + // LblStateClass + // + LblStateClass.AccessibleDescription = "State class textbox"; + LblStateClass.AccessibleName = "State class"; + LblStateClass.AccessibleRole = AccessibleRole.StaticText; + LblStateClass.AutoSize = true; + LblStateClass.Font = new Font("Segoe UI", 10F); + LblStateClass.Location = new Point(34, 206); + LblStateClass.Name = "LblStateClass"; + LblStateClass.Size = new Size(72, 19); + LblStateClass.TabIndex = 12; + LblStateClass.Text = Languages.AdvancedSensorConfig_LblStateClass; + // + // TbStateClass + // + TbStateClass.AccessibleDescription = "State class of the sensor"; + TbStateClass.AccessibleName = "URL"; + TbStateClass.AccessibleRole = AccessibleRole.Text; + TbStateClass.BackColor = Color.FromArgb(63, 63, 70); + TbStateClass.BorderStyle = BorderStyle.FixedSingle; + TbStateClass.Font = new Font("Segoe UI", 10F); + TbStateClass.ForeColor = Color.FromArgb(241, 241, 241); + TbStateClass.Location = new Point(34, 228); + TbStateClass.Name = "TbStateClass"; + TbStateClass.Size = new Size(282, 25); + TbStateClass.TabIndex = 11; + // + // AdvancedSensorConfig + // + AccessibleDescription = "Configuration for the webview command, like position and url."; + AccessibleName = "Webview configuration"; + AccessibleRole = AccessibleRole.Window; + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + BackColor = Color.FromArgb(45, 45, 48); + CaptionBarColor = Color.FromArgb(63, 63, 70); + CaptionFont = new Font("Segoe UI", 10F); + CaptionForeColor = Color.FromArgb(241, 241, 241); + ClientSize = new Size(349, 336); + Controls.Add(LblStateClass); + Controls.Add(TbStateClass); + Controls.Add(LblUnitOfMeasurement); + Controls.Add(TbUnitOfMeasurement); + Controls.Add(BtnSave); + Controls.Add(LblDeviceClass); + Controls.Add(TbDeviceClass); + Controls.Add(LblInfo1); + DoubleBuffered = true; + ForeColor = Color.FromArgb(241, 241, 241); + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; + MetroColor = Color.FromArgb(63, 63, 70); + Name = "AdvancedSensorConfig"; + ShowMaximizeBox = false; + ShowMinimizeBox = false; + StartPosition = FormStartPosition.CenterScreen; + Text = Languages.AdvancedSensorConfig_Title; + Load += AdvancedSensorConfig_Load; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private Syncfusion.WinForms.Controls.SfButton BtnSave; + private System.Windows.Forms.Label LblInfo1; + private Label LblDeviceClass; + private TextBox TbDeviceClass; + private Label LblUnitOfMeasurement; + private TextBox TbUnitOfMeasurement; + private Label LblStateClass; + private TextBox TbStateClass; + } +} + diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs new file mode 100644 index 00000000..abff8f7c --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs @@ -0,0 +1,58 @@ +using HASS.Agent.Functions; +using HASS.Agent.Models.Internal; +using HASS.Agent.Resources.Localization; +using HASS.Agent.Shared.Models.Internal; +using Newtonsoft.Json; +using Serilog; +using Syncfusion.Windows.Forms; +using Syncfusion.WinForms.Controls.Styles; + +namespace HASS.Agent.Forms.Commands.CommandConfig; + +public partial class AdvancedSensorSettings : MetroForm +{ + + public SensorAdvancedSettings AdvancedSettings { get; private set; } = null; + + public AdvancedSensorSettings(string wmiAdvancedInfo = "") + { + InitializeComponent(); + + if (string.IsNullOrEmpty(wmiAdvancedInfo)) + return; + + var advancedInfo = JsonConvert.DeserializeObject(wmiAdvancedInfo); + if (advancedInfo == null) + return; + + AdvancedSettings = advancedInfo; + } + + private void AdvancedSensorConfig_Load(object sender, EventArgs e) + { + CaptionBarHeight = 26; + + if (AdvancedSettings != null) + SetStoredVariables(); + + Opacity = 100; + } + + private void SetStoredVariables() + { + TbDeviceClass.Text = AdvancedSettings.DeviceClass; + TbUnitOfMeasurement.Text = AdvancedSettings.UnitOfMeasurement; + TbStateClass.Text = AdvancedSettings.StateClass; + } + + private void BtnSave_Click(object sender, EventArgs e) + { + AdvancedSettings ??= new SensorAdvancedSettings(); + + AdvancedSettings.DeviceClass = TbDeviceClass.Text; + AdvancedSettings.UnitOfMeasurement = TbUnitOfMeasurement.Text; + AdvancedSettings.StateClass = TbStateClass.Text; + + DialogResult = DialogResult.OK; + } +} diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx new file mode 100644 index 00000000..9cfd6d5f --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAMDAAAAEAIAAQBQAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAwAAAAMAgGAAAAVwL5hwAAAAFz + UkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAEpUlEQVRoQ+2Ya4hU + ZRjHn7nujFnGZmksbVlecLMkpTXCiCQoiD5VHyzoQgRlEdEHMQoiKBI/2AW6YHQlCKIoqCCICEnKUCIr + VyzLsix101xT5z7T+3vPvOPb2TMz55w5zrQwPxjPOHP2nP//ub3vnNhVnx6ryRQmXj9OWfoGek3fQK/p + GwjKlWcl5JklGXl00YAMT+v89l1dB244Jyn3zkvX/ydyqFiTB78uyJ7j1fonwelaBmzxm8YrWvRgOibr + l3SWia4YsMVvPFCRx74vNCLfqYmTbsAt/vHtBamoorXLBxOr5qX0OUE5qQZGTot7ijdg4rP9Ff1++JT/ + QQZmpGL1dw7XnJ3Ux+8OVyeJB7Jz2xwn8m/sLuljUCIxQP2+cGlG3rsiKy8vy8hFpzuX3XbYie7IjLgs + PzOh3xvs0nrux6J8/GdZvw9KxwYQTxMuODWuIzxHlcLaxY4JyoZXQiXmkQsH9BoAbvHv/hZOPHRkwIin + CWnG2zfndLlklU5MEHlKxzaxemE6MvEQ2oBbPBNlb64ma7blW5q4tt4XUYiH0CvxhtGMzJ0eb4hnohiM + eMoop9oAU2MTVS3+8pkJ+fCPsnz5l9MfnRLKANF/7bKMrnnKhsi78TJBZqImVAkRdURTEqsXDmixbmzR + tpmoCX3FdTsKWiSiENcrE6GvhijEhTHBqI2KwFfi5ozBFbMSujHDmLjj/HD7Hi8CGWAhYvqwEDHTefk1 + YTM0LaYnWBT4nkKIRzCNu+totSHAbNKY90a8XV5gSsdd/3z/9p6SvPlLadI+yS++wmCLR/A9W/J6IbK/ + a5YJL/GsGZzHd2zm1l/inMv1pyfVPwFoa8At3uwqWUVtEytmJT0b2xa/L1/T5m/clJOHv528O127eEDe + WZ6V2Rn/JloaGD3DW7wBE/w8hGXqXHCbsCP/vDK885+q/uyJi53rmvPT8ZgsHUyoY/1kn7Q8/VaV3mbi + gWY22+TPx0/saxB139a8fLC3rBc9GC/UtFmTGUrGNrt08ESWePmlpYGJ+v6GDZs7MvaWmExg0mb3sao8 + tbMoT253yozabiYebhp2RuuWg/+9TjtaGuBXkl3P3Bjc4k0veIER4G+biV95bkr/niiqU9/6taQb+6ER + 5/rtaGmAerXrGQHczK94QNT7vzvl5SWeYNx1gRN9RiqZwgBDwc9EamkA7BtiwtzMj3jDSz8VdS/Y16K3 + 7GBQgq+rjNN38Mm+shwtt+8F3wsZTxhWzU/rEYd4Uh0E+iinpgCDgOjecl5KhrJOhBG7bkdRDwQeOXIO + 2/RELKbe1zy364auPVok4verAFw9O9noJSJM1AmIvd4wvfiMBwVw3cbj+uhF2xKKCjJw/ZAjnpH6ys8l + ufmLvGxWv8xoWCJvRvazPxTlgQUpfe7YRL1ZmtC1DAA7WcYxwwFoVvNcCD5SPzWfVqP37rlp3dyU0p1f + 5RtriRddywAwUo14mK9GJ3zzd0UvfK+qrFBGiAeGRCvx0NUMuGFMmqcaNPSG0awuGyL/4i5/Ty26mgE3 + NLGJMOvFeKEqY0ecbYgf8dDTDERBTzMQBX0DvaZvoNdMcQMi/wKHssFyJvHDuwAAAABJRU5ErkJggg== + + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs index ee6b3fed..e2070687 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs @@ -66,7 +66,8 @@ private void InitializeComponent() PbService = new PictureBox(); LblSpecificClient = new Label(); BtnTest = new Syncfusion.WinForms.Controls.SfButton(); - CbNetworkCard = new ComboBox(); + BtnAdvSettings = new Syncfusion.WinForms.Controls.SfButton(); + CbNetworkCard = new ComboBox(); NumRound = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); LblDigits = new Label(); CbApplyRounding = new CheckBox(); @@ -521,10 +522,34 @@ private void InitializeComponent() BtnTest.UseVisualStyleBackColor = false; BtnTest.Visible = false; BtnTest.Click += BtnTest_Click; - // - // CbNetworkCard - // - CbNetworkCard.AccessibleDescription = "List of available network cards."; + // + // BtnAdvSettings + // + BtnAdvSettings.AccessibleDescription = "Shows popup with advanced sensor settings."; + BtnAdvSettings.AccessibleName = "Advanced settings"; + BtnAdvSettings.AccessibleRole = AccessibleRole.PushButton; + BtnAdvSettings.BackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); + BtnAdvSettings.ForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Location = new Point(955, 446); + BtnAdvSettings.Name = "BtnTest"; + BtnAdvSettings.Size = new Size(354, 25); + BtnAdvSettings.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Style.PressedForeColor = Color.Black; + BtnAdvSettings.TabIndex = 48; + BtnAdvSettings.Text = Languages.SensorsMod_BtnAdvancedSettings; + BtnAdvSettings.UseVisualStyleBackColor = false; + BtnAdvSettings.Visible = false; + BtnAdvSettings.Click += BtnAdvSettings_Click; + // + // CbNetworkCard + // + CbNetworkCard.AccessibleDescription = "List of available network cards."; CbNetworkCard.AccessibleName = "Network cards"; CbNetworkCard.AccessibleRole = AccessibleRole.DropList; CbNetworkCard.BackColor = Color.FromArgb(63, 63, 70); @@ -688,6 +713,7 @@ private void InitializeComponent() Controls.Add(LblDescription); Controls.Add(LblSetting1); Controls.Add(BtnStore); + Controls.Add(BtnAdvSettings); Controls.Add(TbSetting1); Controls.Add(LblType); Controls.Add(LblSeconds); @@ -755,7 +781,8 @@ private void InitializeComponent() private Label LblSpecificClient; private ColumnHeader ClmId; private Syncfusion.WinForms.Controls.SfButton BtnTest; - private ComboBox CbNetworkCard; + private Syncfusion.WinForms.Controls.SfButton BtnAdvSettings; + private ComboBox CbNetworkCard; private Syncfusion.Windows.Forms.Tools.NumericUpDownExt NumRound; private Label LblDigits; internal CheckBox CbApplyRounding; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs index 987cbad3..e852dbbf 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs @@ -13,528 +13,530 @@ using HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; using HASS.Agent.Managers.DeviceSensors; using HASS.Agent.Managers; +using HASS.Agent.Forms.Commands.CommandConfig; +using Newtonsoft.Json; namespace HASS.Agent.Forms.Sensors { - public partial class SensorsMod : MetroForm - { - internal readonly ConfiguredSensor Sensor; - - private readonly bool _serviceMode; - private readonly string _serviceDeviceName; + public partial class SensorsMod : MetroForm + { + internal readonly ConfiguredSensor Sensor; - private bool _interfaceLockedWrongType; - private bool _loading = true; + private readonly bool _serviceMode; + private readonly string _serviceDeviceName; - private readonly Dictionary _networkCards = new(); - private readonly Dictionary _internalSensors = new(); - - private SensorType _selectedSensorType = SensorType.ActiveWindowSensor; + private bool _interfaceLockedWrongType; + private bool _loading = true; - public SensorsMod(ConfiguredSensor sensor, bool serviceMode = false, string serviceDeviceName = "") - { - Sensor = sensor; + private readonly Dictionary _networkCards = new(); + private readonly Dictionary _internalSensors = new(); - _serviceMode = serviceMode; - _serviceDeviceName = serviceDeviceName; - - InitializeComponent(); - - BindListViewTheme(); - - BindComboBoxTheme(); - } - - public SensorsMod(bool serviceMode = false, string serviceDeviceName = "") - { - Sensor = new ConfiguredSensor(); - - _serviceMode = serviceMode; - _serviceDeviceName = serviceDeviceName; - - InitializeComponent(); - - BindListViewTheme(); - - BindComboBoxTheme(); - } - - private void CbIgnoreAvailability_CheckedChanged(object sender, EventArgs e) - { - if ((sender as CheckBox).Checked) - MessageBoxAdv.Show(this, Languages.SensorsMod_IgnoreAvailability_Info, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - } - - private void SetCbIgnoreAvailability(bool check) - { - CbIgnoreAvailability.CheckedChanged -= CbIgnoreAvailability_CheckedChanged; - CbIgnoreAvailability.Checked = check; - CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; - } - - private void BindListViewTheme() - { - LvSensors.DrawItem += ListViewTheme.DrawItem; - LvSensors.DrawSubItem += ListViewTheme.DrawSubItem; - LvSensors.DrawColumnHeader += ListViewTheme.DrawColumnHeader; - } - - private void BindComboBoxTheme() => CbNetworkCard.DrawItem += ComboBoxTheme.DrawDictionaryStringStringItem; - - private void SensorMod_Load(object sender, EventArgs e) - { - // catch all key presses - KeyPreview = true; - - // load sensors - LvSensors.BeginUpdate(); - foreach (var sensor in SensorsManager.SensorInfoCards.Select(x => x.Value)) - { - var lvSensor = new ListViewItem(sensor.Key.ToString()); - lvSensor.SubItems.Add(sensor.Name); - lvSensor.SubItems.Add(sensor.MultiValue ? "√" : string.Empty); - lvSensor.SubItems.Add(sensor.AgentCompatible ? "√" : string.Empty); - lvSensor.SubItems.Add(sensor.SatelliteCompatible ? "√" : string.Empty); - LvSensors.Items.Add(lvSensor); - } - LvSensors.EndUpdate(); - - _networkCards.Add("*", Languages.SensorsMod_All); - foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) - _networkCards.Add(nic.Id, nic.Name); - - _internalSensors.Add("none", Languages.SensorsMod_None); - foreach (var internalSensor in InternalDeviceSensorsManager.AvailableSensors) - { - var internalSensorType = internalSensor.Type.ToString(); - _internalSensors.Add(internalSensorType, internalSensorType); - } - - CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; - - // load in gui - CbNetworkCard.DataSource = new BindingSource(_networkCards, null); - - // load or set sensor - if (Sensor.Id == Guid.Empty) - { - Sensor.Id = Guid.NewGuid(); - Text = Languages.SensorsMod_Title_New; - - // done - _loading = false; - return; - } - - // we're modding, load it - LoadSensor(); - Text = Languages.SensorsMod_Title_Mod; - - // done - _loading = false; - } - - /// - /// Loads the to-be-modded sensor - /// - private void LoadSensor() - { - // load the card - var sensorCard = SensorsManager.SensorInfoCards[Sensor.Type]; - - // set type - _selectedSensorType = sensorCard.SensorType; - - // load the type - TbSelectedType.Text = _selectedSensorType.ToString(); - - // select it as well - foreach (ListViewItem lvi in LvSensors.Items) - { - if (lvi.Text != sensorCard.Key.ToString()) continue; - lvi.Selected = true; - LvSensors.SelectedItems[0].EnsureVisible(); - break; - } - - // set gui - var guiOk = SetType(false); - if (!guiOk) return; - - // set the name - TbName.Text = Sensor.EntityName; - if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; - - // set the friendly name - TbFriendlyName.Text = Sensor.Name; - - // set interval - NumInterval.Text = Sensor.UpdateInterval?.ToString() ?? "10"; - - - SetCbIgnoreAvailability(Sensor.IgnoreAvailability); - - // set optional setting - switch (_selectedSensorType) - { - case SensorType.NamedWindowSensor: - TbSetting1.Text = Sensor.WindowName; - break; - - case SensorType.WmiQuerySensor: - TbSetting1.Text = Sensor.Query; - TbSetting2.Text = Sensor.Scope; - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? "2"; - break; - - case SensorType.PerformanceCounterSensor: - TbSetting1.Text = Sensor.Category; - TbSetting2.Text = Sensor.Counter; - TbSetting3.Text = Sensor.Instance; - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? "2"; - break; - - case SensorType.ProcessActiveSensor: - TbSetting1.Text = Sensor.Query; - break; - - case SensorType.ServiceStateSensor: - TbSetting1.Text = Sensor.Query; - break; - - case SensorType.PowershellSensor: - TbSetting1.Text = Sensor.Query; - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? "2"; - break; - - case SensorType.NetworkSensors: - if (_networkCards.ContainsKey(Sensor.Query)) - CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _networkCards[Sensor.Query]); - break; - - case SensorType.InternalDeviceSensor: - if (_internalSensors.ContainsKey(Sensor.Query)) - CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _internalSensors[Sensor.Query]); - break; - - case SensorType.WindowStateSensor: - TbSetting1.Text = Sensor.Query; - break; - - case SensorType.LastActiveSensor: - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? LastActiveSensor.DefaultTimeWindow.ToString(); ; - break; + private SensorType _selectedSensorType = SensorType.ActiveWindowSensor; + + public SensorsMod(ConfiguredSensor sensor, bool serviceMode = false, string serviceDeviceName = "") + { + Sensor = sensor; + + _serviceMode = serviceMode; + _serviceDeviceName = serviceDeviceName; + + InitializeComponent(); + + BindListViewTheme(); + + BindComboBoxTheme(); + } + + public SensorsMod(bool serviceMode = false, string serviceDeviceName = "") + { + Sensor = new ConfiguredSensor(); + + _serviceMode = serviceMode; + _serviceDeviceName = serviceDeviceName; + + InitializeComponent(); + + BindListViewTheme(); + + BindComboBoxTheme(); + } + + private void CbIgnoreAvailability_CheckedChanged(object sender, EventArgs e) + { + if ((sender as CheckBox).Checked) + MessageBoxAdv.Show(this, Languages.SensorsMod_IgnoreAvailability_Info, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + private void SetCbIgnoreAvailability(bool check) + { + CbIgnoreAvailability.CheckedChanged -= CbIgnoreAvailability_CheckedChanged; + CbIgnoreAvailability.Checked = check; + CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; + } + + private void BindListViewTheme() + { + LvSensors.DrawItem += ListViewTheme.DrawItem; + LvSensors.DrawSubItem += ListViewTheme.DrawSubItem; + LvSensors.DrawColumnHeader += ListViewTheme.DrawColumnHeader; + } + + private void BindComboBoxTheme() => CbNetworkCard.DrawItem += ComboBoxTheme.DrawDictionaryStringStringItem; + + private void SensorMod_Load(object sender, EventArgs e) + { + // catch all key presses + KeyPreview = true; + + // load sensors + LvSensors.BeginUpdate(); + foreach (var sensor in SensorsManager.SensorInfoCards.Select(x => x.Value)) + { + var lvSensor = new ListViewItem(sensor.Key.ToString()); + lvSensor.SubItems.Add(sensor.Name); + lvSensor.SubItems.Add(sensor.MultiValue ? "√" : string.Empty); + lvSensor.SubItems.Add(sensor.AgentCompatible ? "√" : string.Empty); + lvSensor.SubItems.Add(sensor.SatelliteCompatible ? "√" : string.Empty); + LvSensors.Items.Add(lvSensor); + } + LvSensors.EndUpdate(); + + _networkCards.Add("*", Languages.SensorsMod_All); + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + _networkCards.Add(nic.Id, nic.Name); + + _internalSensors.Add("none", Languages.SensorsMod_None); + foreach (var internalSensor in InternalDeviceSensorsManager.AvailableSensors) + { + var internalSensorType = internalSensor.Type.ToString(); + _internalSensors.Add(internalSensorType, internalSensorType); + } + + CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; + + // load in gui + CbNetworkCard.DataSource = new BindingSource(_networkCards, null); + + // load or set sensor + if (Sensor.Id == Guid.Empty) + { + Sensor.Id = Guid.NewGuid(); + Text = Languages.SensorsMod_Title_New; + + // done + _loading = false; + return; + } + + // we're modding, load it + LoadSensor(); + Text = Languages.SensorsMod_Title_Mod; + + // done + _loading = false; + } + + /// + /// Loads the to-be-modded sensor + /// + private void LoadSensor() + { + // load the card + var sensorCard = SensorsManager.SensorInfoCards[Sensor.Type]; + + // set type + _selectedSensorType = sensorCard.SensorType; + + // load the type + TbSelectedType.Text = _selectedSensorType.ToString(); + + // select it as well + foreach (ListViewItem lvi in LvSensors.Items) + { + if (lvi.Text != sensorCard.Key.ToString()) continue; + lvi.Selected = true; + LvSensors.SelectedItems[0].EnsureVisible(); + break; + } + + // set gui + var guiOk = SetType(false); + if (!guiOk) return; + + // set the name + TbName.Text = Sensor.EntityName; + if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; + + // set the friendly name + TbFriendlyName.Text = Sensor.Name; + + // set interval + NumInterval.Text = Sensor.UpdateInterval?.ToString() ?? "10"; + + + SetCbIgnoreAvailability(Sensor.IgnoreAvailability); + + // set optional setting + switch (_selectedSensorType) + { + case SensorType.NamedWindowSensor: + TbSetting1.Text = Sensor.WindowName; + break; + + case SensorType.WmiQuerySensor: + TbSetting1.Text = Sensor.Query; + TbSetting2.Text = Sensor.Scope; + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? "2"; + break; + + case SensorType.PerformanceCounterSensor: + TbSetting1.Text = Sensor.Category; + TbSetting2.Text = Sensor.Counter; + TbSetting3.Text = Sensor.Instance; + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? "2"; + break; + + case SensorType.ProcessActiveSensor: + TbSetting1.Text = Sensor.Query; + break; + + case SensorType.ServiceStateSensor: + TbSetting1.Text = Sensor.Query; + break; + + case SensorType.PowershellSensor: + TbSetting1.Text = Sensor.Query; + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? "2"; + break; + + case SensorType.NetworkSensors: + if (_networkCards.ContainsKey(Sensor.Query)) + CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _networkCards[Sensor.Query]); + break; + + case SensorType.InternalDeviceSensor: + if (_internalSensors.ContainsKey(Sensor.Query)) + CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _internalSensors[Sensor.Query]); + break; + + case SensorType.WindowStateSensor: + TbSetting1.Text = Sensor.Query; + break; + + case SensorType.LastActiveSensor: + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? LastActiveSensor.DefaultTimeWindow.ToString(); ; + break; case SensorType.ScreenshotSensor: TbSetting1.Text = Sensor.Query; break; } - } - - /// - /// Change the UI depending on the selected type - /// - /// - private bool SetType(bool setDefaultValues = true) - { - if (LvSensors.SelectedItems.Count == 0) - { - // was the interface locked? - if (_interfaceLockedWrongType) UnlockWrongClient(); - return false; - } - - // find the sensor card - var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); - var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) - .Select(card => card.Value).FirstOrDefault(); - if (sensorCard == null) return false; - - // can the current client load this type? - if (_serviceMode && !sensorCard.SatelliteCompatible) - { - LockWrongClient(); - return false; - } - - if (!_serviceMode && !sensorCard.AgentCompatible) - { - LockWrongClient(); - return false; - } - - // was the interface locked? - if (_interfaceLockedWrongType) UnlockWrongClient(); - - // set default values - if (setDefaultValues) - { - TbName.Text = sensorCard.SensorType.GetSensorName(); - NumInterval.Text = sensorCard.RefreshTimer.ToString(); - _selectedSensorType = sensorCard.SensorType; - } - - SetCbIgnoreAvailability(false); - - TbSelectedType.Text = sensorCard.SensorType.ToString(); - TbDescription.Text = sensorCard.Description; - CbApplyRounding.Visible = false; - NumRound.Visible = false; - LblDigits.Visible = false; - - // process the interface - switch (sensorCard.SensorType) - { - case SensorType.NamedWindowSensor: - SetWindowGui(); - break; - - case SensorType.WmiQuerySensor: - SetWmiGui(); - break; - - case SensorType.PerformanceCounterSensor: - SetPerformanceCounterGui(); - break; - - case SensorType.ProcessActiveSensor: - SetProcessGui(); - break; - - case SensorType.ServiceStateSensor: - SetServiceStateGui(); - break; - - case SensorType.NetworkSensors: - CbNetworkCard.DataSource = new BindingSource(_networkCards, null); - SetNetworkGui(); - break; - - case SensorType.InternalDeviceSensor: - CbNetworkCard.DataSource = new BindingSource(_internalSensors, null); - SetInternalSensorGui(); - break; - - case SensorType.PowershellSensor: - SetPowershellGui(); - break; - - case SensorType.WindowStateSensor: - SetWindowGui(); - break; - - case SensorType.LastActiveSensor: - SetLastActiveGui(); - break; + } + + /// + /// Change the UI depending on the selected type + /// + /// + private bool SetType(bool setDefaultValues = true) + { + if (LvSensors.SelectedItems.Count == 0) + { + // was the interface locked? + if (_interfaceLockedWrongType) UnlockWrongClient(); + return false; + } + + // find the sensor card + var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); + var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) + .Select(card => card.Value).FirstOrDefault(); + if (sensorCard == null) return false; + + // can the current client load this type? + if (_serviceMode && !sensorCard.SatelliteCompatible) + { + LockWrongClient(); + return false; + } + + if (!_serviceMode && !sensorCard.AgentCompatible) + { + LockWrongClient(); + return false; + } + + // was the interface locked? + if (_interfaceLockedWrongType) UnlockWrongClient(); + + // set default values + if (setDefaultValues) + { + TbName.Text = sensorCard.SensorType.GetSensorName(); + NumInterval.Text = sensorCard.RefreshTimer.ToString(); + _selectedSensorType = sensorCard.SensorType; + } + + SetCbIgnoreAvailability(false); + + TbSelectedType.Text = sensorCard.SensorType.ToString(); + TbDescription.Text = sensorCard.Description; + CbApplyRounding.Visible = false; + NumRound.Visible = false; + LblDigits.Visible = false; + + // process the interface + switch (sensorCard.SensorType) + { + case SensorType.NamedWindowSensor: + SetWindowGui(); + break; + + case SensorType.WmiQuerySensor: + SetWmiGui(); + break; + + case SensorType.PerformanceCounterSensor: + SetPerformanceCounterGui(); + break; + + case SensorType.ProcessActiveSensor: + SetProcessGui(); + break; + + case SensorType.ServiceStateSensor: + SetServiceStateGui(); + break; + + case SensorType.NetworkSensors: + CbNetworkCard.DataSource = new BindingSource(_networkCards, null); + SetNetworkGui(); + break; + + case SensorType.InternalDeviceSensor: + CbNetworkCard.DataSource = new BindingSource(_internalSensors, null); + SetInternalSensorGui(); + break; + + case SensorType.PowershellSensor: + SetPowershellGui(); + break; + + case SensorType.WindowStateSensor: + SetWindowGui(); + break; + + case SensorType.LastActiveSensor: + SetLastActiveGui(); + break; case SensorType.ScreenshotSensor: SetScreenshotGui(); break; - default: - SetEmptyGui(); - break; - } - - return true; - } - - /// - /// Change the UI to a 'named window' type - /// - private void SetWindowGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_WindowName; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - - BtnTest.Visible = false; - })); - } - - /// - /// Change the UI to a 'wmi query' type - /// - [SuppressMessage("ReSharper", "InvertIf")] - private void SetWmiGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Wmi; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - - LblSetting2.Text = Languages.SensorsMod_LblSetting2_Wmi; - LblSetting2.Visible = true; - TbSetting2.Visible = true; - - BtnTest.Text = Languages.SensorsMod_BtnTest_Wmi; - BtnTest.Visible = true; - - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a 'powershell command' type - /// - [SuppressMessage("ReSharper", "InvertIf")] - private void SetPowershellGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Powershell; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - - BtnTest.Text = Languages.SensorsMod_SensorsMod_BtnTest_Powershell; - BtnTest.Visible = true; - - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a 'performance counter' type - /// - [SuppressMessage("ReSharper", "InvertIf")] - private void SetPerformanceCounterGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Category; - LblSetting1.Visible = true; - TbSetting1.Text = string.Empty; - TbSetting1.Visible = true; - - LblSetting2.Text = Languages.SensorsMod_LblSetting2_Counter; - LblSetting2.Visible = true; - TbSetting2.Text = string.Empty; - TbSetting2.Visible = true; - - LblSetting3.Text = Languages.SensorsMod_LblSetting3_Instance; - LblSetting3.Visible = true; - TbSetting3.Text = string.Empty; - TbSetting3.Visible = true; - - BtnTest.Text = Languages.SensorsMod_BtnTest_PerformanceCounter; - BtnTest.Visible = true; - - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a 'process active' type - /// - private void SetProcessGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Process; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - })); - } - - /// - /// Change the UI to a 'service state' type - /// - private void SetServiceStateGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Service; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - })); - } - - /// - /// Change the UI to a 'network' type - /// - private void SetNetworkGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Network; - LblSetting1.Visible = true; - - CbNetworkCard.Visible = true; - })); - } - - /// - /// Change the UI to a 'internal sensor' type - /// - private void SetInternalSensorGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_InternalSensor; - LblSetting1.Visible = true; - - CbNetworkCard.Visible = true; - })); - } - - /// - /// Change the UI to a 'lastactive' type - /// - private void SetLastActiveGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding_LastActive; - LblDigits.Text = Languages.SensorsMod_LblSeconds; - NumRound.Text = LastActiveSensor.DefaultTimeWindow.ToString(); - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } + default: + SetEmptyGui(); + break; + } + + return true; + } + + /// + /// Change the UI to a 'named window' type + /// + private void SetWindowGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_WindowName; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + BtnTest.Visible = false; + })); + } + + /// + /// Change the UI to a 'wmi query' type + /// + [SuppressMessage("ReSharper", "InvertIf")] + private void SetWmiGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Wmi; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + LblSetting2.Text = Languages.SensorsMod_LblSetting2_Wmi; + LblSetting2.Visible = true; + TbSetting2.Visible = true; + + BtnTest.Text = Languages.SensorsMod_BtnTest_Wmi; + BtnTest.Visible = true; + + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + /// + /// Change the UI to a 'powershell command' type + /// + [SuppressMessage("ReSharper", "InvertIf")] + private void SetPowershellGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Powershell; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + BtnTest.Text = Languages.SensorsMod_SensorsMod_BtnTest_Powershell; + BtnTest.Visible = true; + + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + /// + /// Change the UI to a 'performance counter' type + /// + [SuppressMessage("ReSharper", "InvertIf")] + private void SetPerformanceCounterGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Category; + LblSetting1.Visible = true; + TbSetting1.Text = string.Empty; + TbSetting1.Visible = true; + + LblSetting2.Text = Languages.SensorsMod_LblSetting2_Counter; + LblSetting2.Visible = true; + TbSetting2.Text = string.Empty; + TbSetting2.Visible = true; + + LblSetting3.Text = Languages.SensorsMod_LblSetting3_Instance; + LblSetting3.Visible = true; + TbSetting3.Text = string.Empty; + TbSetting3.Visible = true; + + BtnTest.Text = Languages.SensorsMod_BtnTest_PerformanceCounter; + BtnTest.Visible = true; + + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + /// + /// Change the UI to a 'process active' type + /// + private void SetProcessGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Process; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + })); + } + + /// + /// Change the UI to a 'service state' type + /// + private void SetServiceStateGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Service; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + })); + } + + /// + /// Change the UI to a 'network' type + /// + private void SetNetworkGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Network; + LblSetting1.Visible = true; + + CbNetworkCard.Visible = true; + })); + } + + /// + /// Change the UI to a 'internal sensor' type + /// + private void SetInternalSensorGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_InternalSensor; + LblSetting1.Visible = true; + + CbNetworkCard.Visible = true; + })); + } + + /// + /// Change the UI to a 'lastactive' type + /// + private void SetLastActiveGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding_LastActive; + LblDigits.Text = Languages.SensorsMod_LblSeconds; + NumRound.Text = LastActiveSensor.DefaultTimeWindow.ToString(); + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } private void SetScreenshotGui() { @@ -554,264 +556,265 @@ private void SetScreenshotGui() /// Change the UI to a general type /// private void SetEmptyGui() - { - Invoke(new MethodInvoker(delegate - { - LblSetting1.Visible = false; - - CbNetworkCard.Visible = false; - - TbSetting1.Text = string.Empty; - TbSetting1.Visible = false; - - LblSetting2.Visible = false; - TbSetting2.Text = string.Empty; - TbSetting2.Visible = false; - - LblSetting3.Visible = false; - TbSetting3.Text = string.Empty; - TbSetting3.Visible = false; - - CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding; - CbApplyRounding.Checked = false; - CbApplyRounding.Visible = false; - NumRound.Visible = false; - LblDigits.Text = Languages.SensorsMod_LblDigits; - LblDigits.Visible = false; - - BtnTest.Visible = false; - })); - } - - private void LvSensors_SelectedIndexChanged(object sender, EventArgs e) - { - if (_loading) return; - - // set the ui to the selected type - SetType(); - - // set focus to the name field - ActiveControl = TbName; - if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; - } - - /// - /// Prepare the sensor for processing - /// - /// - /// - private void BtnStore_Click(object sender, EventArgs e) - { - if (LvSensors.SelectedItems.Count == 0) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // get and check type - var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); - var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) - .Select(card => card.Value).FirstOrDefault(); - - if (sensorCard == null) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox2, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // get and check name - var name = TbName.Text.Trim(); - if (string.IsNullOrEmpty(name)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox3, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbName; - return; - } - - if (CompatHelper.HassVersionEqualOrOver("2023.8") && name.Contains(SharedHelperFunctions.GetSafeConfiguredDeviceName())) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_DeviceNameInSensorName, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); - } - - // get friendly name - var friendlyName = string.IsNullOrEmpty(TbFriendlyName.Text.Trim()) ? name : TbFriendlyName.Text.Trim(); - - // name contains illegal chars? - var sanitized = SharedHelperFunctions.GetSafeValue(name); - if (sanitized != name) - { - var confirmSanitize = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_MessageBox_Sanitize, sanitized), Variables.MessageBoxTitle, MessageBoxButtons.OKCancel, MessageBoxIcon.Question); - if (confirmSanitize != DialogResult.OK) - { - ActiveControl = TbName; - return; - } - - TbName.Text = sanitized; - name = sanitized; - } - - // name already used? - if (!_serviceMode && Variables.SingleValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) - { - var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); - if (confirm != DialogResult.Yes) - { - ActiveControl = TbName; - return; - } - } - - if (!_serviceMode && Variables.MultiValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) - { - var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox5, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); - if (confirm != DialogResult.Yes) - { - ActiveControl = TbName; - return; - } - } - - // get and check update interval - var interval = (int)NumInterval.Value; - if (interval is < 1 or > 43200) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox6, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = NumInterval; - return; - } - - // get and check round value - var applyRounding = CbApplyRounding.Checked; - int? round = null; - if (applyRounding) - { - round = (int)NumRound.Value; - if (round is < 0 or > 20) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox12, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = NumRound; - return; - } - } - - // check and set optional settings - switch (sensorCard.SensorType) - { - case SensorType.NamedWindowSensor: - var window = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(window)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox7, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.WindowName = window; - break; - - case SensorType.WmiQuerySensor: - var query = TbSetting1.Text.Trim(); - var scope = TbSetting2.Text.Trim(); - - // test the query - if (string.IsNullOrEmpty(query)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox8, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - - // test the scope - if (!string.IsNullOrEmpty(scope)) - { - if (!HelperFunctions.CheckWmiScope(scope)) - { - var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), - Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); - - if (scopeQ != DialogResult.Yes) return; - } - } - - Sensor.Query = query; - Sensor.Scope = scope; - break; - - case SensorType.PerformanceCounterSensor: - var category = TbSetting1.Text.Trim(); - var counter = TbSetting2.Text.Trim(); - var instance = TbSetting3.Text.Trim(); - if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox9, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Category = category; - Sensor.Counter = counter; - Sensor.Instance = instance; - break; - - case SensorType.ProcessActiveSensor: - var process = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(process)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Query = process; - break; - - case SensorType.ServiceStateSensor: - var service = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(service)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox11, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Query = service; - break; - - case SensorType.NetworkSensors: - Sensor.Query = "*"; - if (CbNetworkCard.SelectedItem != null) - { - var item = (KeyValuePair)CbNetworkCard.SelectedItem; - Sensor.Query = item.Key; - } - break; - - case SensorType.InternalDeviceSensor: - if (CbNetworkCard.SelectedItem != null) - { - var item = (KeyValuePair)CbNetworkCard.SelectedItem; - if (item.Value == Languages.SensorsMod_None) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = CbNetworkCard; - return; - } - - Sensor.Query = item.Key; - } - break; - - case SensorType.PowershellSensor: - Sensor.Query = TbSetting1.Text.Trim(); - break; - - case SensorType.WindowStateSensor: - var windowprocess = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(windowprocess)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Query = windowprocess; - break; + { + Invoke(new MethodInvoker(delegate + { + LblSetting1.Visible = false; + + CbNetworkCard.Visible = false; + + TbSetting1.Text = string.Empty; + TbSetting1.Visible = false; + + LblSetting2.Visible = false; + TbSetting2.Text = string.Empty; + TbSetting2.Visible = false; + + LblSetting3.Visible = false; + TbSetting3.Text = string.Empty; + TbSetting3.Visible = false; + + CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding; + CbApplyRounding.Checked = false; + CbApplyRounding.Visible = false; + NumRound.Visible = false; + LblDigits.Text = Languages.SensorsMod_LblDigits; + LblDigits.Visible = false; + + BtnTest.Visible = false; + BtnAdvSettings.Visible = true; + })); + } + + private void LvSensors_SelectedIndexChanged(object sender, EventArgs e) + { + if (_loading) return; + + // set the ui to the selected type + SetType(); + + // set focus to the name field + ActiveControl = TbName; + if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; + } + + /// + /// Prepare the sensor for processing + /// + /// + /// + private void BtnStore_Click(object sender, EventArgs e) + { + if (LvSensors.SelectedItems.Count == 0) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // get and check type + var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); + var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) + .Select(card => card.Value).FirstOrDefault(); + + if (sensorCard == null) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox2, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // get and check name + var name = TbName.Text.Trim(); + if (string.IsNullOrEmpty(name)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox3, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbName; + return; + } + + if (CompatHelper.HassVersionEqualOrOver("2023.8") && name.Contains(SharedHelperFunctions.GetSafeConfiguredDeviceName())) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_DeviceNameInSensorName, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + // get friendly name + var friendlyName = string.IsNullOrEmpty(TbFriendlyName.Text.Trim()) ? name : TbFriendlyName.Text.Trim(); + + // name contains illegal chars? + var sanitized = SharedHelperFunctions.GetSafeValue(name); + if (sanitized != name) + { + var confirmSanitize = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_MessageBox_Sanitize, sanitized), Variables.MessageBoxTitle, MessageBoxButtons.OKCancel, MessageBoxIcon.Question); + if (confirmSanitize != DialogResult.OK) + { + ActiveControl = TbName; + return; + } + + TbName.Text = sanitized; + name = sanitized; + } + + // name already used? + if (!_serviceMode && Variables.SingleValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) + { + var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (confirm != DialogResult.Yes) + { + ActiveControl = TbName; + return; + } + } + + if (!_serviceMode && Variables.MultiValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) + { + var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox5, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (confirm != DialogResult.Yes) + { + ActiveControl = TbName; + return; + } + } + + // get and check update interval + var interval = (int)NumInterval.Value; + if (interval is < 1 or > 43200) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox6, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = NumInterval; + return; + } + + // get and check round value + var applyRounding = CbApplyRounding.Checked; + int? round = null; + if (applyRounding) + { + round = (int)NumRound.Value; + if (round is < 0 or > 20) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox12, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = NumRound; + return; + } + } + + // check and set optional settings + switch (sensorCard.SensorType) + { + case SensorType.NamedWindowSensor: + var window = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(window)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox7, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.WindowName = window; + break; + + case SensorType.WmiQuerySensor: + var query = TbSetting1.Text.Trim(); + var scope = TbSetting2.Text.Trim(); + + // test the query + if (string.IsNullOrEmpty(query)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox8, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + + // test the scope + if (!string.IsNullOrEmpty(scope)) + { + if (!HelperFunctions.CheckWmiScope(scope)) + { + var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + + if (scopeQ != DialogResult.Yes) return; + } + } + + Sensor.Query = query; + Sensor.Scope = scope; + break; + + case SensorType.PerformanceCounterSensor: + var category = TbSetting1.Text.Trim(); + var counter = TbSetting2.Text.Trim(); + var instance = TbSetting3.Text.Trim(); + if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox9, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Category = category; + Sensor.Counter = counter; + Sensor.Instance = instance; + break; + + case SensorType.ProcessActiveSensor: + var process = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(process)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = process; + break; + + case SensorType.ServiceStateSensor: + var service = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(service)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox11, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = service; + break; + + case SensorType.NetworkSensors: + Sensor.Query = "*"; + if (CbNetworkCard.SelectedItem != null) + { + var item = (KeyValuePair)CbNetworkCard.SelectedItem; + Sensor.Query = item.Key; + } + break; + + case SensorType.InternalDeviceSensor: + if (CbNetworkCard.SelectedItem != null) + { + var item = (KeyValuePair)CbNetworkCard.SelectedItem; + if (item.Value == Languages.SensorsMod_None) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = CbNetworkCard; + return; + } + + Sensor.Query = item.Key; + } + break; + + case SensorType.PowershellSensor: + Sensor.Query = TbSetting1.Text.Trim(); + break; + + case SensorType.WindowStateSensor: + var windowprocess = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(windowprocess)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = windowprocess; + break; case SensorType.ActiveDesktopSensor: if (!VirtualDesktopManager.Initialized) @@ -834,255 +837,272 @@ private void BtnStore_Click(object sender, EventArgs e) break; } - // set values - Sensor.Type = sensorCard.SensorType; - Sensor.EntityName = name; - Sensor.Name = friendlyName; - Sensor.UpdateInterval = interval; - Sensor.IgnoreAvailability = CbIgnoreAvailability.Checked; - Sensor.ApplyRounding = applyRounding; - Sensor.Round = round; - - // done - DialogResult = DialogResult.OK; - } - - private void TbDescription_LinkClicked(object sender, LinkClickedEventArgs e) - { - if (string.IsNullOrWhiteSpace(e.LinkText)) return; - if (!e.LinkText.ToLower().StartsWith("http")) return; - - HelperFunctions.LaunchUrl(e.LinkText); - } - - private void SensorsMod_ResizeEnd(object sender, EventArgs e) - { - if (Variables.ShuttingDown) return; - if (!IsHandleCreated) return; - if (IsDisposed) return; - - try - { - // hide the pesky horizontal scrollbar - ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); - - Refresh(); - } - catch - { - // best effort - } - } - - private void SensorsMod_KeyUp(object sender, KeyEventArgs e) - { - if (e.KeyCode != Keys.Escape) return; - Close(); - } - - private void SensorsMod_Layout(object sender, LayoutEventArgs e) - { - // hide the pesky horizontal scrollbar - ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); - } - - /// - /// Locks the interface if the selected entity can't be added to the current client - /// - private void LockWrongClient() - { - if (InvokeRequired) - { - Invoke(new MethodInvoker(LockWrongClient)); - return; - } - - _interfaceLockedWrongType = true; - - var requiredClient = _serviceMode ? "hass.agent" : "service"; - LblSpecificClient.Text = string.Format(Languages.SensorsMod_SpecificClient, requiredClient); - - LblSpecificClient.Visible = true; - - TbName.Enabled = false; - TbName.Text = string.Empty; - - TbFriendlyName.Enabled = false; - TbFriendlyName.Text = string.Empty; - - SetEmptyGui(); - - BtnStore.Enabled = false; - } - - /// - /// Unlocks the interface if the selected entity can be added to the current client - /// - private void UnlockWrongClient() - { - if (InvokeRequired) - { - Invoke(new MethodInvoker(UnlockWrongClient)); - return; - } - - _interfaceLockedWrongType = false; - - LblSpecificClient.Visible = false; - - TbName.Enabled = true; - TbFriendlyName.Enabled = true; - BtnStore.Enabled = true; - } - - private void BtnTest_Click(object sender, EventArgs e) - { - switch (_selectedSensorType) - { - case SensorType.WmiQuerySensor: - TestWmi(); - break; - - case SensorType.PerformanceCounterSensor: - TestPerformanceCounter(); - break; - - case SensorType.PowershellSensor: - TestPowershell(); - break; - } - } - - private async void TestWmi() - { - // prepare values - var query = TbSetting1.Text.Trim(); - var scope = TbSetting2.Text.Trim(); - var applyRounding = CbApplyRounding.Checked; - var round = (int)NumRound.Value; - - if (string.IsNullOrEmpty(query)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_TestWmi_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - ActiveControl = TbSetting1; - return; - } - - // test the scope - if (!string.IsNullOrEmpty(scope)) - { - if (!HelperFunctions.CheckWmiScope(scope)) - { - var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), - Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); - - if (scopeQ != DialogResult.Yes) return; - } - } - - BtnTest.Enabled = false; - - // execute the test - var result = await Task.Run(() => SensorTester.TestWmiQuery(query, scope, applyRounding, round)); - - BtnTest.Enabled = true; - - if (result.Succesful) - { - MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - // failed - var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (q != DialogResult.Yes) return; - - // open logs - HelperFunctions.OpenLocalFolder(Variables.LogPath); - } - - private async void TestPerformanceCounter() - { - // prepare values - var category = TbSetting1.Text.Trim(); - var counter = TbSetting2.Text.Trim(); - var instance = TbSetting3.Text.Trim(); - var applyRounding = CbApplyRounding.Checked; - var round = (int)NumRound.Value; - - if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_TestPerformanceCounter_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - return; - } - - BtnTest.Enabled = false; - - // execute the test - var result = await Task.Run((() => SensorTester.TestPerformanceCounter(category, counter, instance, applyRounding, round))); - - BtnTest.Enabled = true; - - if (result.Succesful) - { - MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - // failed - var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (q != DialogResult.Yes) return; - - // open logs - HelperFunctions.OpenLocalFolder(Variables.LogPath); - } - - private async void TestPowershell() - { - // prepare values - var command = TbSetting1.Text.Trim(); - var applyRounding = CbApplyRounding.Checked; - var round = (int)NumRound.Value; - - if (string.IsNullOrEmpty(command)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_TestPowershell_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - return; - } - - BtnTest.Enabled = false; - - // execute the test - var result = await Task.Run((() => SensorTester.TestPowershell(command, applyRounding, round))); - - BtnTest.Enabled = true; - - if (result.Succesful) - { - MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - // failed - var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (q != DialogResult.Yes) return; - - // open logs - HelperFunctions.OpenLocalFolder(Variables.LogPath); - } - - private void CbRdValue_CheckedChanged(object sender, EventArgs e) - { - if (NumRound.Visible == true) - { - NumRound.Visible = false; - LblDigits.Visible = false; - } - else - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - } - } + // set values + Sensor.Type = sensorCard.SensorType; + Sensor.EntityName = name; + Sensor.Name = friendlyName; + Sensor.UpdateInterval = interval; + Sensor.IgnoreAvailability = CbIgnoreAvailability.Checked; + Sensor.ApplyRounding = applyRounding; + Sensor.Round = round; + + // done + DialogResult = DialogResult.OK; + } + + /// + /// Show advanced settings for sensor + /// + /// + /// + private void BtnAdvSettings_Click(object sender, EventArgs e) + { + using var advancedSensorSettingsDialog = new AdvancedSensorSettings(Sensor.AdvancedSettings); + advancedSensorSettingsDialog.Opacity = 0; + + var ret = advancedSensorSettingsDialog.ShowDialog(); + if (ret != DialogResult.OK) + return; + + Sensor.AdvancedSettings = JsonConvert.SerializeObject(advancedSensorSettingsDialog.AdvancedSettings); + } + + private void TbDescription_LinkClicked(object sender, LinkClickedEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.LinkText)) return; + if (!e.LinkText.ToLower().StartsWith("http")) return; + + HelperFunctions.LaunchUrl(e.LinkText); + } + + private void SensorsMod_ResizeEnd(object sender, EventArgs e) + { + if (Variables.ShuttingDown) return; + if (!IsHandleCreated) return; + if (IsDisposed) return; + + try + { + // hide the pesky horizontal scrollbar + ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); + + Refresh(); + } + catch + { + // best effort + } + } + + private void SensorsMod_KeyUp(object sender, KeyEventArgs e) + { + if (e.KeyCode != Keys.Escape) return; + Close(); + } + + private void SensorsMod_Layout(object sender, LayoutEventArgs e) + { + // hide the pesky horizontal scrollbar + ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); + } + + /// + /// Locks the interface if the selected entity can't be added to the current client + /// + private void LockWrongClient() + { + if (InvokeRequired) + { + Invoke(new MethodInvoker(LockWrongClient)); + return; + } + + _interfaceLockedWrongType = true; + + var requiredClient = _serviceMode ? "hass.agent" : "service"; + LblSpecificClient.Text = string.Format(Languages.SensorsMod_SpecificClient, requiredClient); + + LblSpecificClient.Visible = true; + + TbName.Enabled = false; + TbName.Text = string.Empty; + + TbFriendlyName.Enabled = false; + TbFriendlyName.Text = string.Empty; + + SetEmptyGui(); + + BtnStore.Enabled = false; + } + + /// + /// Unlocks the interface if the selected entity can be added to the current client + /// + private void UnlockWrongClient() + { + if (InvokeRequired) + { + Invoke(new MethodInvoker(UnlockWrongClient)); + return; + } + + _interfaceLockedWrongType = false; + + LblSpecificClient.Visible = false; + + TbName.Enabled = true; + TbFriendlyName.Enabled = true; + BtnStore.Enabled = true; + } + + private void BtnTest_Click(object sender, EventArgs e) + { + switch (_selectedSensorType) + { + case SensorType.WmiQuerySensor: + TestWmi(); + break; + + case SensorType.PerformanceCounterSensor: + TestPerformanceCounter(); + break; + + case SensorType.PowershellSensor: + TestPowershell(); + break; + } + } + + private async void TestWmi() + { + // prepare values + var query = TbSetting1.Text.Trim(); + var scope = TbSetting2.Text.Trim(); + var applyRounding = CbApplyRounding.Checked; + var round = (int)NumRound.Value; + + if (string.IsNullOrEmpty(query)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_TestWmi_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + ActiveControl = TbSetting1; + return; + } + + // test the scope + if (!string.IsNullOrEmpty(scope)) + { + if (!HelperFunctions.CheckWmiScope(scope)) + { + var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + + if (scopeQ != DialogResult.Yes) return; + } + } + + BtnTest.Enabled = false; + + // execute the test + var result = await Task.Run(() => SensorTester.TestWmiQuery(query, scope, applyRounding, round)); + + BtnTest.Enabled = true; + + if (result.Succesful) + { + MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // failed + var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (q != DialogResult.Yes) return; + + // open logs + HelperFunctions.OpenLocalFolder(Variables.LogPath); + } + + private async void TestPerformanceCounter() + { + // prepare values + var category = TbSetting1.Text.Trim(); + var counter = TbSetting2.Text.Trim(); + var instance = TbSetting3.Text.Trim(); + var applyRounding = CbApplyRounding.Checked; + var round = (int)NumRound.Value; + + if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_TestPerformanceCounter_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + BtnTest.Enabled = false; + + // execute the test + var result = await Task.Run((() => SensorTester.TestPerformanceCounter(category, counter, instance, applyRounding, round))); + + BtnTest.Enabled = true; + + if (result.Succesful) + { + MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // failed + var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (q != DialogResult.Yes) return; + + // open logs + HelperFunctions.OpenLocalFolder(Variables.LogPath); + } + + private async void TestPowershell() + { + // prepare values + var command = TbSetting1.Text.Trim(); + var applyRounding = CbApplyRounding.Checked; + var round = (int)NumRound.Value; + + if (string.IsNullOrEmpty(command)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_TestPowershell_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + BtnTest.Enabled = false; + + // execute the test + var result = await Task.Run((() => SensorTester.TestPowershell(command, applyRounding, round))); + + BtnTest.Enabled = true; + + if (result.Succesful) + { + MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // failed + var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (q != DialogResult.Yes) return; + + // open logs + HelperFunctions.OpenLocalFolder(Variables.LogPath); + } + + private void CbRdValue_CheckedChanged(object sender, EventArgs e) + { + if (NumRound.Visible == true) + { + NumRound.Visible = false; + LblDigits.Visible = false; + } + else + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + } + } } diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index fb9c0c56..4d4b5c16 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -128,6 +128,9 @@ + + Form + @@ -1613,6 +1616,24 @@ QuickActionsMod.resx + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + SensorsConfig.resx diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs index a5b2c14a..8bc87fae 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs @@ -26,7 +26,7 @@ public class ActiveDesktopSensor : AbstractSingleValueSensor private string _desktopName = string.Empty; private string _attributes = string.Empty; - public ActiveDesktopSensor(int? updateInterval = null, string entityName = _defaultName, string name = _defaultName, string id = default) : base(entityName ?? _defaultName, name ?? null, updateInterval ?? 15, id) + public ActiveDesktopSensor(int? updateInterval = null, string entityName = _defaultName, string name = _defaultName, string id = default, string advancedSettings = default) : base(entityName ?? _defaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { UseAttributes = true; } diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs index 99ecb2f1..de154e55 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs @@ -14,7 +14,7 @@ public class BluetoothDevicesSensor : AbstractSingleValueSensor private const string DefaultName = "bluetoothdevices"; private string _attributes = "{}"; - public BluetoothDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public BluetoothDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { UseAttributes = true; } diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs index 04ecf4d5..f63f17d5 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs @@ -14,7 +14,7 @@ public class BluetoothLeDevicesSensor : AbstractSingleValueSensor private const string DefaultName = "bluetoothledevices"; private string _attributes = "{}"; - public BluetoothLeDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public BluetoothLeDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { UseAttributes = true; diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs index 96138fd4..78bc6d83 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs @@ -9,7 +9,7 @@ namespace HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue public class GeoLocationSensor : AbstractSingleValueSensor { private const string DefaultName = "geolocation"; - public GeoLocationSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) { } + public GeoLocationSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs index 2c261ca7..a9197ac9 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs @@ -17,7 +17,7 @@ public class InternalDeviceSensor : AbstractSingleValueSensor private readonly IInternalDeviceSensor _internalDeviceSensor; - public InternalDeviceSensor(string sensorType, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public InternalDeviceSensor(string sensorType, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { SensorType = Enum.Parse(sensorType); _internalDeviceSensor = InternalDeviceSensorsManager.AvailableSensors.First(s => s.Type == SensorType); diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs index d4564bb1..ae984599 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs @@ -10,7 +10,7 @@ namespace HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue public class MonitorPowerStateSensor : AbstractSingleValueSensor { private const string DefaultName = "monitorpowerstate"; - public MonitorPowerStateSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) { } + public MonitorPowerStateSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index 6aa0e753..04fc72c3 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -163,6 +163,51 @@ internal static string About_Title { } } + /// + /// Looks up a localized string similar to Device class. + /// + internal static string AdvancedSensorConfig_LblDeviceClass { + get { + return ResourceManager.GetString("AdvancedSensorConfig_LblDeviceClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to State class. + /// + internal static string AdvancedSensorConfig_LblStateClass { + get { + return ResourceManager.GetString("AdvancedSensorConfig_LblStateClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unit of measurement. + /// + internal static string AdvancedSensorConfig_LblUnitOfMeasurement { + get { + return ResourceManager.GetString("AdvancedSensorConfig_LblUnitOfMeasurement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Advanced Settings. + /// + internal static string AdvancedSensorConfig_Title { + get { + return ResourceManager.GetString("AdvancedSensorConfig_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning, adding following settings (overrides) may break the sensor, use with caution!. + /// + internal static string AdvancedSensorConfig_Warning { + get { + return ResourceManager.GetString("AdvancedSensorConfig_Warning", resourceCulture); + } + } + /// /// Looks up a localized string similar to Button. /// @@ -6251,6 +6296,15 @@ internal static string SensorsMod_All { } } + /// + /// Looks up a localized string similar to Ad&vanced Settings. + /// + internal static string SensorsMod_BtnAdvancedSettings { + get { + return ResourceManager.GetString("SensorsMod_BtnAdvancedSettings", resourceCulture); + } + } + /// /// Looks up a localized string similar to &Store Sensor. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index 667b8615..e8bbdff5 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3498,4 +3498,13 @@ Hinweis: Bei Verwendung im Satellitendienst werden keine Userspace-Anwendungen e NFC + + Erweiterte Einstellungen + + + Erwe&iterte Einstellungen + + + Achtung: Das Hinzufügen der folgenden Einstellungen (Überschreibungen) kann zur Beschädigung des Sensors führen. Gehen Sie daher vorsichtig vor! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 9ad44e64..23214f52 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3375,4 +3375,22 @@ Note: if used in the satellite service, it won't detect userspace applications.< NFC + + Ad&vanced Settings + + + Warning, adding following settings (overrides) may break the sensor, use with caution! + + + Device class + + + Unit of measurement + + + State class + + + Advanced Settings + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index f4714e88..d85d8775 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3374,4 +3374,13 @@ Nota: si se usa en el servicio satelital, no detectará aplicaciones del espacio NFC + + Ajustes avanzados + + + A&justes avanzados + + + Advertencia, agregar las siguientes configuraciones (anulaciones) puede dañar el sensor, ¡úselo con precaución! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index a3c2f2f1..ae38cacb 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3407,4 +3407,13 @@ Remarque : s'il est utilisé dans le service satellite, il ne détectera pas les NFC + + Réglages avancés + + + Réglages a&vancés + + + Attention, l'ajout des paramètres suivants (remplacements) peut casser le capteur, à utiliser avec prudence ! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index e87c832b..b67fab9a 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3395,4 +3395,13 @@ Opmerking: indien gebruikt in de satellietdienst, zal het geen gebruikersruimtet NFC + + Geavanceerde instellingen + + + Gea&vanceerde instellingen + + + Waarschuwing: het toevoegen van de volgende instellingen (overschrijvingen) kan de sensor kapot maken, wees voorzichtig! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index a209df91..6dd51e93 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3484,4 +3484,13 @@ Uwaga: jeśli jest używany w usłudze satelitarnej, nie wykryje aplikacji przes NFC + + Ustawienia zaawansowane + + + Ustaw&ienia zaawansowane + + + Uwaga, dodanie następujących ustawień (nadpisań) może spowodować uszkodzenie czujnika, zachowaj ostrożność! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 04f553a7..0d4fe746 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3420,4 +3420,13 @@ Nota: se usado no serviço de satélite, não detectará aplicações no espaço NFC + + Configurações avançadas + + + Configurações a&vançadas + + + Atenção, adicionar as seguintes configurações (substituições) pode quebrar o sensor, use com cuidado! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index a91866c5..c79a98a1 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3387,4 +3387,22 @@ Requires audio device name as a payload. NFC + + Ad&vanced Settings + + + Warning, adding following settings (overrides) may break the sensor, use with caution! + + + Device class + + + Unit of measurement + + + State class + + + Advanced Settings + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 2e7a6713..985e25a2 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3443,4 +3443,13 @@ Home Assistant. NFC + + Расширенные настройки + + + Рас&ширенные настройки + + + Внимание! Добавление следующих настроек (переопределений) может привести к поломке датчика, используйте с осторожностью! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 57350977..bb2dfbf6 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3523,4 +3523,13 @@ Opomba: če se uporablja v satelitski storitvi, ne bo zaznal aplikacij uporabni NFC + + Napredne nastavitve + + + Napredne nasta&vitve + + + Opozorilo, dodajanje naslednjih nastavitev (preglasitev) lahko pokvari senzor, uporabljajte previdno! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index 21bd0270..4c44f726 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2990,4 +2990,13 @@ Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılam NFC + + Gelişmiş Ayarlar + + + Ge&lişmiş Ayarlar + + + Uyarı, aşağıdaki ayarların (geçersiz kılmaların) eklenmesi sensörü bozabilir, dikkatli kullanın! + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs index 8c908fd2..bbcd34dc 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs @@ -104,103 +104,103 @@ internal static AbstractSingleValueSensor ConvertConfiguredToAbstractSingleValue switch (sensor.Type) { case SensorType.UserNotificationStateSensor: - abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.DummySensor: - abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentClockSpeedSensor: - abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.CpuLoadSensor: - abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.MemoryUsageSensor: - abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.ActiveWindowSensor: - abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ActiveDesktopSensor: - abstractSensor = new ActiveDesktopSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ActiveDesktopSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.NamedWindowSensor: - abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastActiveSensor: - abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastSystemStateChangeSensor: - abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastBootSensor: - abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamActiveSensor: - abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneActiveSensor: - abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.SessionStateSensor: - abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentVolumeSensor: - abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuLoadSensor: - abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuTemperatureSensor: - abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WmiQuerySensor: - abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PerformanceCounterSensor: - abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ProcessActiveSensor: - abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ServiceStateSensor: - abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUsersSensor: - abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUserSensor: - abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GeoLocationSensor: - abstractSensor = new GeoLocationSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GeoLocationSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MonitorPowerStateSensor: - abstractSensor = new MonitorPowerStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MonitorPowerStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PowershellSensor: - abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WindowStateSensor: - abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneProcessSensor: - abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamProcessSensor: - abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.BluetoothDevicesSensor: - abstractSensor = new BluetoothDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new BluetoothDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.BluetoothLeDevicesSensor: - abstractSensor = new BluetoothLeDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new BluetoothLeDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.InternalDeviceSensor: - abstractSensor = new HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ScreenshotSensor: - abstractSensor = new ScreenshotSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ScreenshotSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; default: Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured single-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); @@ -277,7 +277,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Scope = wmiSensor.Scope, Query = wmiSensor.Query, ApplyRounding = wmiSensor.ApplyRounding, - Round= wmiSensor.Round + Round= wmiSensor.Round, + AdvancedSettings = wmiSensor.AdvancedSettings }; } @@ -292,7 +293,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Type = type, UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, IgnoreAvailability = namedWindowSensor.IgnoreAvailability, - WindowName = namedWindowSensor.WindowName + WindowName = namedWindowSensor.WindowName, + AdvancedSettings = namedWindowSensor.AdvancedSettings }; } @@ -311,7 +313,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Counter = performanceCounterSensor.CounterName, Instance = performanceCounterSensor.InstanceName, ApplyRounding = performanceCounterSensor.ApplyRounding, - Round = performanceCounterSensor.Round + Round = performanceCounterSensor.Round, + AdvancedSettings = performanceCounterSensor.AdvancedSettings }; } @@ -326,7 +329,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Type = type, UpdateInterval = processActiveSensor.UpdateIntervalSeconds, IgnoreAvailability = processActiveSensor.IgnoreAvailability, - Query = processActiveSensor.ProcessName + Query = processActiveSensor.ProcessName, + AdvancedSettings = processActiveSensor.AdvancedSettings }; } @@ -341,7 +345,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Type = type, UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, IgnoreAvailability = serviceStateSensor.IgnoreAvailability, - Query = serviceStateSensor.ServiceName + Query = serviceStateSensor.ServiceName, + AdvancedSettings = serviceStateSensor.AdvancedSettings }; } @@ -358,7 +363,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract IgnoreAvailability = powershellSensor.IgnoreAvailability, Query = powershellSensor.Command, ApplyRounding = powershellSensor.ApplyRounding, - Round = powershellSensor.Round + Round = powershellSensor.Round, + AdvancedSettings = powershellSensor.AdvancedSettings }; } @@ -374,8 +380,9 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract UpdateInterval = lastActiveSensor.UpdateIntervalSeconds, IgnoreAvailability = lastActiveSensor.IgnoreAvailability, ApplyRounding = lastActiveSensor.ApplyRounding, - Round = lastActiveSensor.Round - }; + Round = lastActiveSensor.Round, + AdvancedSettings = lastActiveSensor.AdvancedSettings + }; } case WindowStateSensor windowStateSensor: @@ -389,7 +396,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Type = type, UpdateInterval = windowStateSensor.UpdateIntervalSeconds, IgnoreAvailability = windowStateSensor.IgnoreAvailability, - Query = windowStateSensor.ProcessName + Query = windowStateSensor.ProcessName, + AdvancedSettings = windowStateSensor.AdvancedSettings }; } @@ -404,7 +412,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Type = type, UpdateInterval = internalDeviceSensor.UpdateIntervalSeconds, IgnoreAvailability = internalDeviceSensor.IgnoreAvailability, - Query = internalDeviceSensor.SensorType.ToString() + Query = internalDeviceSensor.SensorType.ToString(), + AdvancedSettings = internalDeviceSensor.AdvancedSettings }; } @@ -419,7 +428,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Type = type, UpdateInterval = screenshotSensor.UpdateIntervalSeconds, IgnoreAvailability = screenshotSensor.IgnoreAvailability, - Query = screenshotSensor.ScreenIndex.ToString() + Query = screenshotSensor.ScreenIndex.ToString(), + AdvancedSettings = screenshotSensor.AdvancedSettings }; } @@ -433,7 +443,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = sensor.Name, Type = type, UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability + IgnoreAvailability = sensor.IgnoreAvailability, + AdvancedSettings = sensor.AdvancedSettings }; } } From 255d209cd2dc094417479cac9119186aab938ffa Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 3 May 2024 21:07:13 +0200 Subject: [PATCH 23/45] Fix: adjust sensor state_class/device_class (#82) This PR adjusts state class and device class of various sensors. Previously sensors like CPU load sensor would report their value as "string" - each update would be visible as a string, not number. After this change, Home Assistant statistics and other features will be available for the corrected sensors. --- .../GeneralSensors/MultiValue/AudioSensors.cs | 10 +++++----- .../GeneralSensors/MultiValue/BatterySensors.cs | 6 +++--- .../MultiValue/DataTypes/DataTypeDoubleSensor.cs | 15 +++++++++++---- .../MultiValue/DataTypes/DataTypeIntSensor.cs | 15 +++++++++++---- .../GeneralSensors/MultiValue/DisplaySensors.cs | 2 +- .../GeneralSensors/MultiValue/NetworkSensors.cs | 2 +- .../GeneralSensors/MultiValue/StorageSensors.cs | 2 +- .../MultiValue/WindowsUpdatesSensors.cs | 8 ++++---- .../SingleValue/CurrentVolumeSensor.cs | 1 + .../GeneralSensors/SingleValue/DummySensor.cs | 1 + .../GeneralSensors/SingleValue/GpuLoadSensor.cs | 3 ++- .../SingleValue/GpuTemperatureSensor.cs | 1 + .../SingleValue/MicrophoneProcessSensor.cs | 1 + .../SingleValue/ProcessActiveSensor.cs | 1 + .../SingleValue/WebcamProcessSensor.cs | 1 + .../SingleValue/CpuLoadSensor.cs | 1 + .../Sensors/PerformanceCounterSensor.cs | 1 + .../SingleValue/CurrentClockSpeedSensor.cs | 2 ++ .../WmiSensors/SingleValue/MemoryUsageSensor.cs | 1 + .../GeneralSensors/MultiValue/PrintersSensors.cs | 6 +++--- .../SingleValue/BluetoothDevicesSensor.cs | 1 + .../SingleValue/BluetoothLeDevicesSensor.cs | 1 + .../SingleValue/InternalDeviceSensor.cs | 7 +++++++ .../Managers/DeviceSensors/AccelerometerSensor.cs | 5 +++++ .../Managers/DeviceSensors/ActivitySensor.cs | 5 +++++ .../Managers/DeviceSensors/AltimeterSensor.cs | 5 +++++ .../Managers/DeviceSensors/BarometerSensor.cs | 5 +++++ .../Managers/DeviceSensors/CompassSensor.cs | 5 +++++ .../Managers/DeviceSensors/GyrometerSensor.cs | 5 +++++ .../Managers/DeviceSensors/HingeAngleSensor.cs | 5 +++++ .../DeviceSensors/IInternalDeviceSensor.cs | 3 +++ .../Managers/DeviceSensors/InclinometerSensor.cs | 5 +++++ .../Managers/DeviceSensors/LightSensor.cs | 5 +++++ .../Managers/DeviceSensors/MagnetometerSensor.cs | 5 +++++ .../Managers/DeviceSensors/OrientationSensor.cs | 5 +++++ .../Managers/DeviceSensors/PedometerSensor.cs | 5 +++++ .../Managers/DeviceSensors/ProximitySensor.cs | 5 +++++ .../DeviceSensors/SimpleOrientationSensor.cs | 5 +++++ 38 files changed, 135 insertions(+), 27 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs index 4739e906..5df24f5d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs @@ -76,7 +76,7 @@ private void HandleAudioOutputSensors(string parentSensorSafeName, string device var masterVolume = Convert.ToInt32(Math.Round(audioDevice.AudioEndpointVolume?.MasterVolumeLevelScalar * 100 ?? 0, 0)); var defaultDeviceVolumeEntityName = $"{parentSensorSafeName}_default_device_volume"; var defaultDeviceVolumeId = $"{Id}_default_device_volume"; - var defaultDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultDeviceVolumeEntityName, $"Default Device Volume", defaultDeviceVolumeId, string.Empty, "mdi:speaker", string.Empty, EntityName); + var defaultDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultDeviceVolumeEntityName, $"Default Device Volume", defaultDeviceVolumeId, string.Empty, "measurement", "mdi:speaker", string.Empty, EntityName); defaultDeviceVolumeSensor.SetState(masterVolume); AddUpdateSensor(defaultDeviceVolumeId, defaultDeviceVolumeSensor); @@ -98,7 +98,7 @@ private void HandleAudioOutputSensors(string parentSensorSafeName, string device var sessionsEntityName = $"{parentSensorSafeName}_sessions"; var sessionsId = $"{Id}_sessions"; - var sessionsSensor = new DataTypeIntSensor(_updateInterval, sessionsEntityName, $"Audio Sessions", sessionsId, string.Empty, "mdi:music-box-multiple-outline", string.Empty, EntityName, true); + var sessionsSensor = new DataTypeIntSensor(_updateInterval, sessionsEntityName, $"Audio Sessions", sessionsId, string.Empty, "measurement", "mdi:music-box-multiple-outline", string.Empty, EntityName, true); sessionsSensor.SetState(sessionInfos.Count); sessionsSensor.SetAttributes( JsonConvert.SerializeObject(new @@ -111,7 +111,7 @@ private void HandleAudioOutputSensors(string parentSensorSafeName, string device var audioOutputDevices = GetAudioOutputDevices(); var audioOutputDevicesEntityName = $"{parentSensorSafeName}_output_devices"; var audioOutputDevicesId = $"{Id}_output_devices"; - var audioOutputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioOutputDevicesEntityName, $"Audio Output Devices", audioOutputDevicesId, string.Empty, "mdi:music-box-multiple-outline", string.Empty, EntityName, true); + var audioOutputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioOutputDevicesEntityName, $"Audio Output Devices", audioOutputDevicesId, string.Empty, "measurement", "mdi:music-box-multiple-outline", string.Empty, EntityName, true); audioOutputDevicesSensor.SetState(audioOutputDevices.Count); audioOutputDevicesSensor.SetAttributes( JsonConvert.SerializeObject(new @@ -148,14 +148,14 @@ private void HandleAudioInputSensors(string parentSensorSafeName, string deviceN var inputVolume = (int)GetDefaultInputDevicePeakVolume(inputDevice); var defaultInputDeviceVolumeEntityName = $"{parentSensorSafeName}_default_input_device_volume"; var defaultInputDeviceVolumeId = $"{Id}_default_input_device_volume"; - var defaultInputDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultInputDeviceVolumeEntityName, $"Default Input Device Volume", defaultInputDeviceVolumeId, string.Empty, "mdi:microphone", string.Empty, EntityName); + var defaultInputDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultInputDeviceVolumeEntityName, $"Default Input Device Volume", defaultInputDeviceVolumeId, string.Empty, "measurement", "mdi:microphone", string.Empty, EntityName); defaultInputDeviceVolumeSensor.SetState(inputVolume); AddUpdateSensor(defaultInputDeviceVolumeId, defaultInputDeviceVolumeSensor); var audioInputDevices = GetAudioInputDevices(); var audioInputDevicesEntityName = $"{parentSensorSafeName}_input_devices"; var audioInputDevicesId = $"{Id}_input_devices"; - var audioInputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioInputDevicesEntityName, $"Audio Input Devices", audioInputDevicesId, string.Empty, "mdi:microphone", string.Empty, EntityName, true); + var audioInputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioInputDevicesEntityName, $"Audio Input Devices", audioInputDevicesId, string.Empty, "measurement", "mdi:microphone", string.Empty, EntityName, true); audioInputDevicesSensor.SetState(audioInputDevices.Count); audioInputDevicesSensor.SetAttributes( JsonConvert.SerializeObject(new diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs index 23835adb..708e2dd2 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs @@ -52,14 +52,14 @@ public sealed override void UpdateSensorValues() var fullChargeLifetimeEntityName = $"{parentSensorSafeName}_full_charge_lifetime"; var fullChargeLifetimeId = $"{Id}_full_charge_lifetime"; - var fullChargeLifetimeSensor = new DataTypeIntSensor(_updateInterval, fullChargeLifetimeEntityName, "Full Charge Lifetime", fullChargeLifetimeId, string.Empty, "mdi:battery-high", string.Empty, EntityName); + var fullChargeLifetimeSensor = new DataTypeIntSensor(_updateInterval, fullChargeLifetimeEntityName, "Full Charge Lifetime", fullChargeLifetimeId, string.Empty, "measurement", "mdi:battery-high", string.Empty, EntityName); fullChargeLifetimeSensor.SetState(fullChargeLifetimeMinutes); AddUpdateSensor(fullChargeLifetimeId, fullChargeLifetimeSensor); var chargeRemainingPercentage = Convert.ToInt32(powerStatus.BatteryLifePercent * 100); var chargeRemainingPercentageEntityName = $"{parentSensorSafeName}_charge_remaining_percentage"; var chargeRemainingPercentageId = $"{Id}_charge_remaining_percentage"; - var chargeRemainingPercentageSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingPercentageEntityName, "Charge Remaining Percentage", chargeRemainingPercentageId, string.Empty, "mdi:battery-high", "%", EntityName); + var chargeRemainingPercentageSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingPercentageEntityName, "Charge Remaining Percentage", chargeRemainingPercentageId, string.Empty, "measurement", "mdi:battery-high", "%", EntityName); chargeRemainingPercentageSensor.SetState(chargeRemainingPercentage); AddUpdateSensor(chargeRemainingPercentageId, chargeRemainingPercentageSensor); @@ -69,7 +69,7 @@ public sealed override void UpdateSensorValues() var chargeRemainingMinutesEntityName = $"{parentSensorSafeName}_charge_remaining"; var chargeRemainingMinutesId = $"{Id}_charge_remaining"; - var chargeRemainingMinutesSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingMinutesEntityName, "Charge Remaining", chargeRemainingMinutesId, string.Empty, "mdi:battery-high", string.Empty, EntityName); + var chargeRemainingMinutesSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingMinutesEntityName, "Charge Remaining", chargeRemainingMinutesId, string.Empty, "measurement", "mdi:battery-high", string.Empty, EntityName); chargeRemainingMinutesSensor.SetState(chargeRemainingMinutes); AddUpdateSensor(chargeRemainingMinutesId, chargeRemainingMinutesSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs index fd80e103..b0fbc741 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs @@ -10,17 +10,19 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.Data public class DataTypeDoubleSensor : AbstractSingleValueSensor { private readonly string _deviceClass; + private readonly string _stateClass; private readonly string _unitOfMeasurement; private readonly string _icon; private double _value = 0d; private string _attributes = string.Empty; - public DataTypeDoubleSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) + public DataTypeDoubleSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string stateClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) { TopicName = multiValueSensorName; _deviceClass = deviceClass; + _stateClass = stateClass; _unitOfMeasurement = unitOfMeasurement; _icon = icon; @@ -60,9 +62,14 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() if (UseAttributes) model.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{TopicName}/{ObjectId}/attributes"; - if (!string.IsNullOrWhiteSpace(_deviceClass)) model.Device_class = _deviceClass; - if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) model.Unit_of_measurement = _unitOfMeasurement; - if (!string.IsNullOrWhiteSpace(_icon)) model.Icon = _icon; + if (!string.IsNullOrWhiteSpace(_deviceClass)) + model.Device_class = _deviceClass; + if (!string.IsNullOrWhiteSpace(_stateClass)) + model.State_class = _stateClass; + if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) + model.Unit_of_measurement = _unitOfMeasurement; + if (!string.IsNullOrWhiteSpace(_icon)) + model.Icon = _icon; return SetAutoDiscoveryConfigModel(model); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs index 59d08581..0a9ec4e6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs @@ -9,17 +9,19 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.Data public class DataTypeIntSensor : AbstractSingleValueSensor { private readonly string _deviceClass; + private readonly string _stateClass; private readonly string _unitOfMeasurement; private readonly string _icon; private int _value = 0; private string _attributes = string.Empty; - public DataTypeIntSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) + public DataTypeIntSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string stateClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) { TopicName = multiValueSensorName; _deviceClass = deviceClass; + _stateClass = stateClass; _unitOfMeasurement = unitOfMeasurement; _icon = icon; @@ -59,9 +61,14 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() if (UseAttributes) model.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{TopicName}/{ObjectId}/attributes"; - if (!string.IsNullOrWhiteSpace(_deviceClass)) model.Device_class = _deviceClass; - if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) model.Unit_of_measurement = _unitOfMeasurement; - if (!string.IsNullOrWhiteSpace(_icon)) model.Icon = _icon; + if (!string.IsNullOrWhiteSpace(_deviceClass)) + model.Device_class = _deviceClass; + if (!string.IsNullOrWhiteSpace(_stateClass)) + model.State_class = _stateClass; + if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) + model.Unit_of_measurement = _unitOfMeasurement; + if (!string.IsNullOrWhiteSpace(_icon)) + model.Icon = _icon; return SetAutoDiscoveryConfigModel(model); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs index d60c31bd..fd454cb9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs @@ -48,7 +48,7 @@ public sealed override void UpdateSensorValues() var displayCount = displays.Length; var displayCountEntityName = $"{parentSensorSafeName}_display_count"; var displayCountId = $"{Id}_display_count"; - var displayCountSensor = new DataTypeIntSensor(_updateInterval, displayCountEntityName, "Display Count", displayCountId, string.Empty, "mdi:monitor", string.Empty, EntityName); + var displayCountSensor = new DataTypeIntSensor(_updateInterval, displayCountEntityName, "Display Count", displayCountId, string.Empty, "measurement", "mdi:monitor", string.Empty, EntityName); displayCountSensor.SetState(displayCount); AddUpdateSensor(displayCountId, displayCountSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs index fdc091e1..e43755b6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs @@ -139,7 +139,7 @@ public override sealed void UpdateSensorValues() var nicCountEntityName = $"{parentSensorSafeName}_total_network_card_count"; var nicCountId = $"{Id}_total_network_card_count"; - var nicCountSensor = new DataTypeIntSensor(_updateInterval, nicCountEntityName, "Network Card Count", nicCountId, string.Empty, "mdi:lan", string.Empty, EntityName); + var nicCountSensor = new DataTypeIntSensor(_updateInterval, nicCountEntityName, "Network Card Count", nicCountId, string.Empty, "measurement", "mdi:lan", string.Empty, EntityName); nicCountSensor.SetState(nicCount); AddUpdateSensor(nicCountId, nicCountSensor); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs index 9d4c682d..dd65447c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs @@ -117,7 +117,7 @@ public override sealed void UpdateSensorValues() var driveCountEntityName = $"{parentSensorSafeName}_total_disk_count"; var driveCountId = $"{Id}_total_disk_count"; - var driveCountSensor = new DataTypeIntSensor(_updateInterval, driveCountEntityName, "Total Disk Count", driveCountId, string.Empty, "mdi:harddisk", string.Empty, EntityName); + var driveCountSensor = new DataTypeIntSensor(_updateInterval, driveCountEntityName, "Total Disk Count", driveCountId, string.Empty, "measurement", "mdi:harddisk", string.Empty, EntityName); driveCountSensor.SetState(driveCount); AddUpdateSensor(driveCountId, driveCountSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs index 3a3af5cb..5ac1797f 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs @@ -41,27 +41,27 @@ public sealed override void UpdateSensorValues() var driverUpdateCount = driverUpdates.Count; var driverUpdateCountId = $"{parentSensorSafeName}_driver_updates_pending"; - var driverUpdateCountSensor = new DataTypeIntSensor(_updateInterval, driverUpdateCountId, "Driver Updates Pending", driverUpdateCountId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName); + var driverUpdateCountSensor = new DataTypeIntSensor(_updateInterval, driverUpdateCountId, "Driver Updates Pending", driverUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); driverUpdateCountSensor.SetState(driverUpdateCount); AddUpdateSensor(driverUpdateCountId, driverUpdateCountSensor); var softwareUpdateCount = softwareUpdates.Count; var softwareUpdateCountId = $"{parentSensorSafeName}_software_updates_pending"; - var softwareUpdateCountSensor = new DataTypeIntSensor(_updateInterval, softwareUpdateCountId, "Software Updates Pending", softwareUpdateCountId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName); + var softwareUpdateCountSensor = new DataTypeIntSensor(_updateInterval, softwareUpdateCountId, "Software Updates Pending", softwareUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); softwareUpdateCountSensor.SetState(softwareUpdateCount); AddUpdateSensor(softwareUpdateCountId, softwareUpdateCountSensor); var driverUpdatesList = new WindowsUpdateInfoCollection(driverUpdates); var driverUpdatesStr = JsonConvert.SerializeObject(driverUpdatesList, Formatting.Indented); var driverUpdatesId = $"{parentSensorSafeName}_driver_updates"; - var driverUpdatesSensor = new DataTypeIntSensor(_updateInterval, driverUpdatesId, "Available Driver Updates", driverUpdatesId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName, true); + var driverUpdatesSensor = new DataTypeIntSensor(_updateInterval, driverUpdatesId, "Available Driver Updates", driverUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); driverUpdatesSensor.SetState(driverUpdates.Count); driverUpdatesSensor.SetAttributes(driverUpdatesStr); AddUpdateSensor(driverUpdatesId, driverUpdatesSensor); var softwareUpdatesStr = JsonConvert.SerializeObject(new WindowsUpdateInfoCollection(softwareUpdates), Formatting.Indented); var softwareUpdatesId = $"{parentSensorSafeName}_software_updates"; - var softwareUpdatesSensor = new DataTypeIntSensor(_updateInterval, softwareUpdatesId, "Available Software Updates", softwareUpdatesId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName, true); + var softwareUpdatesSensor = new DataTypeIntSensor(_updateInterval, softwareUpdatesId, "Available Software Updates", softwareUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); softwareUpdatesSensor.SetState(softwareUpdates.Count); softwareUpdatesSensor.SetAttributes(softwareUpdatesStr); AddUpdateSensor(softwareUpdatesId, softwareUpdatesSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs index b327c891..7ad33701 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs @@ -30,6 +30,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Icon = "mdi:volume-medium", Unit_of_measurement = "%", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs index 2e298eab..45afb306 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs @@ -25,6 +25,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs index 16d1ce0b..e4b4e92a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs @@ -40,7 +40,8 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Unit_of_measurement = "%", - Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" + State_class = "measurement", + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs index 96a209ea..8204c3d9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs @@ -39,6 +39,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Device_class = "temperature", Unit_of_measurement = "°C", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs index 0b57d35c..c7b89615 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs @@ -46,6 +46,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability", Icon = "mdi:microphone", Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs index 594e2c5d..09d51781 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs @@ -36,6 +36,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:file-eye-outline", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability" }); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs index c698f90c..8dbfbccd 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs @@ -41,6 +41,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability", Icon = "mdi:webcam" }; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs index d55b4664..245c143e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs @@ -27,6 +27,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:chart-areaspline", Unit_of_measurement = "%", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs index 2aa2f9d1..cde16828 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs @@ -54,6 +54,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{EntityName}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs index 82e34202..23770359 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs @@ -34,6 +34,8 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", + Device_class = "frequency", Icon = "mdi:speedometer", Unit_of_measurement = "MHz", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs index 77b61ba6..eaf79098 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs @@ -27,6 +27,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:memory", Unit_of_measurement = "%", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs index 7e60bc70..44c6d085 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs @@ -51,7 +51,7 @@ public sealed override void UpdateSensorValues() var printersCountEntityName = $"{parentSensorSafeName}_printers_count"; var printersCountId = $"{Id}_printers_count"; - var printersCountSensor = new DataTypeIntSensor(_updateInterval, printersCountEntityName, "Printers Count", printersCountId, string.Empty, "mdi:printer", string.Empty, EntityName); + var printersCountSensor = new DataTypeIntSensor(_updateInterval, printersCountEntityName, "Printers Count", printersCountId, string.Empty, "measurement", "mdi:printer", string.Empty, EntityName); printersCountSensor.SetState(printerInfo.PrintQueues.Count); AddUpdateSensor(printersCountId, printersCountSensor); @@ -63,7 +63,7 @@ public sealed override void UpdateSensorValues() var defaultQueueJobsEntityName = $"{parentSensorSafeName}_default_queue_jobs"; var defaultQueueJobsId = $"{Id}_default_queue_jobs"; - var defaultQueueJobsSensor = new DataTypeIntSensor(_updateInterval, defaultQueueJobsEntityName, "Default Queue Jobs", defaultQueueJobsId, string.Empty, "mdi:printer", string.Empty, EntityName); + var defaultQueueJobsSensor = new DataTypeIntSensor(_updateInterval, defaultQueueJobsEntityName, "Default Queue Jobs", defaultQueueJobsId, string.Empty, "measurement", "mdi:printer", string.Empty, EntityName); defaultQueueJobsSensor.SetState(printerInfo.DefaultQueueJobs); AddUpdateSensor(defaultQueueJobsId, defaultQueueJobsSensor); @@ -72,7 +72,7 @@ public sealed override void UpdateSensorValues() var printerQueueInfo = JsonConvert.SerializeObject(printer, Formatting.Indented); var printerEntityName = $"{parentSensorSafeName}_{SharedHelperFunctions.GetSafeValue(printer.Name)}"; var printerId = $"{Id}_{SharedHelperFunctions.GetSafeValue(printer.Name)}"; - var printerSensor = new DataTypeIntSensor(_updateInterval, printerEntityName, $"{printer.Name}", printerId, string.Empty, "mdi:printer", string.Empty, EntityName, true); + var printerSensor = new DataTypeIntSensor(_updateInterval, printerEntityName, $"{printer.Name}", printerId, string.Empty, "measurement", "mdi:printer", string.Empty, EntityName, true); printerSensor.SetState(printer.Jobs); printerSensor.SetAttributes(printerQueueInfo); diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs index de154e55..62d75631 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs @@ -33,6 +33,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:bluetooth", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability", Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs index f63f17d5..c61a57b8 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs @@ -36,6 +36,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:bluetooth", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability", Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs index a9197ac9..34fb6fd0 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs @@ -48,6 +48,13 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() if (UseAttributes) sensorDiscoveryConfigModel.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes"; + if (!string.IsNullOrWhiteSpace(_internalDeviceSensor.MeasurementType)) + sensorDiscoveryConfigModel.Device_class = _internalDeviceSensor.MeasurementType; + if (!string.IsNullOrWhiteSpace(_internalDeviceSensor.UnitOfMeasurement)) + sensorDiscoveryConfigModel.Unit_of_measurement = _internalDeviceSensor.UnitOfMeasurement; + if (_internalDeviceSensor.IsNumeric) + sensorDiscoveryConfigModel.State_class = "measurement"; + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(sensorDiscoveryConfigModel); } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs index 9faf68b8..eb7806c5 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs @@ -14,6 +14,9 @@ internal class AccelerometerSensor : IInternalDeviceSensor public const string AttributeAccelerationZ = "AccelerationZ"; public const string AttributeLastShaken = "LastShaken"; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + private readonly Accelerometer _accelerometer; public bool Available => _accelerometer != null; @@ -42,6 +45,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs index 12145bdd..be2956d1 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs @@ -12,6 +12,9 @@ internal class ActivitySensor : IInternalDeviceSensor public const string AttributeConfidence = "Confidence"; public const string AttributeTimestamp = "Timestamp"; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + private readonly Windows.Devices.Sensors.ActivitySensor _activitySensor; public bool Available => _activitySensor != null; @@ -31,6 +34,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs index 3e1afd59..958b9370 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs @@ -11,6 +11,9 @@ internal class AltimeterSensor : IInternalDeviceSensor { private readonly Altimeter _altimeter; + public string MeasurementType { get; } = "distance"; + public string UnitOfMeasurement { get; } = "m"; + public bool Available => _altimeter != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Altimeter; public string Measurement @@ -24,6 +27,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public AltimeterSensor(Altimeter altimeter) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs index 61a42c68..231261fd 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs @@ -11,6 +11,9 @@ internal class BarometerSensor : IInternalDeviceSensor { private readonly Barometer _barometer; + public string MeasurementType { get; } = "pressure"; + public string UnitOfMeasurement { get; } = "hPa"; + public bool Available => _barometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Barometer; public string Measurement @@ -24,6 +27,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public BarometerSensor(Barometer barometer) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs index 89433a79..ec1a5040 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs @@ -13,6 +13,9 @@ internal class CompassSensor : IInternalDeviceSensor private readonly Compass _compass; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _compass != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Compass; public string Measurement @@ -28,6 +31,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs index ae2024ce..209c73c8 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs @@ -15,6 +15,9 @@ internal class GyrometerSensor : IInternalDeviceSensor private readonly Gyrometer _gyrometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _gyrometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Gyrometer; public string Measurement @@ -41,6 +44,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs index b28382af..30c748b3 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs @@ -11,6 +11,9 @@ internal class HingeAngleSensor : IInternalDeviceSensor { private readonly Windows.Devices.Sensors.HingeAngleSensor _hingeAngelSensor; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _hingeAngelSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.HingeAngleSensor; public string Measurement @@ -25,6 +28,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public HingeAngleSensor(Windows.Devices.Sensors.HingeAngleSensor hingeAngleSensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs index 485b9a99..5159a8f8 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs @@ -36,6 +36,9 @@ internal interface IInternalDeviceSensor public bool Available { get; } public InternalDeviceSensorType Type { get; } public string Measurement { get; } + public string MeasurementType { get; } + public string UnitOfMeasurement { get; } + public bool IsNumeric { get; } public Dictionary Attributes { get; } } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs index 6a0cf171..ec582c38 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs @@ -17,6 +17,9 @@ internal class InclinometerSensor : IInternalDeviceSensor private readonly Inclinometer _inclinometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _inclinometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Inclinometer; public string Measurement @@ -37,6 +40,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs index a3ea817a..167d8cd7 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs @@ -11,6 +11,9 @@ internal class LightSensor : IInternalDeviceSensor { private readonly Windows.Devices.Sensors.LightSensor _lightSensor; + public string MeasurementType { get; } = "illuminance"; + public string UnitOfMeasurement { get; } = "lx"; + public bool Available => _lightSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.LightSensor; public string Measurement @@ -24,6 +27,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public LightSensor(Windows.Devices.Sensors.LightSensor lightSensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs index b7d5802c..61b1f538 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs @@ -15,6 +15,9 @@ internal class MagnetometerSensor : IInternalDeviceSensor private readonly Magnetometer _magnetometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _magnetometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Magnetometer; public string Measurement @@ -41,6 +44,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs index cc9c974b..d57b1446 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs @@ -15,6 +15,9 @@ internal class OrientationSensor : IInternalDeviceSensor private readonly Windows.Devices.Sensors.OrientationSensor _orientationSensor; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _orientationSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.OrientationSensor; public string Measurement @@ -32,6 +35,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs index d2d5084e..013b60c4 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs @@ -11,6 +11,9 @@ internal class PedometerSensor : IInternalDeviceSensor { private readonly Pedometer _pedometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _pedometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Pedometer; public string Measurement @@ -37,6 +40,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs index 4ef5e466..de60c167 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs @@ -11,6 +11,9 @@ internal class ProximitySensor : IInternalDeviceSensor { private Windows.Devices.Sensors.ProximitySensor _proximitySensor; + public string MeasurementType { get; } = "distance"; + public string UnitOfMeasurement { get; } = "mm"; + public bool Available => _proximitySensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.ProximitySensor; public string Measurement @@ -24,6 +27,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public ProximitySensor(Windows.Devices.Sensors.ProximitySensor proximitySensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs index f5b5a80e..b4babdfb 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs @@ -11,6 +11,9 @@ internal class SimpleOrientationSensor : IInternalDeviceSensor { private readonly Windows.Devices.Sensors.SimpleOrientationSensor _simpleOrientationSensor; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _simpleOrientationSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.SimpleOrientationSensor; public string Measurement @@ -24,6 +27,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public SimpleOrientationSensor(Windows.Devices.Sensors.SimpleOrientationSensor simpleOrientationSensor) From 2ff369689bc72ae8e9be3615cf0ae75498597084 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 4 May 2024 11:48:45 +0200 Subject: [PATCH 24/45] Fix: screenshot sensor monitor scaling (#84) This PR fixes screenshot sensor capturing only part of the screen when "non 100%" scaling. Thanks for @ElDingo424 for reporting - #81 --- .../SingleValue/ScreenshotSensor.cs | 73 +++++++++++++++++-- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs index f3305b50..fa2dd172 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs @@ -13,12 +13,18 @@ using System.Runtime.InteropServices; using System.Drawing.Imaging; using System.Text.RegularExpressions; +using Windows.Graphics.Display; namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; public class ScreenshotSensor : AbstractSingleValueSensor { private const string DefaultName = "screenshot"; + private DEVMODE _devMode = new() + { + dmSize = (short)Marshal.SizeOf(typeof(DEVMODE)) + }; + public int ScreenIndex; public ScreenshotSensor(string screenIndex = "0", int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) @@ -52,14 +58,14 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() public override string GetState() { - var screenCount = Screen.AllScreens.Length; - if (ScreenIndex >= screenCount || ScreenIndex < 0) + if (ScreenIndex >= Screen.AllScreens.Length || ScreenIndex < 0) { Log.Warning("[SCREENSHOT] Wrong index '{index}' - returning image for screen 0", ScreenIndex); ScreenIndex = 0; } var screenImage = CaptureScreen(ScreenIndex); + return Convert.ToBase64String(screenImage); } @@ -72,22 +78,73 @@ private byte[] CaptureScreen(int screenIndex) catch (Exception ex) { Log.Error(ex, "[SCREENSHOT] Internal Error capturing screen {index}, {ex}", ex.Message); + return Array.Empty(); } } private byte[] CapturePngFile(Screen screen) { + EnumDisplaySettings(screen.DeviceName, -1, ref _devMode); + + var scalingFactor = Math.Round(decimal.Divide(_devMode.dmPelsWidth, screen.Bounds.Width), 2); + var captureRectangle = screen.Bounds; - var captureBitmap = new Bitmap(captureRectangle.Width, captureRectangle.Height, PixelFormat.Format32bppArgb); - var captureGraphics = Graphics.FromImage(captureBitmap); - captureGraphics.CopyFromScreen(captureRectangle.Left, captureRectangle.Top, 0, 0, captureRectangle.Size); + var captureSize = new Size( + Convert.ToInt32(screen.Bounds.Width * scalingFactor), + Convert.ToInt32(screen.Bounds.Height * scalingFactor) + ); + using var captureBitmap = new Bitmap(captureSize.Width, captureSize.Height, PixelFormat.Format32bppArgb); + using var captureGraphics = Graphics.FromImage(captureBitmap); + captureGraphics.CopyFromScreen(captureRectangle.Left, captureRectangle.Top, 0, 0, captureSize); - using var ms = new MemoryStream(); - captureBitmap.Save(ms, ImageFormat.Png); + using var memoryStream = new MemoryStream(); + captureBitmap.Save(memoryStream, ImageFormat.Png); - return ms.ToArray(); + return memoryStream.ToArray(); } public override string GetAttributes() => string.Empty; + + [StructLayout(LayoutKind.Sequential)] + private struct DEVMODE + { + private const int CCHDEVICENAME = 0x20; + private const int CCHFORMNAME = 0x20; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string dmDeviceName; + public short dmSpecVersion; + public short dmDriverVersion; + public short dmSize; + public short dmDriverExtra; + public int dmFields; + public int dmPositionX; + public int dmPositionY; + public ScreenOrientation dmDisplayOrientation; + public int dmDisplayFixedOutput; + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string dmFormName; + public short dmLogPixels; + public int dmBitsPerPel; + public int dmPelsWidth; + public int dmPelsHeight; + public int dmDisplayFlags; + public int dmDisplayFrequency; + public int dmICMMethod; + public int dmICMIntent; + public int dmMediaType; + public int dmDitherType; + public int dmReserved1; + public int dmReserved2; + public int dmPanningWidth; + public int dmPanningHeight; + } + + [DllImport("user32.dll")] + private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode); } From eec64dec6e006432b37e724a076c571d0ddd8c3f Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 4 May 2024 12:22:14 +0200 Subject: [PATCH 25/45] Fix: CoreAudio library memory leak / redesign (#85) This PR should fix issues with the CoreAudio library that were either due to our improper use of it or some internal unmanaged resources leak - #64 After testing multiple audio libraries I've settled for: CSCore for general audio sensors DIY solution for activating specific device (making it default) In addition to that, whole audio management was redesigned to work on an event basis rather than constantly pooling the OS for information. --- .../HASS.Agent.Shared.csproj | 3 +- .../SetApplicationVolumeCommand.cs | 83 +++--- .../InternalCommands/SetAudioInputCommand.cs | 30 +- .../InternalCommands/SetAudioOutputCommand.cs | 30 +- .../InternalCommands/SetVolumeCommand.cs | 34 +-- .../GeneralSensors/MultiValue/AudioSensors.cs | 261 ++--------------- .../SingleValue/CurrentVolumeSensor.cs | 14 +- .../Managers/Audio/AudioDevice.cs | 19 ++ .../Managers/Audio/AudioManager.cs | 270 ++++++++++++++++++ .../Managers/Audio/AudioSession.cs | 17 ++ .../Managers/Audio/DeviceRole.cs | 13 + .../Managers/Audio/DeviceType.cs | 12 + .../Audio/Exceptions/AudioSessionException.cs | 21 ++ .../Internal/CPolicyConfigVistaClient.cs | 43 +++ .../Audio/Internal/IPolicyConfigVista.cs | 50 ++++ .../Audio/Internal/InternalAudioDevice.cs | 49 ++++ .../Audio/Internal/InternalAudioSession.cs | 85 ++++++ .../Internal/InternalAudioSessionManager.cs | 69 +++++ .../Internal/_CPolicyConfigVistaClient.cs | 17 ++ .../Models/Internal/AudioSessionInfo.cs | 19 -- src/HASS.Agent/HASS.Agent.Shared/Variables.cs | 2 - src/HASS.Agent/HASS.Agent/Forms/Main.cs | 11 + .../MultiValue/PrintersSensors.cs | 1 - .../HASS.Agent/Media/MediaManager.cs | 4 +- .../HASS.Agent/Media/MediaManagerCommands.cs | 19 +- .../HASS.Agent/Media/MediaManagerRequests.cs | 31 +- .../Resources/Localization/Languages.de.resx | 10 +- .../Resources/Localization/Languages.en.resx | 29 +- .../Resources/Localization/Languages.es.resx | 10 +- .../Resources/Localization/Languages.fr.resx | 10 +- .../Resources/Localization/Languages.nl.resx | 10 +- .../Resources/Localization/Languages.pl.resx | 10 +- .../Localization/Languages.pt-br.resx | 10 +- .../Resources/Localization/Languages.resx | 10 +- .../Resources/Localization/Languages.ru.resx | 10 +- .../Resources/Localization/Languages.sl.resx | 10 +- .../Resources/Localization/Languages.tr.resx | 10 +- src/HASS.Agent/HASS.Agent/Variables.cs | 2 - 38 files changed, 945 insertions(+), 393 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs delete mode 100644 src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index aa05de02..d142df54 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -33,7 +33,7 @@ - + @@ -44,6 +44,7 @@ + diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs index f0646eb3..0d0e1084 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs @@ -1,5 +1,7 @@ -using CoreAudio; -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; +using HidSharp; using Newtonsoft.Json; using Serilog; using System; @@ -13,7 +15,6 @@ namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands public class SetApplicationVolumeCommand : InternalCommand { private const string DefaultName = "setappvolume"; - private static readonly Dictionary ApplicationNames = new Dictionary(); public SetApplicationVolumeCommand(string entityName = DefaultName, string name = DefaultName, string commandConfig = "", CommandEntityType entityType = CommandEntityType.Button, string id = default) : base(entityName ?? DefaultName, name ?? null, commandConfig, entityType, id) { @@ -33,28 +34,14 @@ public override void TurnOn() TurnOnWithAction(CommandConfig); } - private MMDevice GetAudioDeviceOrDefault(string playbackDeviceName) + private AudioDevice GetDeviceOrDefault(string deviceName) { - var devices = Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); - var playbackDevice = devices.Where(d => d.DeviceFriendlyName == playbackDeviceName).FirstOrDefault(); + var device = AudioManager.GetDevices().Where(d => d.FriendlyName == deviceName).FirstOrDefault(); + if (device != null) + return device; - return playbackDevice ?? Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - } - - private string GetSessionDisplayName(AudioSessionControl2 session) - { - var procId = (int)session.ProcessID; - - if (procId <= 0) - return session.DisplayName; - - if (ApplicationNames.ContainsKey(procId)) - return ApplicationNames[procId]; - - using var p = Process.GetProcessById(procId); - ApplicationNames.Add(procId, p.ProcessName); - - return p.ProcessName; + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); + return AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); } public override void TurnOnWithAction(string action) @@ -72,29 +59,44 @@ public override void TurnOnWithAction(string action) return; } - using var audioDevice = GetAudioDeviceOrDefault(actionData.PlaybackDevice); - using var session = audioDevice.AudioSessionManager2?.Sessions?.Where(s => - s != null && - actionData.ApplicationName == GetSessionDisplayName(s) - ).FirstOrDefault(); - - if (session == null) - { - Log.Error("[SETAPPVOLUME] Error, no session of application {app} can be found", actionData.ApplicationName); - + var audioDevice = GetDeviceOrDefault(actionData.PlaybackDevice); + if (audioDevice == null) return; - } - session.SimpleAudioVolume.Mute = actionData.Mute; + var applicationAudioSessions = audioDevice.Sessions.Where(s => + s.Application == actionData.ApplicationName + ); + if (actionData.Volume == -1) - { Log.Debug("[SETAPPVOLUME] No volume value provided, only mute has been set for {app}", actionData.ApplicationName); - return; - } - var volume = Math.Clamp(actionData.Volume, 0, 100) / 100.0f; - session.SimpleAudioVolume.MasterVolume = volume; + if (string.IsNullOrWhiteSpace(actionData.SessionId)) + { + foreach (var session in applicationAudioSessions) + { + AudioManager.SetMute(session, actionData.Mute); + if (actionData.Volume == -1) + return; + + AudioManager.SetVolume(session, actionData.Volume); + } + } + else + { + var session = applicationAudioSessions.Where(s => s.Id == actionData.SessionId).FirstOrDefault(); + if (session == null) + { + Log.Debug("[SETAPPVOLUME] No session {actionData.SessionId} found for device {device}", actionData.ApplicationName, audioDevice.FriendlyName); + return; + } + + AudioManager.SetMute(session, actionData.Mute); + if (actionData.Volume == -1) + return; + + AudioManager.SetVolume(session, actionData.Volume); + } } catch (Exception ex) { @@ -112,6 +114,7 @@ private class ApplicationVolumeAction public bool Mute { get; set; } = false; public string ApplicationName { get; set; } = string.Empty; public string PlaybackDevice { get; set; } = string.Empty; + public string SessionId { get; set; } = string.Empty; } } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs index 292b09ba..2d7de409 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs @@ -1,5 +1,6 @@ -using CoreAudio; -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Newtonsoft.Json; using Serilog; using System; @@ -35,25 +36,28 @@ public override void TurnOn() TurnOnWithAction(InputDevice); } - private MMDevice GetAudioDeviceOrDefault(string playbackDeviceName) - { - var devices = Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active); - var playbackDevice = devices.Where(d => d.DeviceFriendlyName == playbackDeviceName).FirstOrDefault(); - - return playbackDevice ?? Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications); - } - public override void TurnOnWithAction(string action) { State = "ON"; try { - var outputDevice = GetAudioDeviceOrDefault(action); - if (outputDevice == Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications)) + var audioDevices = AudioManager.GetDevices(); + var inputDevice = audioDevices + .Where(d => d.Type == DeviceType.Input) + .Where(d => d.FriendlyName == action) + .FirstOrDefault(); + + if (inputDevice == null) + { + Log.Warning("[SETAUDIOIN] No input device {device} found", action); + return; + } + + if(inputDevice.Default) return; - outputDevice.Selected = true; + AudioManager.Activate(inputDevice); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs index 4893eea6..1cefb69a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs @@ -1,5 +1,6 @@ -using CoreAudio; -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Newtonsoft.Json; using Serilog; using System; @@ -35,25 +36,28 @@ public override void TurnOn() TurnOnWithAction(OutputDevice); } - private MMDevice GetAudioDeviceOrDefault(string playbackDeviceName) - { - var devices = Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); - var playbackDevice = devices.Where(d => d.DeviceFriendlyName == playbackDeviceName).FirstOrDefault(); - - return playbackDevice ?? Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - } - public override void TurnOnWithAction(string action) { State = "ON"; try { - var outputDevice = GetAudioDeviceOrDefault(action); - if (outputDevice == Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)) + var audioDevices = AudioManager.GetDevices(); + var outputDevice = audioDevices + .Where(d => d.Type == DeviceType.Output) + .Where(d => d.FriendlyName == action) + .FirstOrDefault(); + + if (outputDevice == null) + { + Log.Warning("[SETAUDIOOUT] No input device {device} found", action); + return; + } + + if (outputDevice.Default) return; - outputDevice.Selected = true; + AudioManager.Activate(outputDevice); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs index 746d70c7..96ffda7e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using CoreAudio; +using System.Linq; using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Functions; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Serilog; namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands @@ -18,7 +20,7 @@ namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands public class SetVolumeCommand : InternalCommand { private const string DefaultName = "setvolume"; - private readonly float _volume = -1f; + private readonly int _volume = -1; public SetVolumeCommand(string entityName = DefaultName, string name = DefaultName, string volume = "", CommandEntityType entityType = CommandEntityType.Button, string id = default) : base(entityName ?? DefaultName, name ?? null, volume, entityType, id) { @@ -28,10 +30,10 @@ public SetVolumeCommand(string entityName = DefaultName, string name = DefaultNa if (!parsed) { Log.Error("[SETVOLUME] [{name}] Unable to parse configured volume level, not an int: {val}", EntityName, volume); - _volume = -1f; + _volume = -1; } - _volume = volumeInt / 100.0f; + _volume = volumeInt; } State = "OFF"; @@ -50,16 +52,12 @@ public override void TurnOn() return; } - // get the current default endpoint - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - if (audioDevice?.AudioEndpointVolume == null) - { - Log.Warning("[SETVOLUME] [{name}] Unable to trigger command, no default audio endpoint found", EntityName); - + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); + var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); + if(audioDevice == null) return; - } - audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = _volume; + AudioManager.SetVolume(audioDevice, _volume); } catch (Exception ex) { @@ -85,16 +83,12 @@ public override void TurnOnWithAction(string action) return; } - // get the current default endpoint - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - if (audioDevice?.AudioEndpointVolume == null) - { - Log.Warning("[SETVOLUME] [{name}] Unable to trigger action for command, no default audio endpoint found", EntityName); - + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); + var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); + if (audioDevice == null) return; - } - audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = volumeInt / 100.0f; ; + AudioManager.SetVolume(audioDevice, volumeInt); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs index 5df24f5d..4c6ecb97 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs @@ -3,9 +3,10 @@ using System.Diagnostics; using System.Globalization; using System.Linq; -using CoreAudio; using HASS.Agent.Shared.Functions; using HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.DataTypes; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using HASS.Agent.Shared.Models.HomeAssistant; using HASS.Agent.Shared.Models.Internal; using HidSharp; @@ -20,7 +21,6 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue; public class AudioSensors : AbstractMultiValueSensor { private const string DefaultName = "audio"; - private static readonly Dictionary ApplicationNames = new(); private bool _errorPrinted = false; private readonly int _updateInterval; @@ -42,73 +42,55 @@ private void AddUpdateSensor(string sensorId, AbstractSingleValueSensor sensor) Sensors[sensorId] = sensor; } - private List GetAudioDevices(DataFlow type) + private void HandleAudioOutputSensors(IEnumerable outputDevices, string parentSensorSafeName) { - var audioDevices = new List(); - foreach (var device in Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(type, DeviceState.Active)) - { - audioDevices.Add(device.DeviceFriendlyName); - device.Dispose(); - } - - return audioDevices; - } - - private List GetAudioOutputDevices() => GetAudioDevices(DataFlow.Render); - private List GetAudioInputDevices() => GetAudioDevices(DataFlow.Capture); - - private void HandleAudioOutputSensors(string parentSensorSafeName, string deviceName) - { - using var audioDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var outputDevice = outputDevices.Where(d => d.Default).FirstOrDefault(); + if (outputDevice == null) + return; var defaultDeviceEntityName = $"{parentSensorSafeName}_default_device"; var defaultDeviceId = $"{Id}_default_device"; var defaultDeviceSensor = new DataTypeStringSensor(_updateInterval, defaultDeviceEntityName, $"Default Device", defaultDeviceId, string.Empty, "mdi:speaker", string.Empty, EntityName); - defaultDeviceSensor.SetState(audioDevice.DeviceFriendlyName); + defaultDeviceSensor.SetState(outputDevice.FriendlyName); AddUpdateSensor(defaultDeviceId, defaultDeviceSensor); var defaultDeviceStateEntityName = $"{parentSensorSafeName}_default_device_state"; var defaultDeviceStateId = $"{Id}_default_device_state"; var defaultDeviceStateSensor = new DataTypeStringSensor(_updateInterval, defaultDeviceStateEntityName, $"Default Device State", defaultDeviceStateId, string.Empty, "mdi:speaker", string.Empty, EntityName); - defaultDeviceStateSensor.SetState(GetReadableState(audioDevice.State)); + defaultDeviceStateSensor.SetState(outputDevice.State); AddUpdateSensor(defaultDeviceStateId, defaultDeviceStateSensor); - var masterVolume = Convert.ToInt32(Math.Round(audioDevice.AudioEndpointVolume?.MasterVolumeLevelScalar * 100 ?? 0, 0)); var defaultDeviceVolumeEntityName = $"{parentSensorSafeName}_default_device_volume"; var defaultDeviceVolumeId = $"{Id}_default_device_volume"; var defaultDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultDeviceVolumeEntityName, $"Default Device Volume", defaultDeviceVolumeId, string.Empty, "measurement", "mdi:speaker", string.Empty, EntityName); - defaultDeviceVolumeSensor.SetState(masterVolume); + defaultDeviceVolumeSensor.SetState(outputDevice.Volume); AddUpdateSensor(defaultDeviceVolumeId, defaultDeviceVolumeSensor); - var defaultDeviceIsMuted = audioDevice.AudioEndpointVolume?.Mute ?? false; var defaultDeviceIsMutedEntityName = $"{parentSensorSafeName}_default_device_muted"; var defaultDeviceIsMutedId = $"{Id}_default_device_muted"; var defaultDeviceIsMutedSensor = new DataTypeBoolSensor(_updateInterval, defaultDeviceIsMutedEntityName, $"Default Device Muted", defaultDeviceIsMutedId, string.Empty, "mdi:speaker", EntityName); - defaultDeviceIsMutedSensor.SetState(defaultDeviceIsMuted); + defaultDeviceIsMutedSensor.SetState(outputDevice.Muted); AddUpdateSensor(defaultDeviceIsMutedId, defaultDeviceIsMutedSensor); - // get session and volume info - var sessionInfos = GetSessions(out var peakVolume); - var peakVolumeEntityName = $"{parentSensorSafeName}_peak_volume"; var peakVolumeId = $"{Id}_peak_volume"; - var peakVolumeSensor = new DataTypeStringSensor(_updateInterval, peakVolumeEntityName, $"Peak Volume", peakVolumeId, string.Empty, "mdi:volume-high", string.Empty, EntityName); - peakVolumeSensor.SetState(peakVolume.ToString(CultureInfo.CurrentCulture)); + var peakVolumeSensor = new DataTypeDoubleSensor(_updateInterval, peakVolumeEntityName, $"Peak Volume", peakVolumeId, string.Empty, "measurement", "mdi:volume-high", string.Empty, EntityName); + peakVolumeSensor.SetState(outputDevice.PeakVolume); AddUpdateSensor(peakVolumeId, peakVolumeSensor); var sessionsEntityName = $"{parentSensorSafeName}_sessions"; var sessionsId = $"{Id}_sessions"; var sessionsSensor = new DataTypeIntSensor(_updateInterval, sessionsEntityName, $"Audio Sessions", sessionsId, string.Empty, "measurement", "mdi:music-box-multiple-outline", string.Empty, EntityName, true); - sessionsSensor.SetState(sessionInfos.Count); + sessionsSensor.SetState(outputDevice.Sessions.Count); sessionsSensor.SetAttributes( JsonConvert.SerializeObject(new { - AudioSessions = sessionInfos + AudioSessions = outputDevice.Sessions }, Formatting.Indented) ); AddUpdateSensor(sessionsId, sessionsSensor); - var audioOutputDevices = GetAudioOutputDevices(); + var audioOutputDevices = outputDevices.Select(d => d.FriendlyName).ToList(); var audioOutputDevicesEntityName = $"{parentSensorSafeName}_output_devices"; var audioOutputDevicesId = $"{Id}_output_devices"; var audioOutputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioOutputDevicesEntityName, $"Audio Output Devices", audioOutputDevicesId, string.Empty, "measurement", "mdi:music-box-multiple-outline", string.Empty, EntityName, true); @@ -122,37 +104,37 @@ private void HandleAudioOutputSensors(string parentSensorSafeName, string device AddUpdateSensor(audioOutputDevicesId, audioOutputDevicesSensor); } - private void HandleAudioInputSensors(string parentSensorSafeName, string deviceName) + private void HandleAudioInputSensors(IEnumerable inputDevices, string parentSensorSafeName) { - using var inputDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications); + var inputDevice = inputDevices.Where(d => d.Default).FirstOrDefault(); + if (inputDevice == null) + return; var defaultInputDeviceEntityName = $"{parentSensorSafeName}_default_input_device"; var defaultInputDeviceId = $"{Id}_default_input_device"; var defaultInputDeviceSensor = new DataTypeStringSensor(_updateInterval, defaultInputDeviceEntityName, $"Default Input Device", defaultInputDeviceId, string.Empty, "mdi:microphone", string.Empty, EntityName); - defaultInputDeviceSensor.SetState(inputDevice.DeviceFriendlyName); + defaultInputDeviceSensor.SetState(inputDevice.FriendlyName); AddUpdateSensor(defaultInputDeviceId, defaultInputDeviceSensor); var defaultInputDeviceStateEntityName = $"{parentSensorSafeName}_default_input_device_state"; var defaultInputDeviceStateId = $"{Id}_default_input_device_state"; var defaultInputDeviceStateSensor = new DataTypeStringSensor(_updateInterval, defaultInputDeviceStateEntityName, $"Default Input Device State", defaultInputDeviceStateId, string.Empty, "mdi:microphone", string.Empty, EntityName); - defaultInputDeviceStateSensor.SetState(GetReadableState(inputDevice.State)); + defaultInputDeviceStateSensor.SetState(inputDevice.State); AddUpdateSensor(defaultInputDeviceStateId, defaultInputDeviceStateSensor); - var defaultInputDeviceIsMuted = inputDevice.AudioEndpointVolume?.Mute ?? false; var defaultInputDeviceIsMutedEntityName = $"{parentSensorSafeName}_default_input_device_muted"; var defaultInputDeviceIsMutedId = $"{Id}_default_input_device_muted"; var defaultInputDeviceIsMutedSensor = new DataTypeBoolSensor(_updateInterval, defaultInputDeviceIsMutedEntityName, $"Default Input Device Muted", defaultInputDeviceIsMutedId, string.Empty, "mdi:microphone", EntityName); - defaultInputDeviceIsMutedSensor.SetState(defaultInputDeviceIsMuted); + defaultInputDeviceIsMutedSensor.SetState(inputDevice.Muted); AddUpdateSensor(defaultInputDeviceIsMutedId, defaultInputDeviceIsMutedSensor); - var inputVolume = (int)GetDefaultInputDevicePeakVolume(inputDevice); var defaultInputDeviceVolumeEntityName = $"{parentSensorSafeName}_default_input_device_volume"; var defaultInputDeviceVolumeId = $"{Id}_default_input_device_volume"; var defaultInputDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultInputDeviceVolumeEntityName, $"Default Input Device Volume", defaultInputDeviceVolumeId, string.Empty, "measurement", "mdi:microphone", string.Empty, EntityName); - defaultInputDeviceVolumeSensor.SetState(inputVolume); + defaultInputDeviceVolumeSensor.SetState(Convert.ToInt32(inputDevice.PeakVolume)); AddUpdateSensor(defaultInputDeviceVolumeId, defaultInputDeviceVolumeSensor); - var audioInputDevices = GetAudioInputDevices(); + var audioInputDevices = inputDevices.Select(d => d.FriendlyName).ToList(); var audioInputDevicesEntityName = $"{parentSensorSafeName}_input_devices"; var audioInputDevicesId = $"{Id}_input_devices"; var audioInputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioInputDevicesEntityName, $"Audio Input Devices", audioInputDevicesId, string.Empty, "measurement", "mdi:microphone", string.Empty, EntityName, true); @@ -170,11 +152,14 @@ public override sealed void UpdateSensorValues() { try { + var audioDevices = AudioManager.GetDevices(); + var outputDevices = audioDevices.Where(d => d.Type == DeviceType.Output); + var inputDevices = audioDevices.Where(d => d.Type == DeviceType.Input); + var parentSensorSafeName = SharedHelperFunctions.GetSafeValue(EntityName); - var deviceSafeName = SharedHelperFunctions.GetSafeDeviceName(); - HandleAudioOutputSensors(parentSensorSafeName, deviceSafeName); - HandleAudioInputSensors(parentSensorSafeName, deviceSafeName); + HandleAudioOutputSensors(outputDevices, parentSensorSafeName); + HandleAudioInputSensors(inputDevices, parentSensorSafeName); if (_errorPrinted) _errorPrinted = false; @@ -190,191 +175,5 @@ public override sealed void UpdateSensorValues() } } - private string GetSessionDisplayName(AudioSessionControl2 session) - { - var procId = (int)session.ProcessID; - - if (procId <= 0) - return session.DisplayName; - - if (ApplicationNames.ContainsKey(procId)) - return ApplicationNames[procId]; - - // we don't know this app yet, get process info - using var p = Process.GetProcessById(procId); - ApplicationNames.Add(procId, p.ProcessName); - - return p.ProcessName; - } - - private List GetSessions(out float peakVolume) - { - var sessionInfos = new List(); - peakVolume = 0f; - - try - { - var errors = false; - - foreach (var device in Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)) - { - using (device) - { - foreach (var session in device.AudioSessionManager2?.Sessions.Where(x => x != null)) - { - if (session.ProcessID == 0) - continue; - - try - { - var displayName = GetSessionDisplayName(session); - - if (displayName == "audiodg") - continue; - - if (displayName.Length > 30) - displayName = $"{displayName[..30]}.."; - - var sessionInfo = new AudioSessionInfo - { - Application = displayName, - PlaybackDevice = device.DeviceFriendlyName, - Muted = session.SimpleAudioVolume?.Mute ?? false, - Active = session.State == AudioSessionState.AudioSessionStateActive, - MasterVolume = session.SimpleAudioVolume?.MasterVolume * 100 ?? 0f, - PeakVolume = session.AudioMeterInformation?.MasterPeakValue * 100 ?? 0f - }; - - // new max? - if (sessionInfo.PeakVolume > peakVolume) - peakVolume = sessionInfo.PeakVolume; - - sessionInfos.Add(sessionInfo); - } - catch (Exception ex) - { - if (!_errorPrinted) - Log.Fatal(ex, "[AUDIO] [{name}] [{app}] Exception while retrieving info: {err}", EntityName, session.DisplayName, ex.Message); - - errors = true; - } - finally - { - session?.Dispose(); - } - } - } - } - - // only print errors once - if (errors && !_errorPrinted) - { - _errorPrinted = true; - - return sessionInfos; - } - - // optionally reset error flag - if (_errorPrinted) - _errorPrinted = false; - } - catch (Exception ex) - { - // something went wrong, only print once - if (_errorPrinted) - return sessionInfos; - - _errorPrinted = true; - - Log.Fatal(ex, "[AUDIO] [{name}] Fatal exception while getting sessions: {err}", EntityName, ex.Message); - } - - return sessionInfos; - } - - private float GetDefaultInputDevicePeakVolume(MMDevice inputDevice) - { - if (inputDevice == null) - return 0f; - - var peakVolume = 0f; - - try - { - var errors = false; - - // process sessions (and get peak volume) - foreach (var session in inputDevice.AudioSessionManager2?.Sessions?.Where(x => x != null)!) - { - try - { - // filter inactive sessions - if (session.State != AudioSessionState.AudioSessionStateActive) - continue; - - // set peak volume - var sessionPeakVolume = session.AudioMeterInformation?.MasterPeakValue * 100 ?? 0f; - - // new max? - if (sessionPeakVolume > peakVolume) - peakVolume = sessionPeakVolume; - } - catch (Exception ex) - { - if (!_errorPrinted) - Log.Fatal(ex, "[AUDIO] [{name}] [{app}] Exception while retrieving input info: {err}", EntityName, session.DisplayName, ex.Message); - - errors = true; - } - finally - { - session?.Dispose(); - } - } - - // only print errors once - if (errors && !_errorPrinted) - { - _errorPrinted = true; - - return peakVolume; - } - - // optionally reset error flag - if (_errorPrinted) - _errorPrinted = false; - } - catch (Exception ex) - { - // something went wrong, only print once - if (_errorPrinted) - return peakVolume; - - _errorPrinted = true; - - Log.Fatal(ex, "[AUDIO] [{name}] Fatal exception while getting input info: {err}", EntityName, ex.Message); - } - - return peakVolume; - } - - /// - /// Converts the audio device's state to a better readable form - /// - /// - /// - private static string GetReadableState(DeviceState state) - { - return state switch - { - DeviceState.Active => "ACTIVE", - DeviceState.Disabled => "DISABLED", - DeviceState.NotPresent => "NOT PRESENT", - DeviceState.Unplugged => "UNPLUGGED", - DeviceState.MaskAll => "STATEMASK_ALL", - _ => "UNKNOWN" - }; - } - public override DiscoveryConfigModel GetAutoDiscoveryConfig() => null; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs index 7ad33701..e1a03ce9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; -using CoreAudio; +using System.Linq; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using HASS.Agent.Shared.Models.HomeAssistant; namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue @@ -37,13 +39,13 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() public override string GetState() { - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - // check for null & mute - if (audioDevice?.AudioEndpointVolume == null) return "0"; - if (audioDevice.AudioEndpointVolume.Mute) return "0"; + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); + var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); + if (audioDevice == null) + return "0"; // return as percentage - return Math.Round(audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0).ToString(CultureInfo.InvariantCulture); + return audioDevice.Volume.ToString(CultureInfo.InvariantCulture); } public override string GetAttributes() => string.Empty; diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs new file mode 100644 index 00000000..7db9b95c --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public class AudioDevice +{ + public string State { get; set; } = string.Empty; + public DeviceType Type { get; set; } + public string Id { get; set; } = string.Empty; + public string FriendlyName { get; set; } = string.Empty; + public int Volume { get; set; } + public double PeakVolume { get; set; } + public bool Muted { get; set; } + public List Sessions { get; set; } = new(); + public bool Default { get; set; } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs new file mode 100644 index 00000000..8f0cea0d --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CSCore.CoreAudioAPI; +using HASS.Agent.Shared.Managers.Audio.Exceptions; +using HASS.Agent.Shared.Managers.Audio.Internal; +using Serilog; + +namespace HASS.Agent.Shared.Managers.Audio; +public static class AudioManager +{ + private static bool _initialized = false; + + private static readonly MMDeviceEnumerator _enumerator = new(); + + private static readonly ConcurrentDictionary _devices = new(); + private static readonly ConcurrentQueue _devicesToBeRemoved = new(); + private static readonly ConcurrentQueue _devicesToBeAdded = new(); + + private static readonly Dictionary _applicationNameCache = new(); + + public static void Initialize() + { + Log.Debug("[AUDIOMGR] Audio Manager initializing"); + + foreach (var device in _enumerator.EnumAudioEndpoints(DataFlow.All, DeviceState.Active)) + _devices[device.DeviceID] = new InternalAudioDevice(device); + + var nc = new MMNotificationClient(_enumerator); + nc.DeviceAdded += (sender, e) => + { + if (_devices.ContainsKey(e.DeviceId)) + return; + + _devicesToBeAdded.Enqueue(e.DeviceId); + }; + + nc.DeviceRemoved += (sender, e) => + { + _devicesToBeRemoved.Enqueue(e.DeviceId); + }; + + _initialized = true; + + nc.DeviceStateChanged += (sender, e) => + { + switch (e.DeviceState) + { + case DeviceState.Active: + if (_devices.ContainsKey(e.DeviceId)) + return; + + _devicesToBeAdded.Enqueue(e.DeviceId); + break; + + case DeviceState.NotPresent: + case DeviceState.UnPlugged: + _devicesToBeRemoved.Enqueue(e.DeviceId); + break; + + default: + break; + } + }; + + Log.Information("[AUDIOMGR] Audio Manager initialized"); + } + + private static void CheckInitialization() + { + if (!_initialized) + throw new InvalidOperationException("AudioManager is not initialized"); + + while (_devicesToBeRemoved.TryDequeue(out var deviceId)) + { + if (_devices.Remove(deviceId, out var device)) + device.Dispose(); + } + + while (_devicesToBeAdded.TryDequeue(out var deviceId)) + { + var device = _enumerator.GetDevice(deviceId); + _devices[deviceId] = new InternalAudioDevice(device); + } + } + + private static string GetSessionDisplayName(InternalAudioSession session) + { + var procId = session.ProcessId; + + if (procId <= 0) + return session.DisplayName; + + if (_applicationNameCache.TryGetValue(procId, out var cachedName)) + return cachedName; + + using var process = Process.GetProcessById(procId); + _applicationNameCache[procId] = process.ProcessName; + + return process.ProcessName; + } + + private static List GetDeviceSessions(InternalAudioDevice internalAudioDevice) + { + var audioSessions = new List(); + + internalAudioDevice.Manager.RemoveDisconnectedSessions(); + foreach (var (sessionId, session) in internalAudioDevice.Manager.Sessions) + { + try + { + var displayName = string.IsNullOrWhiteSpace(session.DisplayName) ? GetSessionDisplayName(session) : session.DisplayName; + if (displayName == "audiodg") + continue; + + if (displayName.Length > 30) + displayName = $"{displayName[..30]}.."; + + var audioSession = new AudioSession + { + Id = sessionId, + Application = displayName, + PlaybackDevice = internalAudioDevice.FriendlyName, + Muted = session.Volume.IsMuted, + Active = session.Control.SessionState == AudioSessionState.AudioSessionStateActive, + MasterVolume = Convert.ToInt32(session.Volume.MasterVolume * 100), + PeakVolume = Convert.ToDouble(session.MeterInformation.PeakValue * 100) + }; + + audioSessions.Add(audioSession); + } + catch (Exception ex) + { + throw new AudioSessionException($"error retrieving session information for {internalAudioDevice.FriendlyName}", ex); + } + } + + return audioSessions; + } + + private static string GetReadableState(DeviceState state) + { + return state switch + { + DeviceState.Active => "ACTIVE", + DeviceState.Disabled => "DISABLED", + DeviceState.NotPresent => "NOT PRESENT", + DeviceState.UnPlugged => "UNPLUGGED", + DeviceState.All => "STATEMASK_ALL", + _ => "UNKNOWN" + }; + } + + public static List GetDevices() + { + CheckInitialization(); + + var audioDevices = new List(); + + using var defaultInputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + using var defaultOutputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + + var defaultInputDeviceId = defaultInputDevice.DeviceID; + var defaultOutputDeviceId = defaultOutputDevice.DeviceID; + + foreach (var device in _devices.Values.Where(d => d.MMDevice.DeviceState == DeviceState.Active)) + { + + var audioSessions = GetDeviceSessions(device); + var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); + + var audioDevice = new AudioDevice + { + State = GetReadableState(device.MMDevice.DeviceState), + Type = device.MMDevice.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, + Id = device.DeviceId, + FriendlyName = device.FriendlyName, + Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.GetMasterVolumeLevelScalar() * 100, 0)), + PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, + Sessions = audioSessions, + Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId + }; + + audioDevices.Add(audioDevice); + } + + return audioDevices; + } + + public static string GetDefaultDeviceId(DeviceType deviceType, DeviceRole deviceRole) + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; + + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + + return defaultDevice.DeviceID; + } + + public static void Activate(AudioDevice audioDevice) + { + if (!_devices.TryGetValue(audioDevice.Id, out var device)) + return; + + device.Activate(); + } + + public static void SetVolume(AudioDevice audioDevice, int volume) + { + if (!_devices.TryGetValue(audioDevice.Id, out var device)) + return; + + var volumeScalar = volume / 100f; + device.AudioEndpointVolume.SetMasterVolumeLevelScalar(volumeScalar, Guid.Empty); + } + + public static void SetVolume(AudioSession audioSession, int volume) + { + var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); + if (device == null) + return; + + if (!device.Manager.Sessions.TryGetValue(audioSession.Id, out var session)) + return; + + var volumeScalar = volume / 100f; + session.Volume.MasterVolume = volumeScalar; + } + + public static void SetMute(AudioDevice audioDevice, bool mute) + { + if (!_devices.TryGetValue(audioDevice.Id, out var device)) + return; + + device.AudioEndpointVolume.SetMute(mute, Guid.Empty); + } + + public static void SetMute(AudioSession audioSession, bool mute) + { + var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); + if (device == null) + return; + + if (!device.Manager.Sessions.TryGetValue(audioSession.Id, out var session)) + return; + + session.Volume.IsMuted = mute; + } + + public static void Shutdown() + { + Log.Debug("[AUDIOMGR] Audio Manager shutting down"); + try + { + foreach(var device in _devices.Values) + device.Dispose(); + + _enumerator.Dispose(); + } + catch (Exception ex) + { + Log.Fatal(ex, "[AUDIOMGR] Audio Manager shutdown fatal error: {ex}", ex.Message); + } + Log.Debug("[AUDIOMGR] Audio Manager shutdown completed"); + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs new file mode 100644 index 00000000..26849437 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public class AudioSession +{ + public string Id { get; set; } + public string Application { get; set; } = string.Empty; + public string PlaybackDevice { get; set; } = string.Empty; + public bool Muted { get; set; } + public bool Active { get; set; } + public int MasterVolume { get; set; } + public double PeakVolume { get; set; } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs new file mode 100644 index 00000000..1c0a34c2 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public enum DeviceRole +{ + Console, + Multimedia, + Communications +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs new file mode 100644 index 00000000..3de925f6 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public enum DeviceType +{ + Input, + Output +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs new file mode 100644 index 00000000..246e53ea --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio.Exceptions; +public class AudioSessionException : Exception +{ + public AudioSessionException() + { + } + + public AudioSessionException(string message) : base(message) + { + } + + public AudioSessionException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs new file mode 100644 index 00000000..73db3317 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using CSCore.CoreAudioAPI; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +// https://github.com/morphx666/CoreAudio +// https://github.com/File-New-Project/EarTrumpet + +internal class CPolicyConfigVistaClient : IDisposable +{ + private IPolicyConfigVista policyConfigVistaClient; + + public CPolicyConfigVistaClient() + { + policyConfigVistaClient = (IPolicyConfigVista)new _CPolicyConfigVistaClient(); + } + + public void SetDefaultDevice(string deviceID) + { + + policyConfigVistaClient.SetDefaultEndpoint(deviceID, Role.Console); + policyConfigVistaClient.SetDefaultEndpoint(deviceID, Role.Multimedia); + policyConfigVistaClient.SetDefaultEndpoint(deviceID, Role.Communications); + } + + public void Dispose() + { + if (policyConfigVistaClient != null && Marshal.IsComObject(policyConfigVistaClient)) + Marshal.FinalReleaseComObject(policyConfigVistaClient); + + GC.SuppressFinalize(this); + } + + ~CPolicyConfigVistaClient() + { + Dispose(); + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs new file mode 100644 index 00000000..e430e102 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using CSCore.CoreAudioAPI; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +// https://github.com/morphx666/CoreAudio +// https://github.com/File-New-Project/EarTrumpet + +[Guid("568b9108-44bf-40b4-9006-86afe5b5a620")] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface IPolicyConfigVista +{ + [PreserveSig] + int GetMixFormat(); + + [PreserveSig] + int GetDeviceFormat(); + + [PreserveSig] + int SetDeviceFormat(); + + [PreserveSig] + int GetProcessingPeriod(); + + [PreserveSig] + int SetProcessingPeriod(); + + [PreserveSig] + int GetShareMode(); + + [PreserveSig] + int SetShareMode(); + + [PreserveSig] + int GetPropertyValue(); + + [PreserveSig] + int SetPropertyValue(); + + [PreserveSig] + int SetDefaultEndpoint([MarshalAs(UnmanagedType.LPWStr)] string wszDeviceId, Role eRole); + + [PreserveSig] + int SetEndpointVisibility(); +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs new file mode 100644 index 00000000..59721484 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CSCore.CoreAudioAPI; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; +internal class InternalAudioDevice : IDisposable +{ + public MMDevice MMDevice { get; private set; } + public AudioEndpointVolume AudioEndpointVolume { get; private set; } + public InternalAudioSessionManager Manager { get; private set; } + + public string DeviceId { get; private set; } + public string FriendlyName { get; private set; } + public bool Reinitialized { get; private set; } + + public InternalAudioDevice(MMDevice device) + { + MMDevice = device; + var sessionManager2 = AudioSessionManager2.FromMMDevice(device); + Manager = new InternalAudioSessionManager(sessionManager2); + AudioEndpointVolume = AudioEndpointVolume.FromDevice(device); + + DeviceId = device.DeviceID; + FriendlyName = device.FriendlyName; + } + + public void Activate() + { + using var configClient = new CPolicyConfigVistaClient(); + configClient.SetDefaultDevice(MMDevice.DeviceID); + } + + public void Dispose() + { + AudioEndpointVolume?.Dispose(); + Manager?.Dispose(); + MMDevice?.Dispose(); + + GC.SuppressFinalize(this); + } + + ~InternalAudioDevice() + { + Dispose(); + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs new file mode 100644 index 00000000..480b40c4 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CSCore.CoreAudioAPI; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; +internal class InternalAudioSession : IDisposable, IAudioSessionEvents +{ + public AudioSessionControl Control { get; private set; } + public AudioSessionControl2 Control2 { get; private set; } + public SimpleAudioVolume Volume { get; private set; } + public AudioMeterInformation MeterInformation { get; private set; } + + public string DisplayName { get; private set; } + public int ProcessId { get; private set; } + public bool Expired { get; private set; } = false; + + public InternalAudioSession(AudioSessionControl audioSessionControl) + { + Control = audioSessionControl; + Control2 = audioSessionControl.QueryInterface(); + Volume = audioSessionControl.QueryInterface(); + MeterInformation = audioSessionControl.QueryInterface(); + + DisplayName = Control2.DisplayName; + ProcessId = Control2.ProcessID; + + Control.RegisterAudioSessionNotification(this); + } + + public void Dispose() + { + Control.UnregisterAudioSessionNotification(this); + + MeterInformation?.Dispose(); + Volume?.Dispose(); + Control2?.Dispose(); + Control?.Dispose(); + + GC.SuppressFinalize(this); + } + + ~InternalAudioSession() + { + Dispose(); + } + + public void OnDisplayNameChanged(string newDisplayName, ref Guid eventContext) + { + + } + + public void OnIconPathChanged(string newIconPath, ref Guid eventContext) + { + + } + + public void OnSimpleVolumeChanged(float newVolume, bool newMute, ref Guid eventContext) + { + + } + + public void OnChannelVolumeChanged(int channelCount, float[] newChannelVolumeArray, int changedChannel, ref Guid eventContext) + { + + } + + public void OnGroupingParamChanged(ref Guid newGroupingParam, ref Guid eventContext) + { + + } + + public void OnStateChanged(AudioSessionState newState) + { + if (newState == AudioSessionState.AudioSessionStateExpired) + Expired = true; + } + + public void OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason) + { + + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs new file mode 100644 index 00000000..0ebab737 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CSCore.CoreAudioAPI; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; +internal class InternalAudioSessionManager : IDisposable +{ + public AudioSessionManager2 Manager { get; private set; } + public ConcurrentDictionary Sessions { get; private set; } = new(); + public InternalAudioSessionManager(AudioSessionManager2 sessionManager2) + { + Manager = sessionManager2; + + using var sessionEnumerator = Manager.GetSessionEnumerator(); + foreach (var session in sessionEnumerator) + { + var internalSession = new InternalAudioSession(session); + Sessions[internalSession.Control2.SessionInstanceIdentifier] = internalSession; + } + + Manager.SessionCreated += Manager_SessionCreated; + } + + private void Manager_SessionCreated(object? sender, SessionCreatedEventArgs e) + { + if (e.NewSession != null) + { + var internalSession = new InternalAudioSession(e.NewSession); + Sessions[internalSession.Control2.SessionInstanceIdentifier] = internalSession; + } + } + + public void RemoveDisconnectedSessions() + { + var expiredSessionsId = Sessions.Values.Where(s => s.Expired).Select(s => s.Control2.SessionInstanceIdentifier); + if (!expiredSessionsId.Any()) + return; + + foreach (var expiredSessionId in expiredSessionsId) + { + Sessions.Remove(expiredSessionId, out var expiredSession); + expiredSession?.Dispose(); + } + } + + public void Dispose() + { + if (Manager != null) + Manager.SessionCreated -= Manager_SessionCreated; + + foreach (var session in Sessions.Values) + { + session?.Dispose(); + } + + Manager?.Dispose(); + + GC.SuppressFinalize(this); + } + + ~InternalAudioSessionManager() + { + Dispose(); + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs new file mode 100644 index 00000000..eea62999 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +// https://github.com/morphx666/CoreAudio +// https://github.com/File-New-Project/EarTrumpet + +[ComImport] +[Guid("294935CE-F637-4E7C-A41B-AB255460B862")] +internal class _CPolicyConfigVistaClient +{ +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs deleted file mode 100644 index c884e3ed..00000000 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; - -namespace HASS.Agent.Shared.Models.Internal -{ - public class AudioSessionInfo - { - public AudioSessionInfo() - { - // - } - - public string Application { get; set; } = string.Empty; - public string PlaybackDevice { get; set; } = string.Empty; - public bool Muted { get; set; } - public bool Active { get; set; } - public float MasterVolume { get; set; } = 0f; - public float PeakVolume { get; set; } = 0f; - } -} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Variables.cs b/src/HASS.Agent/HASS.Agent.Shared/Variables.cs index 1c73153d..bcd3ff7a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Variables.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Variables.cs @@ -1,5 +1,4 @@ using System; -using CoreAudio; using HASS.Agent.Shared.Mqtt; namespace HASS.Agent.Shared @@ -14,7 +13,6 @@ internal class Variables /// /// public references /// - internal static MMDeviceEnumerator AudioDeviceEnumerator { get; } = new MMDeviceEnumerator(Guid.NewGuid()); internal static Random Rnd { get; } = new Random(); /// diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.cs b/src/HASS.Agent/HASS.Agent/Forms/Main.cs index 583c9197..e09e8933 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.cs @@ -20,6 +20,7 @@ using HASS.Agent.Shared.Extensions; using HASS.Agent.Shared.Functions; using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Serilog; using Syncfusion.Windows.Forms; using WindowsDesktop; @@ -91,6 +92,7 @@ private async void Main_Load(object sender, EventArgs e) await InternalDeviceSensorsManager.Initialize(); InitializeHardwareManager(); InitializeVirtualDesktopManager(); + await Task.Run(InitializeAudioManager); // load entities var loaded = await SettingsManager.LoadEntitiesAsync(); @@ -163,6 +165,7 @@ await Task.WhenAll(hassApiTask, sensorTask, commandsTask, serviceTask, private void OnProcessExit(object sender, EventArgs e) { + AudioManager.Shutdown(); HardwareManager.Shutdown(); NotificationManager.Exit(); } @@ -334,6 +337,14 @@ private void InitializeHardwareManager() HardwareManager.Initialize(); } + /// + /// Initialized the Audio Manager + /// + private void InitializeAudioManager() + { + AudioManager.Initialize(); + } + /// /// Hide if not shutting down, close otherwise /// diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs index 44c6d085..a06e73e3 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Globalization; using System.Printing; -using CoreAudio; using HASS.Agent.Models.Internal; using HASS.Agent.Shared.Functions; using HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.DataTypes; diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs index 76ed14f6..08b97f7a 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs @@ -2,7 +2,6 @@ using System.Text.Json; using Windows.Media.Control; using Windows.Media.Playback; -using CoreAudio; using HASS.Agent.Enums; using HASS.Agent.Extensions; using HASS.Agent.Managers; @@ -49,8 +48,7 @@ internal static async Task InitializeAsync() // todo: optional, but add an OS check - not all OSs support this try { - // create the objects - Variables.AudioDeviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); + // create the object Variables.MediaPlayer = new MediaPlayer(); _sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs index 1d4b2646..47089ca0 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs @@ -1,5 +1,4 @@ -using CoreAudio; -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -12,6 +11,8 @@ using static HASS.Agent.Shared.Functions.Inputs; using static System.Runtime.CompilerServices.RuntimeHelpers; using HASS.Agent.Shared.Functions; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; namespace HASS.Agent.Media { @@ -70,18 +71,12 @@ internal static void SetVolume(int volume) if (volume < 0) volume = 0; if (volume > 100) volume = 100; - var fVolume = volume / 100.0f; - - // get the current default endpoint - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - if (audioDevice?.AudioEndpointVolume == null) - { - Log.Warning("[MEDIA] Unable to set volume, no default audio endpoint found"); + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); + var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); + if (audioDevice == null) return; - } - // all good, set the volume - audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = fVolume; + AudioManager.SetVolume(audioDevice, volume); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs index 9317ad4b..bdf93fca 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs @@ -3,7 +3,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using CoreAudio; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Serilog; namespace HASS.Agent.Media @@ -18,16 +19,12 @@ internal static int GetVolume() { try { - // get the default audio device - using var audioDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia); + var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); + if (audioDevice == null) + return 0; - // get default device volume - var volume = Convert.ToInt32(Math.Round(audioDevice.AudioEndpointVolume?.MasterVolumeLevelScalar * 100 ?? 0, 0)); - - // Log.Debug("[MEDIA] Current volume: {vol}", volume); - - // return it - return volume; + return audioDevice.Volume; } catch (Exception ex) { @@ -44,16 +41,12 @@ internal static bool GetMuteState() { try { - // get the default audio device - using var audioDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - - // get mute state - var muted = audioDevice.AudioEndpointVolume?.Mute ?? false; - - // Log.Debug("[MEDIA] Muted: {mute}", muted); + var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia); + var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); + if (audioDevice == null) + return false; - // return it - return muted; + return audioDevice.Muted; } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index e8bbdff5..08861f67 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3416,7 +3416,15 @@ Befehl/Nutzlast muss im JSON-Format vorliegen. Beispiel aller möglichen Optione Wenn kein „playbackDevice“ angegeben ist, verwendet HASS.Agent das Standardgerät. Wenn keine „volume“ angegeben ist, stellt HASS.Agent nur den Stummschaltungsstatus ein. -Wenn keine „mute“ bereitgestellt wird, hebt HASS.Agent die Stummschaltung der bereitgestellten Anwendung auf. +Wenn keine „mute“ bereitgestellt wird, hebt HASS.Agent die Stummschaltung der bereitgestellten Anwendung auf. + +Erweiterte Option: Zusätzliche „sessionId“ kann bereitgestellt werden, um die Lautstärke nur für eine bestimmte Sitzung der Anwendung festzulegen. Die Sitzungs-ID kann vom Sensor „Audio Sessions“ abgerufen werden: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Bitte geben Sie einen gültigen JSON-String ein! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 23214f52..a96a6a15 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3312,7 +3312,34 @@ Command / payload needs to be in JSON format. Example of all possible options: If no "playbackDevice" is provided, HASS.Agent will use the default one. If no "volume" is provided, HASS.Agent will set only mute status. -If no "mute" is provided, HASS.Agent will unmute the provided application. +If no "mute" is provided, HASS.Agent will unmute the provided application. + +Advanced option: additional "sessionId" can be provided to set volume only for specific session of the application, session id can be obtained from "Audio Sessions" sensor: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +}Sets the volume and mute status of the provided application on provided audio device to the specified level. +Command / payload needs to be in JSON format. Example of all possible options: +{ + "playbackDevice": "Speakers (THX Spatial Audio)", + "applicationName": "Discord", + "volume": 90, + "mute": true +} + +If no "playbackDevice" is provided, HASS.Agent will use the default one. +If no "volume" is provided, HASS.Agent will set only mute status. +If no "mute" is provided, HASS.Agent will unmute the provided application. + +Advanced option: additional "sessionId" can be provided to set volume only for specific session of the application, session id can be obtained from "Audio Sessions" sensor: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Please enter a valid JSON string! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index d85d8775..11bc91fd 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3292,7 +3292,15 @@ El comando/carga útil debe estar en formato JSON. Ejemplo de todas las opciones Si no se proporciona ningún "playbackDevice", HASS.Agent utilizará el predeterminado. Si no se proporciona ningún "volume", HASS.Agent establecerá sólo el estado de silencio. -Si no se proporciona ningún "mute", HASS.Agent reactivará el silencio de la aplicación proporcionada. +Si no se proporciona ningún "mute", HASS.Agent reactivará el silencio de la aplicación proporcionada. + +Opción avanzada: se puede proporcionar un "sessionId" adicional para configurar el volumen solo para una sesión específica de la aplicación; el id de sesión se puede obtener del sensor "Sesiones de audio": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} ¡Ingrese una cadena JSON válida! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index ae38cacb..036c40c4 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3325,7 +3325,15 @@ La commande/charge utile doit être au format JSON. Exemple de toutes les option Si aucun "playbackDevice" n'est fourni, HASS.Agent utilisera celui par défaut. Si aucun "volume" n'est fourni, HASS.Agent définira uniquement le statut muet. -Si aucun "mute" n'est fourni, HASS.Agent réactivera le son de l'application fournie. +Si aucun "mute" n'est fourni, HASS.Agent réactivera le son de l'application fournie. + +Option avancée : un "sessionId" supplémentaire peut être fourni pour définir le volume uniquement pour une session spécifique de l'application, l'identifiant de session peut être obtenu à partir du capteur "Sessions audio" : +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Veuillez saisir une chaîne JSON valide ! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index b67fab9a..7b6f0a41 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3313,7 +3313,15 @@ Commando/payload moet in JSON-indeling zijn. Voorbeeld van alle mogelijke opties } Als er geen "playbackDevice" is opgegeven, zal HASS.Agent het standaardapparaat gebruiken. Als er geen "volume" is opgegeven, zal HASS.Agent alleen de mute-status instellen. -Als er geen "mute" is opgegeven, zal HASS.Agent het dempen van de opgegeven applicatie opheffen. +Als er geen "mute" is opgegeven, zal HASS.Agent het dempen van de opgegeven applicatie opheffen. + +Geavanceerde optie: er kan een extra "sessionId" worden opgegeven om het volume alleen in te stellen voor een specifieke sessie van de applicatie. De sessie-ID kan worden verkregen via de sensor "Audio Sessions": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Voer een geldige JSON-string in! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 6dd51e93..684d9e02 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3402,7 +3402,15 @@ Polecenie/ładunek musi być w formacie JSON. Przykład wszystkich możliwych op Jeśli nie podano „playbackDevice”, HASS.Agent użyje urządzenia domyślnego. Jeśli nie zostanie podany „volume”, HASS.Agent ustawi jedynie stan wyciszenia. -Jeśli nie podano opcji „mute”, HASS.Agent wyłączy wyciszenie dostarczonej aplikacji. +Jeśli nie podano opcji „mute”, HASS.Agent wyłączy wyciszenie dostarczonej aplikacji. + +Opcja zaawansowana: można udostępnić dodatkowy parametr „sessionId”, aby ustawić głośność tylko dla konkretnej sesji aplikacji, identyfikator sesji można uzyskać z czujnika „Audio Sessions”: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Proszę wprowadzić prawidłowy ciąg JSON! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 0d4fe746..bdebb522 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3338,7 +3338,15 @@ O comando/carga precisa estar no formato JSON. Exemplo de todas as opções poss Se nenhum "playbackDevice" for fornecido, o HASS.Agent usará o padrão. Se nenhum "volume" for fornecido, o HASS.Agent definirá apenas o status mudo. -Se nenhum "mute" for fornecido, o HASS.Agent ativará o som do aplicativo fornecido. +Se nenhum "mute" for fornecido, o HASS.Agent ativará o som do aplicativo fornecido. + +Opção avançada: "sessionId" adicional pode ser fornecido para definir o volume apenas para uma sessão específica do aplicativo, o ID da sessão pode ser obtido no sensor "Sessões de Áudio": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Insira uma string JSON válida! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index c79a98a1..8c25f36e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3267,7 +3267,15 @@ Command / payload needs to be in JSON format. Example of all possible options: If no "playbackDevice" is provided, HASS.Agent will use the default one. If no "volume" is provided, HASS.Agent will set only mute status. -If no "mute" is provided, HASS.Agent will unmute the provided application. +If no "mute" is provided, HASS.Agent will unmute the provided application. + +Advanced option: additional "sessionId" can be provided to set volume only for specific session of the application, session id can be obtained from "Audio Sessions" sensor: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Please enter a valid JSON string! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 985e25a2..b34d9222 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3361,7 +3361,15 @@ Home Assistant. Если «playbackDevice» не указано, HASS.Agent будет использовать устройство по умолчанию. Если «громкость» не указана, HASS.Agent установит только статус отключения звука. -Если «отключение звука» не указано, HASS.Agent включит звук предоставленного приложения. +Если «отключение звука» не указано, HASS.Agent включит звук предоставленного приложения. + +Расширенный вариант: можно указать дополнительный «sessionId» для установки громкости только для определенного сеанса приложения, идентификатор сеанса можно получить из датчика «Аудио сеансы»: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Пожалуйста, введите действительную строку JSON! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index bb2dfbf6..e8554e97 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3441,7 +3441,15 @@ Ukaz/tovor mora biti v formatu JSON. Primer vseh možnih možnosti: Če ni na voljo "playbackDevice", bo HASS.Agent uporabil privzeto. Če ni na voljo nobena "volume", bo HASS.Agent nastavil samo stanje utišanja. -Če ni na voljo možnost "mute", bo HASS.Agent znova vključil zvok navedene aplikacije. +Če ni na voljo možnost "mute", bo HASS.Agent znova vključil zvok navedene aplikacije. + +Napredna možnost: dodatni "sessionId" je mogoče zagotoviti za nastavitev glasnosti samo za določeno sejo aplikacije, ID seje je mogoče pridobiti iz senzorja "Audio Sessions": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Vnesite veljaven niz JSON! diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index 4c44f726..edc26869 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -2908,7 +2908,15 @@ Komut/yükün JSON formatında olması gerekir. Tüm olası seçeneklere örnek: Hiçbir "playbackDevice" sağlanmazsa, HASS.Agent varsayılan olanı kullanacaktır. Hiçbir "volume" sağlanmazsa, HASS.Agent yalnızca sessiz durumunu ayarlayacaktır. -Eğer "mute" sağlanmadıysa, HASS.Agent sağlanan uygulamanın sesini açacaktır. +Eğer "mute" sağlanmadıysa, HASS.Agent sağlanan uygulamanın sesini açacaktır. + +Gelişmiş seçenek: yalnızca uygulamanın belirli bir oturumu için ses seviyesini ayarlamak üzere ek "sessionId" sağlanabilir, oturum kimliği "Sesli Oturumlar" sensöründen alınabilir: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Lütfen geçerli bir JSON dizesi girin! diff --git a/src/HASS.Agent/HASS.Agent/Variables.cs b/src/HASS.Agent/HASS.Agent/Variables.cs index 5fe6cca1..c8f67547 100644 --- a/src/HASS.Agent/HASS.Agent/Variables.cs +++ b/src/HASS.Agent/HASS.Agent/Variables.cs @@ -5,7 +5,6 @@ using System.Net.Http; using System.Reflection; using Windows.Media.Playback; -using CoreAudio; using Grapevine; using HASS.Agent.Forms; using HASS.Agent.Functions; @@ -123,7 +122,6 @@ public static class Variables /// /// Media /// - internal static MMDeviceEnumerator AudioDeviceEnumerator { get; set; } internal static MediaPlayer MediaPlayer { get; set; } /// From efa9a323bbe5bbfc160aedf71b55f87d83d8846a Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 7 May 2024 15:13:48 +0200 Subject: [PATCH 26/45] Fix: name being used instead of friendly name while creating/editing commands (#88) This PR fixed commands friendly name not being properly saved when creating/editing sensors - entity name was used instead. --- src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs index 983b41b8..1bdbd978 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs @@ -265,7 +265,7 @@ private void BtnStore_Click(object sender, EventArgs e) MessageBoxAdv.Show(this, Languages.CommandsMod_BtnStore_DeviceNameInSensorName, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); } - var friendlyName = string.IsNullOrEmpty(TbName.Text.Trim()) ? name : TbName.Text.Trim(); + var friendlyName = string.IsNullOrEmpty(TbFriendlyName.Text.Trim()) ? name : TbFriendlyName.Text.Trim(); var sanitized = SharedHelperFunctions.GetSafeValue(name); if (sanitized != name) From dafd930ed37675012a70d72e6047cc326fdfa520 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Wed, 8 May 2024 21:33:43 +0200 Subject: [PATCH 27/45] Fix: windows updates sensor missing uniqueid (#90) * separated sensor id from sensor entity name in image of audio sensors * cleanup --- .../MultiValue/WindowsUpdatesSensors.cs | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs index 5ac1797f..949d7933 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs @@ -16,7 +16,7 @@ public class WindowsUpdatesSensors : AbstractMultiValueSensor private const string DefaultName = "windowsupdates"; private readonly int _updateInterval; - public sealed override Dictionary Sensors { get; protected set; } = new Dictionary(); + public override sealed Dictionary Sensors { get; protected set; } = new Dictionary(); public WindowsUpdatesSensors(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 900, id) { @@ -33,37 +33,44 @@ private void AddUpdateSensor(string sensorId, AbstractSingleValueSensor sensor) Sensors[sensorId] = sensor; } - public sealed override void UpdateSensorValues() + public override sealed void UpdateSensorValues() { var parentSensorSafeName = SharedHelperFunctions.GetSafeValue(EntityName); var (driverUpdates, softwareUpdates) = WindowsUpdatesManager.GetAvailableUpdates(); - var driverUpdateCount = driverUpdates.Count; - var driverUpdateCountId = $"{parentSensorSafeName}_driver_updates_pending"; - var driverUpdateCountSensor = new DataTypeIntSensor(_updateInterval, driverUpdateCountId, "Driver Updates Pending", driverUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); - driverUpdateCountSensor.SetState(driverUpdateCount); + var driverUpdateCountEntityName = $"{parentSensorSafeName}_driver_updates_pending"; + var driverUpdateCountId = $"{Id}_driver_updates_pending"; + var driverUpdateCountSensor = new DataTypeIntSensor(_updateInterval, driverUpdateCountEntityName, "Driver Updates Pending", driverUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); + driverUpdateCountSensor.SetState(driverUpdates.Count); AddUpdateSensor(driverUpdateCountId, driverUpdateCountSensor); - var softwareUpdateCount = softwareUpdates.Count; - var softwareUpdateCountId = $"{parentSensorSafeName}_software_updates_pending"; - var softwareUpdateCountSensor = new DataTypeIntSensor(_updateInterval, softwareUpdateCountId, "Software Updates Pending", softwareUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); - softwareUpdateCountSensor.SetState(softwareUpdateCount); + var softwareUpdateCountEntityName = $"{parentSensorSafeName}_software_updates_pending"; + var softwareUpdateCountId = $"{Id}_software_updates_pending"; + var softwareUpdateCountSensor = new DataTypeIntSensor(_updateInterval, softwareUpdateCountEntityName, "Software Updates Pending", softwareUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); + softwareUpdateCountSensor.SetState(softwareUpdates.Count); AddUpdateSensor(softwareUpdateCountId, softwareUpdateCountSensor); - var driverUpdatesList = new WindowsUpdateInfoCollection(driverUpdates); - var driverUpdatesStr = JsonConvert.SerializeObject(driverUpdatesList, Formatting.Indented); - var driverUpdatesId = $"{parentSensorSafeName}_driver_updates"; - var driverUpdatesSensor = new DataTypeIntSensor(_updateInterval, driverUpdatesId, "Available Driver Updates", driverUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); + var driverUpdatesEntityName = $"{parentSensorSafeName}_driver_updates"; + var driverUpdatesId = $"{Id}_driver_updates"; + var driverUpdatesSensor = new DataTypeIntSensor(_updateInterval, driverUpdatesEntityName, "Available Driver Updates", driverUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); driverUpdatesSensor.SetState(driverUpdates.Count); - driverUpdatesSensor.SetAttributes(driverUpdatesStr); + driverUpdatesSensor.SetAttributes( + JsonConvert.SerializeObject( + new WindowsUpdateInfoCollection(driverUpdates) + , Formatting.Indented) + ); AddUpdateSensor(driverUpdatesId, driverUpdatesSensor); - var softwareUpdatesStr = JsonConvert.SerializeObject(new WindowsUpdateInfoCollection(softwareUpdates), Formatting.Indented); - var softwareUpdatesId = $"{parentSensorSafeName}_software_updates"; - var softwareUpdatesSensor = new DataTypeIntSensor(_updateInterval, softwareUpdatesId, "Available Software Updates", softwareUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); + var softwareUpdatesEntityName = $"{parentSensorSafeName}_software_updates"; + var softwareUpdatesId = $"{Id}_software_updates"; + var softwareUpdatesSensor = new DataTypeIntSensor(_updateInterval, softwareUpdatesEntityName, "Available Software Updates", softwareUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); softwareUpdatesSensor.SetState(softwareUpdates.Count); - softwareUpdatesSensor.SetAttributes(softwareUpdatesStr); + softwareUpdatesSensor.SetAttributes( + JsonConvert.SerializeObject( + new WindowsUpdateInfoCollection(softwareUpdates) + , Formatting.Indented) + ); AddUpdateSensor(softwareUpdatesId, softwareUpdatesSensor); } From c8cb353b6541bea5fb9d9a5e76f71f8db2f518fc Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 16 May 2024 22:03:36 +0200 Subject: [PATCH 28/45] Fix: added lost useAttributes parameter (#92) --- .../Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs index 849a1a28..46b689e3 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs @@ -16,7 +16,7 @@ public class ActiveWindowSensor : AbstractSingleValueSensor private string _processName = string.Empty; - public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { } + public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, useAttributes: true, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { From 8051a8bbbe24be84f2dae1734ed4b858efccde74 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:25:17 +0300 Subject: [PATCH 29/45] Feature: "MultipleKeys" command support for action parameter (#98) This PR adds functionality requested via #97 - action parameter support for "MultipleKeys" command. As of now, the "Key" command has been left unchanged. --- .../Commands/MultipleKeysCommand.cs | 63 ++++++++++++++++++- .../HASS.Agent/Commands/CommandsManager.cs | 2 +- .../HASS.Agent/Forms/Commands/CommandsMod.cs | 14 ++++- .../HASS.Agent/Functions/HelperFunctions.cs | 5 +- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs index 485c80ad..3d56a286 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Models.HomeAssistant; +using HASS.Agent.Shared.Resources.Localization; using Serilog; namespace HASS.Agent.Shared.HomeAssistant.Commands @@ -50,7 +53,7 @@ public override void TurnOff() // } - public override async void TurnOn() + public async override void TurnOn() { try { @@ -73,9 +76,63 @@ public override async void TurnOn() } } - public override void TurnOnWithAction(string action) + public async override void TurnOnWithAction(string action) { - // + var keys = ParseMultipleKeys(action); + if (keys.Count == 0) + return; + + foreach (var key in keys) + { + SendKeys.SendWait(key); + SendKeys.Flush(); + await Task.Delay(50); + } + } + + private List ParseMultipleKeys(string keyString) + { + var keys = new List(); + + try + { + if (string.IsNullOrWhiteSpace(keyString)) + return keys; + + + if (!keyString.Contains('[') || !keyString.Contains(']')) + return keys; + + // replace all escaped brackets + // todo: ugly, let regex do that + keyString = keyString.Replace(@"\[", "left_bracket"); + keyString = keyString.Replace(@"\]", "right_bracket"); + + // lets see if the brackets corresponds + var leftBrackets = keyString.Count(x => x == '['); + var rightBrackets = keyString.Count(x => x == ']'); + + if (leftBrackets != rightBrackets) + return keys; + + // ok, try parsen + var pattern = @"\[(.*?)\]"; + var matches = Regex.Matches(keyString, pattern); + keys.AddRange(from Match m in matches select m.Groups[1].ToString()); + + // restore escaped brackets + for (var i = 0; i < keys.Count; i++) + { + if (keys[i] == "left_bracket") keys[i] = "["; + if (keys[i] == "right_bracket") keys[i] = "]"; + } + } + catch (Exception ex) + { + Log.Error("[MULTIPLEKEYS] [{name}] Error parsing multiple keys: {msg}", EntityName, ex.Message); + } + + return keys; } } } diff --git a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs index 0d4dbc69..f3646e59 100644 --- a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs @@ -423,7 +423,7 @@ internal static void LoadCommandInfo() commandInfoCard = new CommandInfoCard(CommandType.MultipleKeysCommand, Languages.CommandsManager_MultipleKeysCommandDescription, - true, false, false); + true, false, true); CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs index 1bdbd978..f494dbb9 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs @@ -353,7 +353,19 @@ private void BtnStore_Click(object sender, EventArgs e) return; } - Command.Keys = keys; + + if (keys.Count == 0) + { + var q = MessageBoxAdv.Show(this, Languages.CommandsMod_BtnStore_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (q != DialogResult.Yes) + { + ActiveControl = TbSetting; + + return; + } + } + + Command.Keys = keys; break; case CommandType.LaunchUrlCommand: diff --git a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs index 877b4e57..db1138ef 100644 --- a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs +++ b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs @@ -236,10 +236,7 @@ internal static bool ParseMultipleKeys(string keyString, out List keys, { // any value? if (string.IsNullOrWhiteSpace(keyString)) - { - errorMsg = Languages.HelperFunctions_ParseMultipleKeys_ErrorMsg1; - return false; - } + return true; // any brackets? if (!keyString.Contains('[') || !keyString.Contains(']')) From 8e6727272fb1dc8c75602164dce7ad2ed5d21d31 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:25:56 +0200 Subject: [PATCH 30/45] Fix: audio library/sleep management/playback (#101) This PR: changes the audio library to NAudio fixed issue with media playback using deprecated method that occasionally resulted in access memory violation adds additional logic to handle audio management after system has been put to sleep --- .../HASS.Agent.Shared.csproj | 2 +- .../Managers/Audio/AudioManager.cs | 227 ++++++++++++------ .../Internal/CPolicyConfigVistaClient.cs | 2 +- .../Audio/Internal/DeviceChangeEventArgs.cs | 50 ++++ .../Audio/Internal/IPolicyConfigVista.cs | 2 +- .../Audio/Internal/InternalAudioDevice.cs | 11 +- .../Audio/Internal/InternalAudioSession.cs | 47 ++-- .../Internal/InternalAudioSessionManager.cs | 29 ++- .../Audio/Internal/MMNotificationClient.cs | 43 ++++ .../HASS.Agent/Managers/SystemStateManager.cs | 7 + .../HASS.Agent/Media/MediaManager.cs | 3 +- 11 files changed, 298 insertions(+), 125 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs create mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index d142df54..6b665622 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -33,11 +33,11 @@ - + diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs index 8f0cea0d..ce7e246d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs @@ -5,17 +5,19 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using CSCore.CoreAudioAPI; +using NAudio.CoreAudioApi; using HASS.Agent.Shared.Managers.Audio.Exceptions; using HASS.Agent.Shared.Managers.Audio.Internal; using Serilog; +using NAudio.CoreAudioApi.Interfaces; namespace HASS.Agent.Shared.Managers.Audio; public static class AudioManager { private static bool _initialized = false; - private static readonly MMDeviceEnumerator _enumerator = new(); + private static MMDeviceEnumerator _enumerator = null; + private static MMNotificationClient _notificationClient = null; private static readonly ConcurrentDictionary _devices = new(); private static readonly ConcurrentQueue _devicesToBeRemoved = new(); @@ -23,57 +25,65 @@ public static class AudioManager private static readonly Dictionary _applicationNameCache = new(); - public static void Initialize() + private static void InitializeDevices() { - Log.Debug("[AUDIOMGR] Audio Manager initializing"); + _enumerator = new MMDeviceEnumerator(); - foreach (var device in _enumerator.EnumAudioEndpoints(DataFlow.All, DeviceState.Active)) - _devices[device.DeviceID] = new InternalAudioDevice(device); + foreach (var device in _enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active)) + _devices[device.ID] = new InternalAudioDevice(device); - var nc = new MMNotificationClient(_enumerator); - nc.DeviceAdded += (sender, e) => - { - if (_devices.ContainsKey(e.DeviceId)) - return; + _notificationClient = new MMNotificationClient(); + _notificationClient.DeviceAdded += DeviceAdded; + _notificationClient.DeviceRemoved += DeviceRemoved; + _notificationClient.DeviceStateChanged += DeviceStateChanged; + _enumerator.RegisterEndpointNotificationCallback(_notificationClient); - _devicesToBeAdded.Enqueue(e.DeviceId); - }; + _initialized = true; + } - nc.DeviceRemoved += (sender, e) => + private static void DeviceStateChanged(object sender, DeviceStateChangedEventArgs e) + { + switch (e.DeviceState) { - _devicesToBeRemoved.Enqueue(e.DeviceId); - }; + case DeviceState.Active: + if (_devices.ContainsKey(e.DeviceId)) + return; - _initialized = true; + _devicesToBeAdded.Enqueue(e.DeviceId); + break; - nc.DeviceStateChanged += (sender, e) => - { - switch (e.DeviceState) - { - case DeviceState.Active: - if (_devices.ContainsKey(e.DeviceId)) - return; + case DeviceState.NotPresent: + case DeviceState.Unplugged: + case DeviceState.Disabled: + _devicesToBeRemoved.Enqueue(e.DeviceId); + break; - _devicesToBeAdded.Enqueue(e.DeviceId); - break; + default: + break; + } + } - case DeviceState.NotPresent: - case DeviceState.UnPlugged: - _devicesToBeRemoved.Enqueue(e.DeviceId); - break; + private static void DeviceRemoved(object sender, DeviceNotificationEventArgs e) + { + _devicesToBeRemoved.Enqueue(e.DeviceId); + } - default: - break; - } - }; + private static void DeviceAdded(object sender, DeviceNotificationEventArgs e) + { + if (_devices.ContainsKey(e.DeviceId)) + return; - Log.Information("[AUDIOMGR] Audio Manager initialized"); + _devicesToBeAdded.Enqueue(e.DeviceId); } - private static void CheckInitialization() + private static bool CheckInitialization() { if (!_initialized) - throw new InvalidOperationException("AudioManager is not initialized"); + { + Log.Warning("[AUDIOMGR] not yet initialized!"); + + return false; + } while (_devicesToBeRemoved.TryDequeue(out var deviceId)) { @@ -86,6 +96,8 @@ private static void CheckInitialization() var device = _enumerator.GetDevice(deviceId); _devices[deviceId] = new InternalAudioDevice(device); } + + return true; } private static string GetSessionDisplayName(InternalAudioSession session) @@ -125,10 +137,10 @@ private static List GetDeviceSessions(InternalAudioDevice internal Id = sessionId, Application = displayName, PlaybackDevice = internalAudioDevice.FriendlyName, - Muted = session.Volume.IsMuted, - Active = session.Control.SessionState == AudioSessionState.AudioSessionStateActive, - MasterVolume = Convert.ToInt32(session.Volume.MasterVolume * 100), - PeakVolume = Convert.ToDouble(session.MeterInformation.PeakValue * 100) + Muted = session.Volume.Mute, + Active = session.Control.State == AudioSessionState.AudioSessionStateActive, + MasterVolume = Convert.ToInt32(session.Volume.Volume * 100), + PeakVolume = Convert.ToDouble(session.MeterInformation.MasterPeakValue * 100) }; audioSessions.Add(audioSession); @@ -149,43 +161,91 @@ private static string GetReadableState(DeviceState state) DeviceState.Active => "ACTIVE", DeviceState.Disabled => "DISABLED", DeviceState.NotPresent => "NOT PRESENT", - DeviceState.UnPlugged => "UNPLUGGED", + DeviceState.Unplugged => "UNPLUGGED", DeviceState.All => "STATEMASK_ALL", _ => "UNKNOWN" }; } - public static List GetDevices() + public static void Initialize() { - CheckInitialization(); + Log.Debug("[AUDIOMGR] initializing"); - var audioDevices = new List(); + InitializeDevices(); + + Log.Information("[AUDIOMGR] initialized"); + } + + public static void ReInitialize() + { + if (_initialized) + CleanupDevices(); + + Log.Debug("[AUDIOMGR] re-initializing"); + + InitializeDevices(); + + Log.Information("[AUDIOMGR] re-initialized"); + } - using var defaultInputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - using var defaultOutputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + public static void CleanupDevices() + { + Log.Debug("[AUDIOMGR] starting cleanup"); + _initialized = false; + + foreach (var device in _devices.Values) + device.Dispose(); + + _notificationClient.DeviceAdded -= DeviceAdded; + _notificationClient.DeviceRemoved -= DeviceRemoved; + _notificationClient.DeviceStateChanged -= DeviceStateChanged; + _enumerator.UnregisterEndpointNotificationCallback(_notificationClient); - var defaultInputDeviceId = defaultInputDevice.DeviceID; - var defaultOutputDeviceId = defaultOutputDevice.DeviceID; + _enumerator.Dispose(); + + _devices.Clear(); + Log.Debug("[AUDIOMGR] cleanup completed"); + } + + public static List GetDevices() + { + var audioDevices = new List(); - foreach (var device in _devices.Values.Where(d => d.MMDevice.DeviceState == DeviceState.Active)) + if (!CheckInitialization()) + return audioDevices; + + try { + using var defaultInputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + using var defaultOutputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - var audioSessions = GetDeviceSessions(device); - var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); + var defaultInputDeviceId = defaultInputDevice.ID; + var defaultOutputDeviceId = defaultOutputDevice.ID; - var audioDevice = new AudioDevice + foreach (var device in _devices.Values.Where(d => d.MMDevice.State == DeviceState.Active)) { - State = GetReadableState(device.MMDevice.DeviceState), - Type = device.MMDevice.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, - Id = device.DeviceId, - FriendlyName = device.FriendlyName, - Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.GetMasterVolumeLevelScalar() * 100, 0)), - PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, - Sessions = audioSessions, - Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId - }; - - audioDevices.Add(audioDevice); + + var audioSessions = GetDeviceSessions(device); + var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); + + var audioDevice = new AudioDevice + { + State = GetReadableState(device.MMDevice.State), + Type = device.MMDevice.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, + Id = device.DeviceId, + FriendlyName = device.FriendlyName, + Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0)), + PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, + Sessions = audioSessions, + Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId + }; + + audioDevices.Add(audioDevice); + } + } + catch (Exception ex) + { + Log.Debug(ex, "[AUDIOMGR] Failed to retrieve devices: {msg}", ex.Message); } return audioDevices; @@ -193,16 +253,22 @@ public static List GetDevices() public static string GetDefaultDeviceId(DeviceType deviceType, DeviceRole deviceRole) { + if (!CheckInitialization()) + return string.Empty; + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; var role = (Role)deviceRole; using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); - return defaultDevice.DeviceID; + return defaultDevice.ID; } public static void Activate(AudioDevice audioDevice) { + if (!CheckInitialization()) + return; + if (!_devices.TryGetValue(audioDevice.Id, out var device)) return; @@ -211,15 +277,21 @@ public static void Activate(AudioDevice audioDevice) public static void SetVolume(AudioDevice audioDevice, int volume) { + if (!CheckInitialization()) + return; + if (!_devices.TryGetValue(audioDevice.Id, out var device)) return; var volumeScalar = volume / 100f; - device.AudioEndpointVolume.SetMasterVolumeLevelScalar(volumeScalar, Guid.Empty); + device.AudioEndpointVolume.MasterVolumeLevelScalar = volumeScalar; } public static void SetVolume(AudioSession audioSession, int volume) { + if (!CheckInitialization()) + return; + var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); if (device == null) return; @@ -228,19 +300,25 @@ public static void SetVolume(AudioSession audioSession, int volume) return; var volumeScalar = volume / 100f; - session.Volume.MasterVolume = volumeScalar; + session.Volume.Volume = volumeScalar; } public static void SetMute(AudioDevice audioDevice, bool mute) { + if (!CheckInitialization()) + return; + if (!_devices.TryGetValue(audioDevice.Id, out var device)) return; - device.AudioEndpointVolume.SetMute(mute, Guid.Empty); + device.AudioEndpointVolume.Mute = mute; } public static void SetMute(AudioSession audioSession, bool mute) { + if (!CheckInitialization()) + return; + var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); if (device == null) return; @@ -248,23 +326,20 @@ public static void SetMute(AudioSession audioSession, bool mute) if (!device.Manager.Sessions.TryGetValue(audioSession.Id, out var session)) return; - session.Volume.IsMuted = mute; + session.Volume.Mute = mute; } public static void Shutdown() { - Log.Debug("[AUDIOMGR] Audio Manager shutting down"); + Log.Debug("[AUDIOMGR] shutting down"); try { - foreach(var device in _devices.Values) - device.Dispose(); - - _enumerator.Dispose(); + CleanupDevices(); } catch (Exception ex) { - Log.Fatal(ex, "[AUDIOMGR] Audio Manager shutdown fatal error: {ex}", ex.Message); + Log.Fatal(ex, "[AUDIOMGR] shutdown fatal error: {ex}", ex.Message); } - Log.Debug("[AUDIOMGR] Audio Manager shutdown completed"); + Log.Debug("[AUDIOMGR] shutdown completed"); } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs index 73db3317..9eed7840 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using CSCore.CoreAudioAPI; +using NAudio.CoreAudioApi; namespace HASS.Agent.Shared.Managers.Audio.Internal; diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs new file mode 100644 index 00000000..177cf4e3 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +internal class DeviceNotificationEventArgs : EventArgs +{ + public string DeviceId { get; private set; } + + public DeviceNotificationEventArgs(string deviceId) + { + DeviceId = deviceId; + } +} + +internal class DefaultDeviceChangedEventArgs : DeviceNotificationEventArgs +{ + public DataFlow DataFlow { get; private set; } + public Role Role { get; private set; } + + public DefaultDeviceChangedEventArgs(string deviceId, DataFlow dataFlow, Role role) : base(deviceId) + { + DataFlow = dataFlow; + Role = role; + } +} + +internal class DeviceStateChangedEventArgs : DeviceNotificationEventArgs +{ + public DeviceState DeviceState { get; private set; } + + public DeviceStateChangedEventArgs(string deviceId, DeviceState deviceState) : base(deviceId) + { + DeviceState = deviceState; + } +} + +internal class DevicePropertyChangedEventArgs : DeviceNotificationEventArgs +{ + public PropertyKey PropertyKey { get; private set; } + + public DevicePropertyChangedEventArgs(string deviceId, PropertyKey propertyKey) : base(deviceId) + { + PropertyKey = propertyKey; + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs index e430e102..b6d91b92 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using CSCore.CoreAudioAPI; +using NAudio.CoreAudioApi; namespace HASS.Agent.Shared.Managers.Audio.Internal; diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs index 59721484..b477c2d9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using CSCore.CoreAudioAPI; +using NAudio.CoreAudioApi; namespace HASS.Agent.Shared.Managers.Audio.Internal; internal class InternalAudioDevice : IDisposable @@ -19,18 +19,17 @@ internal class InternalAudioDevice : IDisposable public InternalAudioDevice(MMDevice device) { MMDevice = device; - var sessionManager2 = AudioSessionManager2.FromMMDevice(device); - Manager = new InternalAudioSessionManager(sessionManager2); - AudioEndpointVolume = AudioEndpointVolume.FromDevice(device); + Manager = new InternalAudioSessionManager(device.AudioSessionManager); + AudioEndpointVolume = device.AudioEndpointVolume; - DeviceId = device.DeviceID; + DeviceId = device.ID; FriendlyName = device.FriendlyName; } public void Activate() { using var configClient = new CPolicyConfigVistaClient(); - configClient.SetDefaultDevice(MMDevice.DeviceID); + configClient.SetDefaultDevice(MMDevice.ID); } public void Dispose() diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs index 480b40c4..9e9c210d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs @@ -3,78 +3,71 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using CSCore.CoreAudioAPI; +using NAudio.CoreAudioApi; +using NAudio.CoreAudioApi.Interfaces; namespace HASS.Agent.Shared.Managers.Audio.Internal; -internal class InternalAudioSession : IDisposable, IAudioSessionEvents +internal class InternalAudioSession : IDisposable, IAudioSessionEventsHandler { public AudioSessionControl Control { get; private set; } - public AudioSessionControl2 Control2 { get; private set; } public SimpleAudioVolume Volume { get; private set; } public AudioMeterInformation MeterInformation { get; private set; } public string DisplayName { get; private set; } public int ProcessId { get; private set; } + public bool Expired { get; private set; } = false; public InternalAudioSession(AudioSessionControl audioSessionControl) { Control = audioSessionControl; - Control2 = audioSessionControl.QueryInterface(); - Volume = audioSessionControl.QueryInterface(); - MeterInformation = audioSessionControl.QueryInterface(); + Volume = audioSessionControl.SimpleAudioVolume; + MeterInformation = audioSessionControl.AudioMeterInformation; - DisplayName = Control2.DisplayName; - ProcessId = Control2.ProcessID; + DisplayName = Control.DisplayName; + ProcessId = Convert.ToInt32(Control.GetProcessID); - Control.RegisterAudioSessionNotification(this); + Control.RegisterEventClient(this); } public void Dispose() { - Control.UnregisterAudioSessionNotification(this); + Control.UnRegisterEventClient(this); - MeterInformation?.Dispose(); Volume?.Dispose(); - Control2?.Dispose(); Control?.Dispose(); GC.SuppressFinalize(this); } - ~InternalAudioSession() - { - Dispose(); - } - - public void OnDisplayNameChanged(string newDisplayName, ref Guid eventContext) + public void OnVolumeChanged(float volume, bool isMuted) { } - public void OnIconPathChanged(string newIconPath, ref Guid eventContext) + public void OnDisplayNameChanged(string displayName) { } - public void OnSimpleVolumeChanged(float newVolume, bool newMute, ref Guid eventContext) + public void OnIconPathChanged(string iconPath) { } - public void OnChannelVolumeChanged(int channelCount, float[] newChannelVolumeArray, int changedChannel, ref Guid eventContext) + public void OnChannelVolumeChanged(uint channelCount, nint newVolumes, uint channelIndex) { } - public void OnGroupingParamChanged(ref Guid newGroupingParam, ref Guid eventContext) + public void OnGroupingParamChanged(ref Guid groupingId) { } - public void OnStateChanged(AudioSessionState newState) + public void OnStateChanged(AudioSessionState state) { - if (newState == AudioSessionState.AudioSessionStateExpired) + if (state == AudioSessionState.AudioSessionStateExpired) Expired = true; } @@ -82,4 +75,10 @@ public void OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason) { } + + ~InternalAudioSession() + { + Dispose(); + } + } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs index 0ebab737..c201b427 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs @@ -4,39 +4,40 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using CSCore.CoreAudioAPI; +using NAudio.CoreAudioApi; namespace HASS.Agent.Shared.Managers.Audio.Internal; internal class InternalAudioSessionManager : IDisposable { - public AudioSessionManager2 Manager { get; private set; } + public AudioSessionManager Manager { get; private set; } public ConcurrentDictionary Sessions { get; private set; } = new(); - public InternalAudioSessionManager(AudioSessionManager2 sessionManager2) + public InternalAudioSessionManager(AudioSessionManager sessionManager2) { Manager = sessionManager2; - using var sessionEnumerator = Manager.GetSessionEnumerator(); - foreach (var session in sessionEnumerator) + var sessions = Manager.Sessions; + for (var i = 0; i < sessions.Count; i++) { + var session = sessions[i]; var internalSession = new InternalAudioSession(session); - Sessions[internalSession.Control2.SessionInstanceIdentifier] = internalSession; + Sessions[internalSession.Control.GetSessionInstanceIdentifier] = internalSession; } - Manager.SessionCreated += Manager_SessionCreated; + Manager.OnSessionCreated += Manager_OnSessionCreated; } - private void Manager_SessionCreated(object? sender, SessionCreatedEventArgs e) + private void Manager_OnSessionCreated(object sender, NAudio.CoreAudioApi.Interfaces.IAudioSessionControl newSession) { - if (e.NewSession != null) + if (newSession != null) { - var internalSession = new InternalAudioSession(e.NewSession); - Sessions[internalSession.Control2.SessionInstanceIdentifier] = internalSession; + var internalSession = new InternalAudioSession(new AudioSessionControl(newSession)); + Sessions[internalSession.Control.GetSessionInstanceIdentifier] = internalSession; } } public void RemoveDisconnectedSessions() { - var expiredSessionsId = Sessions.Values.Where(s => s.Expired).Select(s => s.Control2.SessionInstanceIdentifier); + var expiredSessionsId = Sessions.Values.Where(s => s.Expired).Select(s => s.Control.GetSessionInstanceIdentifier); if (!expiredSessionsId.Any()) return; @@ -50,12 +51,10 @@ public void RemoveDisconnectedSessions() public void Dispose() { if (Manager != null) - Manager.SessionCreated -= Manager_SessionCreated; + Manager.OnSessionCreated -= Manager_OnSessionCreated; foreach (var session in Sessions.Values) - { session?.Dispose(); - } Manager?.Dispose(); diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs new file mode 100644 index 00000000..acc3c66c --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi.Interfaces; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +internal class MMNotificationClient : IMMNotificationClient +{ + public event EventHandler DeviceStateChanged; + public event EventHandler DeviceAdded; + public event EventHandler DeviceRemoved; + public event EventHandler DefaultDeviceChanged; + public event EventHandler DevicePropertyChanged; + + void IMMNotificationClient.OnDeviceStateChanged(string deviceId, DeviceState deviceState) + { + DeviceStateChanged?.Invoke(this, new DeviceStateChangedEventArgs(deviceId, deviceState)); + } + + void IMMNotificationClient.OnDeviceAdded(string deviceId) + { + DeviceAdded?.Invoke(this, new DeviceNotificationEventArgs(deviceId)); + } + + void IMMNotificationClient.OnDeviceRemoved(string deviceId) + { + DeviceRemoved?.Invoke(this, new DeviceNotificationEventArgs(deviceId)); + } + + void IMMNotificationClient.OnDefaultDeviceChanged(DataFlow dataFlow, Role role, string deviceId) + { + DefaultDeviceChanged?.Invoke(this, new DefaultDeviceChangedEventArgs(deviceId, dataFlow, role)); + } + + void IMMNotificationClient.OnPropertyValueChanged(string deviceId, PropertyKey key) + { + DevicePropertyChanged?.Invoke(this, new DevicePropertyChangedEventArgs(deviceId, key)); + } +} diff --git a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs index 22fbfb1e..a8794708 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using HASS.Agent.Functions; using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers.Audio; using Microsoft.Win32; using Serilog; @@ -200,6 +201,9 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Log.Information("[SYSTEMSTATE] Session resuming"); Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync()); LastSystemStateEvent = SystemStateEvent.Resume; + + AudioManager.ReInitialize(); + break; case PowerModes.Suspend: @@ -209,6 +213,9 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Log.Information("[SYSTEMSTATE] Session halting: system suspending"); Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync(true)); LastSystemStateEvent = SystemStateEvent.Suspend; + + AudioManager.CleanupDevices(); + break; } } diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs index 08b97f7a..fd15f99d 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs @@ -12,6 +12,7 @@ using Serilog; using MediaPlayerState = HASS.Agent.Enums.MediaPlayerState; using Octokit; +using Windows.Media.Core; namespace HASS.Agent.Media { @@ -406,7 +407,7 @@ internal static async void ProcessMedia(string mediaUri) if (Variables.MediaPlayer.CurrentState == Windows.Media.Playback.MediaPlayerState.Playing) Variables.MediaPlayer.Pause(); // set the uri source - Variables.MediaPlayer.SetUriSource(new Uri(localFile)); + Variables.MediaPlayer.Source = MediaSource.CreateFromUri(new Uri(localFile)); if (Variables.ExtendedLogging) Log.Information("[MEDIA] Playing: {file}", Path.GetFileName(localFile)); From bd03bf5413f65d527bb942d6f17b6a94538fa70c Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sun, 16 Jun 2024 22:54:37 +0200 Subject: [PATCH 31/45] added missing audio device mute attribute assignment; added missing media player mute assignment (#102) This PR fixes: audio devices returned from AudioManager not having "Mute" attribute assigned properly media player not reporting mute status --- .../Managers/Audio/AudioManager.cs | 1 + .../HASS.Agent/Media/MediaManager.cs | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs index ce7e246d..b6358d45 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs @@ -235,6 +235,7 @@ public static List GetDevices() Id = device.DeviceId, FriendlyName = device.FriendlyName, Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0)), + Muted = device.MMDevice.AudioEndpointVolume.Mute, PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, Sessions = audioSessions, Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs index fd15f99d..9dae42ec 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs @@ -13,6 +13,7 @@ using MediaPlayerState = HASS.Agent.Enums.MediaPlayerState; using Octokit; using Windows.Media.Core; +using HASS.Agent.Shared.Managers.Audio; namespace HASS.Agent.Media { @@ -38,7 +39,7 @@ internal static async Task InitializeAsync() Log.Information("[MEDIA] Disabled"); return; } - + if (!Variables.AppSettings.LocalApiEnabled && !Variables.AppSettings.MqttEnabled) { Log.Warning("[MEDIA] Both local API and MQTT are disabled, unable to receive media requests"); @@ -70,7 +71,7 @@ internal static async Task InitializeAsync() } catch (Exception ex) { - Log.Fatal(ex, "[MEDIA] Unable to initialize: {err}" , ex.Message); + Log.Fatal(ex, "[MEDIA] Unable to initialize: {err}", ex.Message); Variables.AppSettings.MediaPlayerEnabled = false; Log.Warning("[MEDIA] Failed, disabled"); @@ -79,7 +80,7 @@ internal static async Task InitializeAsync() // start monitoring playing media _ = Task.Run(MediaMonitor); - + if (!Variables.AppSettings.MqttEnabled) Log.Warning("[MEDIA] MQTT is disabled, only basic media functionality will work"); else { @@ -191,7 +192,8 @@ private static async void MediaMonitor() message.AlbumArtist = mediaProperties.AlbumArtist; message.AlbumTitle = mediaProperties.AlbumTitle; message.Volume = MediaManagerRequests.GetVolume(); - + message.Muted = AudioManager.GetDevices().FirstOrDefault(d => d.Type == DeviceType.Output && d.Default)?.Muted == true; + // get timeline info var timeline = session.GetTimelineProperties(); if (timeline != null) @@ -243,9 +245,9 @@ private static GlobalSystemMediaTransportControlsSession GetCurrentMediaSession( // if none are playing: pick the first if (sessions.Count == 1) return sessions[0]; - - return sessions.Any(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) - ? sessions.First(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) + + return sessions.Any(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) + ? sessions.First(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) : sessions[0]; } @@ -331,7 +333,8 @@ internal static void ProcessCommand(MediaPlayerCommand command) case MediaPlayerCommand.Play: if (Variables.ExtendedLogging) Log.Information("[MEDIA] Command received: Play"); - if (State == MediaPlayerState.Playing) { + if (State == MediaPlayerState.Playing) + { if (Variables.ExtendedLogging) Log.Warning("[MEDIA] Media already playing"); break; } @@ -340,7 +343,8 @@ internal static void ProcessCommand(MediaPlayerCommand command) case MediaPlayerCommand.Pause: if (Variables.ExtendedLogging) Log.Information("[MEDIA] Command received: Pause"); - if (State == MediaPlayerState.Paused) { + if (State == MediaPlayerState.Paused) + { if (Variables.ExtendedLogging) Log.Warning("[MEDIA] Media already paused"); break; } From c63e485edb24e3765d0acec4dc9849756ba0f733 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:40:52 +0200 Subject: [PATCH 32/45] Feature: separate installer for client and satellite (#103) This PR separates the installer into two different ones (embedded) that allows for non-privileged user installation. --- .github/workflows/build.yml | 7 +- .../AfterInstallNotice-Service.rtf | 222 ++++++++++++++++++ .../BeforeInstallNotice-Service.rtf | 218 +++++++++++++++++ .../InstallerScript-Service.iss | 82 +++++++ src/HASS.Agent.Installer/InstallerScript.iss | 54 ++--- .../HASS.Agent.Satellite.Service.csproj | 2 +- .../HASS.Agent.Shared.csproj | 2 +- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 2 +- 8 files changed, 555 insertions(+), 34 deletions(-) create mode 100644 src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf create mode 100644 src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf create mode 100644 src/HASS.Agent.Installer/InstallerScript-Service.iss diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 329c2c9c..53104d3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,7 +120,12 @@ jobs: working-directory: "D:\\a\\HASS.Agent\\HASS.Agent\\src\\HASS.Agent\\HASS.Agent.Satellite.Service" run: "dotnet publish -c Release -f net6.0-windows10.0.19041.0 -o bin\\Publish-x64\\Release\\ --no-self-contained -r win-x64 -p:Platform=x64" - - name: Compile InnoSetup Installer + - name: Compile InnoSetup Installer - Satellite Service + working-directory: "D:\\a\\HASS.Agent\\HASS.Agent\\src\\HASS.Agent.Installer" + run: | + & "${Env:ProgramFiles(x86)}\\Inno Setup 6\\iscc.exe" InstallerScript-Service.iss + + - name: Compile InnoSetup Installer - Client working-directory: "D:\\a\\HASS.Agent\\HASS.Agent\\src\\HASS.Agent.Installer" run: | & "${Env:ProgramFiles(x86)}\\Inno Setup 6\\iscc.exe" InstallerScript.iss diff --git a/src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf b/src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf new file mode 100644 index 00000000..e0517548 --- /dev/null +++ b/src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf @@ -0,0 +1,222 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang2057\deflangfe2057\themelang1045\themelangfe0\themelangcs1025{\fonttbl{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\f34\fbidi \froman\fcharset238\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f43\fbidi \fswiss\fcharset238\fprq2{\*\panose 00000000000000000000}Tahoma;} +{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \fswiss\fcharset238\fprq2{\*\panose 020b0604020202020204}Arial;}{\f46\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\f45\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f47\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f48\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f49\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\f50\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f51\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f52\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f386\fbidi \froman\fcharset0\fprq2 Cambria Math;} +{\f385\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f387\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f388\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f391\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;} +{\f392\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f476\fbidi \fswiss\fcharset0\fprq2 Tahoma;}{\f475\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr;}{\f477\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek;} +{\f478\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur;}{\f479\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);}{\f480\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f481\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic;} +{\f482\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f483\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;} +{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;} +{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;} +{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31550\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);} +{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);} +{\fbiminor\f31580\fbidi \fswiss\fcharset0\fprq2 Arial;}{\fbiminor\f31579\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;}{\fbiminor\f31581\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\fbiminor\f31582\fbidi \fswiss\fcharset162\fprq2 Arial Tur;} +{\fbiminor\f31583\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\fbiminor\f31584\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\fbiminor\f31585\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;} +{\fbiminor\f31586\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0; +\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0; +\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;}{\*\defchp \f31506\fs22\lang2057\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\upr{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 +\ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid11156637 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid11156637 Unresolved Mention;}{ +\s17\ql \li720\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 +\sbasedon0 \snext17 \sqformat \spriority34 \styrsid5989241 List Paragraph;}}{\*\ud\uc0{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid11156637 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid11156637 Unresolved Mention;}{ +\s17\ql \li720\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 +\sbasedon0 \snext17 \sqformat \spriority34 \styrsid5989241 List Paragraph;}}}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid8367\rsid4147124\rsid5989241\rsid7820185\rsid7821067\rsid8347995\rsid8915765\rsid9249300\rsid11156637\rsid11431173 +\rsid12408085\rsid15815836\rsid15995208\rsid16254539\rsid16263522\rsid16538847}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Amadeo}{\operator Amadeo} +{\creatim\yr2023\mo11\dy27\hr16\min48}{\revtim\yr2024\mo5\dy5\hr20\min11}{\version13}{\edmins110}{\nofpages1}{\nofwords69}{\nofchars394}{\nofcharsws462}{\vern81}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} +\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1 +\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot16254539\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}} +{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9 +\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid16254539 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid11431173 \line }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 Thank you for choosing HASS.Agent! +\par We hope you'll have a seamless and enjoyable experience with our application. If you ever need assistance or have feedback, feel free to reach out to the HASS.Agent team on}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid5989241\charrsid8347995 } +{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid8367 HYPERLINK "https://discord.com/invite/JfZj98xqJr"}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid8367\charrsid8347995 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b64000000680074007400700073003a002f002f0064006900730063006f00720064002e0063006f006d002f0069006e0076006900740065002f004a0066005a006a0039003800780071004a0072000000795881f43b1d +7f48af2c825dc485276300000000a5ab000345}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid5989241\charrsid8347995 Discord}}}\sectd \ltrsect +\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid5989241\charrsid8347995 . +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 Make sure to read through the documentation }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid7820185 HYPERLINK "https://www.hass-agent.io/latest/"}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid7820185\charrsid8347995 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b5c000000680074007400700073003a002f002f007700770077002e0068006100730073002d006100670065006e0074002e0069006f002f006c00610074006500730074002f000000795881f43b1d7f48af2c825dc485 +276300000000a5ab0003000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid16254539\charrsid8347995 here}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj { +\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 to get started configuring HASS.Agent}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16538847 Satellite Service}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid8347995\charrsid8347995 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 Happy exploring! +\par The HASS.Agent Team}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid12408085\charrsid8347995 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210041daf6e5a6070000e6200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f222b524cfda33f660c90e7bac914add65557789aed28cc56208de532e81c026e49085 +bded21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c9f3810326cc0886eed2efbdfad57bafde7baabaffd9ab983a1738e584253db77eafe63a3899b13949 +c29efb7c3aae745c870b94cc116509eeb91bccddcf1efcfa57f7d19188708c1d904ff811eab99110aba36a95cf6018f17b6c8513f86ec1d21809784dc3ea3c45 +97a037a6d546add6aac68824ae93a018d49e32b1b974566849b0583b4f170b32c3ee83ed24230a332582cb81194d27720a9c4b9e29195d64beac4b20dff080a6 +ce05a23d17a69db3cb297e255c87222ee08b9e5b537f6ef5c1fd2a3aca85a83820abc98dd55f2e970bcc970d35671a9e17937a9eefb5fa857e05a0621f376a8f +5aa356a14f01d06c060bceb8983adb8dc0cbb11a287bb4e81eb687cdba81d7f437f738f77df931f00a94e9f7f6f0e371005634f00a94e1fd3dbc3fe80e86a67e +05caf0ad3d7cbbd61f7a6d43bf02459424cb3d74cd6f3583ed6a0bc882d1632bbceb7be37623575ea2201a8a2093532c5822ae08b918bd64e91870124f912089 +23362bbc403388ed0051729e12e7848411c4df0a258cc370ad511bd79af05f7e3cf5a41c8b8e30d2a4253d20c4f786242d87cf52b2123df711687535c8bb9f7f +7efbe6c7b76f7e7afbd5576fdffc3d9f5ba932e48e5112ea72bf7cf787ff7cf35be7df3f7cfbcbd77fcca6dec5731dfffe6fbf7bff8f7f7e483dacb834c5bb3f +7dfffec7efdffdf9f7fffaebd716edfd149debf0298931779ee04be7198b618116fef83cbd99c434424497e82721470992b358f48f4464a09f6c104516dc009b +767c9142c6b1011fae5f1a842751ba16c4a2f171141bc053c6e880a5562b3c967369669eae93d03e79bad671cf10bab0cd1da0c4f0f268bd828c4b6c2a83081b +34cf284a040a71828523bf634b8c2dabfb8210c3aea7649632ce16c2f982380344ac26999273239a4aa16312835f363682e06fc336a72f9c01a3b6550ff18589 +84bd81a885fc1453c38c0fd15aa0d8a6728a62aa1bfc0489c84672b249673a6ec405783ac49439a339e6dc26f33485f56a4e7f0c69c6eef653ba894d642ac8d2 +a6f30431a623876c1944285ed9b01392443af673be841045ce191336f8293377887c073fa0e4a0bb5f402fa04f707536780e1956972803447eb34e2dbe7c8899 +11bf930d5d206c4b35fd3436526c3f25d6e818ac4323b44f30a6e812cd31769e7f6e6130602bc3e625e947116495636c0bac47c88c55f99e608e1dd5e3ece7c9 +13c28d909de0901de073bad9493c1b94c4283da4f909785db7f9084a5d6c0b80a774b6d4814f087486102f56a33ce5a0430bee835acf22641430f9ceedf1ba49 +0dff5d678fc1be7c69d0b8c6be04197c631948ecbacc076d3345d498a00c9829822ec3966e41c4707f29228bab125b5be516e6a62ddd004d92d1f4c424b9b203 +dae97dfcff5def031dc6bbbf7c63d96c1fa7dfb12b3692d50d3b9d43c9e478a7bf3984dbed6a0296cec9a7dfd40cd13a39c35047f633d65d4f73d7d3b8fff73d +cda1fd7cd7c91cea37ee3a19173a8cbb4e263f5cf9389d4cd9bc405f230f3cb2f31e75fa135f75f8b320944ec486e213aece7f38fcac998f61508aabe3505c9c +09ae227894d50ee63170618a948c9332f11b22a249845670485477a59290e7aa43eeac1887b323356cd52df1741d9fb27976f459afcb63ceacc07224caf19a5f +8cc37995c8d0ad76799c57a8576c4375faba2520656f42429bcc24d1b490686f07a591d4592f18cd4242adeca3b0e85a5874a4faadabf65800b5c22bf0bbdb81 +5feb3dd7f7400484e0580e7af4b9f453e6eaad7795333fa6a70f19d38800e8b3b711507aba2bb91e5c9e5c5d166ad7f0b441420b379384b28ceaf37804bf86f3 +e894a3d7a171535f774b971af4a429d47c105a258d76e7432c6eeb6b90dbcd0d34d133054d9ccb9edb6afa103233b4eab90b383b86c77805b1c3e54f2f4443b8 +96998934dbf0b7c92cab948b21e25166709574b26c101381538792b8e7cae5176ea089ca218a5bbd0109e19325d785b4f2a99103a79b4ec68b059e09ddedda88 +b474f60a193ecb15d66f95f8edc15292adc1dd93687ee99cd375fa0c4188f9edba34e09c70b842a867d69c13b81a2b1259197f3b85294fbbfadd948aa16c1cd1 +5584f28aa227f30cae52794147bd1536d0def23583413593e485f03c94055637aa514d8baa9171385875af169296d3926659338dac22aba63d8b19336ccbc08e +2d6f57e435565b13434ed32b7c96ba77536e779beb76fa84a24a80c10bfb59aaee350a8246ad9ccca02619efa76199b3f351b3766c177805b5eb14092debb7b6 +6a77ec56d408eb743078abca0f72bb510b438b6d7ba92cadaed4f5db6e76fe1292c7109add35155cb9122eb153040dd144f52459da802df24ae45b039e9c754a +7aee9735bfef050d3fa8d43afea8e235bd5aa5e3f79b95beef37eb23bf5e1b0e1aafa1b08828aefbd975fe18ee31e826bfd457e37b17fbf1f6aae6de8cc555a6 +aeeaab8ab8bad8af378c8bfdec2adf99ca1b7bd7219074be6c35c6dd6677d0aa749bfd71c51b0e3a956ed01a5486ada03d1c0f03bfd31dbf769d0b05f6facdc0 +6b8d3a95563d082a5eab26e977ba95b6d768f4bd76bf33f2faaff33606569ea58fdc16605ec5ebc17f010000ffff0300504b0304140006000800000021000dd1 +909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277 +086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b +64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996 +509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff +0000001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7 +c0000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000 +001c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210041 +daf6e5a6070000e62000001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d0014000600080000 +0021000dd1909fb60000001b0100002700000000000000000000000000b00a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000ab0b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid; +\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; +\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; +\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; +\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; +\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; +\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; +\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e50000000000000000000000000098 +809f179fda01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf b/src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf new file mode 100644 index 00000000..b9ab19df --- /dev/null +++ b/src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf @@ -0,0 +1,218 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang2057\deflangfe2057\themelang1045\themelangfe0\themelangcs1025{\fonttbl{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\f34\fbidi \froman\fcharset238\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f43\fbidi \fswiss\fcharset238\fprq2{\*\panose 020b0604030504040204}Tahoma;} +{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \fswiss\fcharset238\fprq2{\*\panose 020b0604020202020204}Arial;}{\f46\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\f45\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f47\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f48\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f49\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\f50\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f51\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f52\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f386\fbidi \froman\fcharset0\fprq2 Cambria Math;} +{\f385\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f387\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f388\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f391\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;} +{\f392\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f476\fbidi \fswiss\fcharset0\fprq2 Tahoma;}{\f475\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr;}{\f477\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek;} +{\f478\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur;}{\f479\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);}{\f480\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f481\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic;} +{\f482\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f483\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;} +{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;} +{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;} +{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31550\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);} +{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);} +{\fbiminor\f31580\fbidi \fswiss\fcharset0\fprq2 Arial;}{\fbiminor\f31579\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;}{\fbiminor\f31581\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\fbiminor\f31582\fbidi \fswiss\fcharset162\fprq2 Arial Tur;} +{\fbiminor\f31583\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\fbiminor\f31584\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\fbiminor\f31585\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;} +{\fbiminor\f31586\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0; +\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0; +\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;}{\*\defchp \f31506\fs22\lang2057\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\upr{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 +\ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid490208 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid490208 Unresolved Mention;}} +{\*\ud\uc0{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 +\snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid490208 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid490208 Unresolved Mention;}}}} +{\*\rsidtbl \rsid227266\rsid490208\rsid1248923\rsid2324250\rsid3874449\rsid4072052\rsid5209844\rsid6709929\rsid7356386\rsid7820567\rsid12408085\rsid16263522}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1 +\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Amadeo}{\operator Amadeo}{\creatim\yr2023\mo11\dy27\hr16\min53}{\revtim\yr2024\mo5\dy5\hr20\min10}{\version9}{\edmins14}{\nofpages1}{\nofwords76}{\nofchars435}{\nofcharsws510}{\vern81}} +{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1 +\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot490208\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}} +{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9 +\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid490208 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid227266 \line }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 Welcome to HASS.Agent}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\f43\fs16\insrsid1248923 Satellite Service}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 Installer!}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 This version is maintained by the }{\field{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid2324250 HYPERLINK "https://github.com/hass-agent/" }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \f43\fs16\insrsid2324250 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b56000000680074007400700073003a002f002f006700690074006800750062002e0063006f006d002f0068006100730073002d006100670065006e0074002f000000795881f43b1d7f48af2c825dc485276300000000 +a5ab000300}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid490208\charrsid2324250 HASS.Agent Team}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\rtlch\fcs1 +\af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 and is a fork of the }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 HYPERLINK "https://github.com/LAB02-Research/HASS.Agent" }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b72000000680074007400700073003a002f002f006700690074006800750062002e0063006f006d002f004c0041004200300032002d00520065007300650061007200630068002f0048004100530053002e0041006700 +65006e0074000000795881f43b1d7f48af2c825dc485276300000000a5ab00030064d96621af0000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid490208\charrsid4072052 original}}}\sectd \ltrsect +\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 version by LAB02-Research. +\par The intention of this version is to provide bug-fix and feature updated until the development of original version is resumed. +\par We've tried our best to maintain compatibility with the original version but some breaking changes are unavoidable.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid12408085\charrsid4072052 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210041daf6e5a6070000e6200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f222b524cfda33f660c90e7bac914add65557789aed28cc56208de532e81c026e49085 +bded21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c9f3810326cc0886eed2efbdfad57bafde7baabaffd9ab983a1738e584253db77eafe63a3899b13949 +c29efb7c3aae745c870b94cc116509eeb91bccddcf1efcfa57f7d19188708c1d904ff811eab99110aba36a95cf6018f17b6c8513f86ec1d21809784dc3ea3c45 +97a037a6d546add6aac68824ae93a018d49e32b1b974566849b0583b4f170b32c3ee83ed24230a332582cb81194d27720a9c4b9e29195d64beac4b20dff080a6 +ce05a23d17a69db3cb297e255c87222ee08b9e5b537f6ef5c1fd2a3aca85a83820abc98dd55f2e970bcc970d35671a9e17937a9eefb5fa857e05a0621f376a8f +5aa356a14f01d06c060bceb8983adb8dc0cbb11a287bb4e81eb687cdba81d7f437f738f77df931f00a94e9f7f6f0e371005634f00a94e1fd3dbc3fe80e86a67e +05caf0ad3d7cbbd61f7a6d43bf02459424cb3d74cd6f3583ed6a0bc882d1632bbceb7be37623575ea2201a8a2093532c5822ae08b918bd64e91870124f912089 +23362bbc403388ed0051729e12e7848411c4df0a258cc370ad511bd79af05f7e3cf5a41c8b8e30d2a4253d20c4f786242d87cf52b2123df711687535c8bb9f7f +7efbe6c7b76f7e7afbd5576fdffc3d9f5ba932e48e5112ea72bf7cf787ff7cf35be7df3f7cfbcbd77fcca6dec5731dfffe6fbf7bff8f7f7e483dacb834c5bb3f +7dfffec7efdffdf9f7fffaebd716edfd149debf0298931779ee04be7198b618116fef83cbd99c434424497e82721470992b358f48f4464a09f6c104516dc009b +767c9142c6b1011fae5f1a842751ba16c4a2f171141bc053c6e880a5562b3c967369669eae93d03e79bad671cf10bab0cd1da0c4f0f268bd828c4b6c2a83081b +34cf284a040a71828523bf634b8c2dabfb8210c3aea7649632ce16c2f982380344ac26999273239a4aa16312835f363682e06fc336a72f9c01a3b6550ff18589 +84bd81a885fc1453c38c0fd15aa0d8a6728a62aa1bfc0489c84672b249673a6ec405783ac49439a339e6dc26f33485f56a4e7f0c69c6eef653ba894d642ac8d2 +a6f30431a623876c1944285ed9b01392443af673be841045ce191336f8293377887c073fa0e4a0bb5f402fa04f707536780e1956972803447eb34e2dbe7c8899 +11bf930d5d206c4b35fd3436526c3f25d6e818ac4323b44f30a6e812cd31769e7f6e6130602bc3e625e947116495636c0bac47c88c55f99e608e1dd5e3ece7c9 +13c28d909de0901de073bad9493c1b94c4283da4f909785db7f9084a5d6c0b80a774b6d4814f087486102f56a33ce5a0430bee835acf22641430f9ceedf1ba49 +0dff5d678fc1be7c69d0b8c6be04197c631948ecbacc076d3345d498a00c9829822ec3966e41c4707f29228bab125b5be516e6a62ddd004d92d1f4c424b9b203 +dae97dfcff5def031dc6bbbf7c63d96c1fa7dfb12b3692d50d3b9d43c9e478a7bf3984dbed6a0296cec9a7dfd40cd13a39c35047f633d65d4f73d7d3b8fff73d +cda1fd7cd7c91cea37ee3a19173a8cbb4e263f5cf9389d4cd9bc405f230f3cb2f31e75fa135f75f8b320944ec486e213aece7f38fcac998f61508aabe3505c9c +09ae227894d50ee63170618a948c9332f11b22a249845670485477a59290e7aa43eeac1887b323356cd52df1741d9fb27976f459afcb63ceacc07224caf19a5f +8cc37995c8d0ad76799c57a8576c4375faba2520656f42429bcc24d1b490686f07a591d4592f18cd4242adeca3b0e85a5874a4faadabf65800b5c22bf0bbdb81 +5feb3dd7f7400484e0580e7af4b9f453e6eaad7795333fa6a70f19d38800e8b3b711507aba2bb91e5c9e5c5d166ad7f0b441420b379384b28ceaf37804bf86f3 +e894a3d7a171535f774b971af4a429d47c105a258d76e7432c6eeb6b90dbcd0d34d133054d9ccb9edb6afa103233b4eab90b383b86c77805b1c3e54f2f4443b8 +96998934dbf0b7c92cab948b21e25166709574b26c101381538792b8e7cae5176ea089ca218a5bbd0109e19325d785b4f2a99103a79b4ec68b059e09ddedda88 +b474f60a193ecb15d66f95f8edc15292adc1dd93687ee99cd375fa0c4188f9edba34e09c70b842a867d69c13b81a2b1259197f3b85294fbbfadd948aa16c1cd1 +5584f28aa227f30cae52794147bd1536d0def23583413593e485f03c94055637aa514d8baa9171385875af169296d3926659338dac22aba63d8b19336ccbc08e +2d6f57e435565b13434ed32b7c96ba77536e779beb76fa84a24a80c10bfb59aaee350a8246ad9ccca02619efa76199b3f351b3766c177805b5eb14092debb7b6 +6a77ec56d408eb743078abca0f72bb510b438b6d7ba92cadaed4f5db6e76fe1292c7109add35155cb9122eb153040dd144f52459da802df24ae45b039e9c754a +7aee9735bfef050d3fa8d43afea8e235bd5aa5e3f79b95beef37eb23bf5e1b0e1aafa1b08828aefbd975fe18ee31e826bfd457e37b17fbf1f6aae6de8cc555a6 +aeeaab8ab8bad8af378c8bfdec2adf99ca1b7bd7219074be6c35c6dd6677d0aa749bfd71c51b0e3a956ed01a5486ada03d1c0f03bfd31dbf769d0b05f6facdc0 +6b8d3a95563d082a5eab26e977ba95b6d768f4bd76bf33f2faaff33606569ea58fdc16605ec5ebc17f010000ffff0300504b0304140006000800000021000dd1 +909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277 +086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b +64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996 +509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff +0000001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7 +c0000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000 +001c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210041 +daf6e5a6070000e62000001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d0014000600080000 +0021000dd1909fb60000001b0100002700000000000000000000000000b00a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000ab0b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid; +\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; +\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; +\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; +\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; +\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; +\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; +\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000803c +d68e179fda01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss new file mode 100644 index 00000000..694e19ca --- /dev/null +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -0,0 +1,82 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! +; Modified to suit HASS.Agent requirements :) + +; InnoDependencyInstaller +; Thanks to https://github.com/DomGries/InnoDependencyInstaller for the amazing work! +#define public Dependency_Path_NetCoreCheck "dependencies\" +#include "CodeDependencies.iss" + +; Standard installation constants +#define MyAppName "HASS.Agent Satellite Service" +#define MyAppVersion "2.1.0-beta2" +#define MyAppPublisher "HASS.Agent Team" +#define MyAppURL "https://hass-agent.io" +#define MyAppExeName "HASS.Agent.Satellite.Service.exe" +#define ServiceName "hass.agent.svc" +#define ServiceDisplayName "HASS.Agent - Satellite Service" +#define ServiceDescription "Satellite service for HASS.Agent: a Windows based Home Assistant client. This service processes commands and sensors without the requirement of a logged-in user." + +[Setup] +ArchitecturesInstallIn64BitMode=x64 +;SetupMutex=Global\HASS.Agent.Setup.Satellite.Mutex,HASS.Agent.Satellite.Setup.Mutex +AppMutex=Global\\HASS.Agent.Service.Mutex +AppId={{4004588E-F411-41C2-ABD8-A898B0A14B93} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={commonpf64}\{#MyAppName}\Service +DisableProgramGroupPage=yes +LicenseFile=..\..\LICENSE.md +InfoBeforeFile=.\BeforeInstallNotice-Service.rtf +InfoAfterFile=.\AfterInstallNotice-Service.rtf +PrivilegesRequired=admin +OutputDir=.\bin +OutputBaseFilename=HASS.Agent.Service.Installer +SetupIconFile=..\HASS.Agent\HASS.Agent.Shared\hassagent.ico +Compression=lzma +SolidCompression=yes +WizardStyle=modern +CloseApplications=force +CloseApplicationsFilter=*.* +UninstallDisplayIcon={app}\{#MyAppExeName} +UninstallDisplayName={#MyAppName} {#MyAppVersion} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +; NOTE: Don't use "Flags: ignoreversion" on any shared system files +[Files] +; Service files +Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs + +[Run] +Filename: "{sys}\sc.exe"; Parameters: "start {#ServiceName}"; Description: "Start Satellite Service"; Flags: postinstall runhidden runascurrentuser + +[Registry] +Root: HKLM; Subkey: "SOFTWARE\HASSAgent\SatelliteService"; ValueType: string; ValueName: "InstallPath"; ValueData: "{commonpf64}\{#MyAppName}\Service"; Flags: createvalueifdoesntexist uninsdeletevalue + +; Service registration and removal +[Run] +Filename: "{sys}\sc.exe"; Parameters: "create {#ServiceName} binpath= ""{commonpf64}\{#MyAppName}\Service\{#MyAppExeName}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "failure {#ServiceName} reset= 86400 actions= restart/60000/restart/60000//1000"; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "description {#ServiceName} ""{#ServiceDescription}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} DisplayName= ""{#ServiceDisplayName}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} start= auto"; Flags: runhidden +[UninstallRun] +Filename: "{sys}\sc.exe"; Parameters: "stop {#ServiceName}"; RunOnceId: StopService; Flags: runhidden +Filename: "{sys}\timeout.exe"; Parameters: "5"; RunOnceId: Delay1; Flags:runhidden +Filename: "{sys}\sc.exe"; Parameters: "delete {#ServiceName}" ; RunOnceId: DeleteService; Flags: runhidden +Filename: "{sys}\timeout.exe"; Parameters: "5"; RunOnceId: Delay2; Flags:runhidden + +[Code] +function InitializeSetup: Boolean; +begin + Dependency_ForceX86 := False; + Dependency_AddDotNet60Desktop; + Result := True; +end; \ No newline at end of file diff --git a/src/HASS.Agent.Installer/InstallerScript.iss b/src/HASS.Agent.Installer/InstallerScript.iss index 8e7190f5..d1425a74 100644 --- a/src/HASS.Agent.Installer/InstallerScript.iss +++ b/src/HASS.Agent.Installer/InstallerScript.iss @@ -9,19 +9,15 @@ ; Standard installation constants #define MyAppName "HASS.Agent" -#define MyAppVersion "2.1.0-beta1" +#define MyAppVersion "2.1.0-beta2" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.exe" -#define MyAppServiceExeName "HASS.Agent.Satellite.Service.exe" -#define ServiceName "hass.agent.svc" -#define ServiceDisplayName "HASS.Agent - Satellite Service" -#define ServiceDescription "Satellite service for HASS.Agent: a Windows based Home Assistant client. This service processes commands and sensors without the requirement of a logged-in user." [Setup] ArchitecturesInstallIn64BitMode=x64 SetupMutex=Global\HASS.Agent.Setup.Mutex,HASS.Agent.Setup.Mutex -AppMutex=Global\HASS.Agent.App.Mutex,HASS.Agent.App.Mutex +AppMutex=HASS.Agent.App.Mutex AppId={{7BBED458-609B-4D13-AD9E-4FF219DF8644} AppName={#MyAppName} AppVersion={#MyAppVersion} @@ -36,7 +32,7 @@ LicenseFile=..\..\LICENSE.md InfoBeforeFile=.\BeforeInstallNotice.rtf InfoAfterFile=.\AfterInstallNotice.rtf ; Uncomment the following line to run in non administrative install mode (install for current user only.) -;PrivilegesRequired=lowest +PrivilegesRequired=lowest OutputDir=.\bin OutputBaseFilename=HASS.Agent.Installer SetupIconFile=..\HASS.Agent\HASS.Agent.Shared\hassagent.ico @@ -57,38 +53,19 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{ ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Files] ; Client files -;Source: "..\HASS.Agent\HASS.Agent\bin\Publish-x64\Release\*"; Excludes: "*.pdb"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -; Service files -;Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; Excludes: "*.pdb"; DestDir: "{commonpf64}\{#MyAppName}\Service"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\HASS.Agent\HASS.Agent\bin\Publish-x64\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -; Service files -Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; DestDir: "{commonpf64}\{#MyAppName}\Service"; Flags: ignoreversion recursesubdirs createallsubdirs +; Service installer +Source: ".\bin\HASS.Agent.Service.Installer.exe"; DestDir: "{tmp}"; Flags: ignoreversion [Icons] Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon [Run] -Filename: "{app}\{#MyAppExeName}"; Parameters: "compat_migrate"; Description: "Try to migrate configuration"; Flags: postinstall skipifsilent runascurrentuser unchecked -Filename: "{sys}\sc.exe"; Parameters: "start {#ServiceName}"; Description: "Start Satellite Service"; Flags: postinstall runhidden runascurrentuser +Filename: "{app}\{#MyAppExeName}"; Parameters: "compat_migrate"; Description: "Try to migrate configuration (administrative permissions required)"; Flags: postinstall skipifsilent runascurrentuser unchecked +Filename: "{tmp}\HASS.Agent.Service.Installer.exe"; Description: "Install Satellite Service (administrative permissions required)"; Flags: postinstall runascurrentuser Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: postinstall skipifsilent nowait -[Registry] -Root: HKLM; Subkey: "SOFTWARE\HASSAgent\SatelliteService"; ValueType: string; ValueName: "InstallPath"; ValueData: "{commonpf64}\{#MyAppName}\Service"; Flags: createvalueifdoesntexist uninsdeletevalue - -; Service registration and removal -[Run] -Filename: "{sys}\sc.exe"; Parameters: "create {#ServiceName} binpath= ""{commonpf64}\{#MyAppName}\Service\{#MyAppServiceExeName}"""; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "failure {#ServiceName} reset= 86400 actions= restart/60000/restart/60000//1000"; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "description {#ServiceName} ""{#ServiceDescription}"""; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} DisplayName= ""{#ServiceDisplayName}"""; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} start= auto"; Flags: runhidden -[UninstallRun] -Filename: "{sys}\sc.exe"; Parameters: "stop {#ServiceName}"; RunOnceId: StopService; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "delete {#ServiceName}" ; RunOnceId: DeleteService; Flags: runhidden -; Additional delay for the service to be uninstalled -Filename: "{sys}\timeout.exe"; Parameters: "5"; Flags:runhidden - [Code] function InitializeSetup: Boolean; begin @@ -118,3 +95,20 @@ begin Result := True; end end; + +procedure CurUninstallStepChanged (CurUninstallStep: TUninstallStep); +var + mres : integer; + serviceUninstallerPath : String; + ResultCode : integer; +begin + case CurUninstallStep of + usPostUninstall: + begin + mres := MsgBox('Do you want to uninstall the Satellite Service? (administrative permissions required)', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) + if mres = IDYES then + RegQueryStringValue(HKLM, 'SOFTWARE\HASSAgent\SatelliteService', 'InstallPath', serviceUninstallerPath); + Exec(serviceUninstallerPath + '\unins000.exe', '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode); + end; + end; +end; diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index e9e2d70f..243ebf47 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 x64 x64;x86 - 2.1.0-beta1 + 2.1.0-beta2 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index 6b665622..bd41bf7b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -12,7 +12,7 @@ https://github.com/hass-agent/HASS.Agent 2.1.0 2.1.0 - 2.1.0-beta1 + 2.1.0-beta2 logo_128.png True hassagent.ico diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index 4d4b5c16..b4d922eb 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -12,7 +12,7 @@ x64 x64;x86 full - 2.1.0-beta1 + 2.1.0-beta2 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. From 0bd19040d3808c535d4ef09a766b5bbc394e4a25 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 22 Jun 2024 14:35:30 +0200 Subject: [PATCH 33/45] Fix: advanced settings not being saved for satellite sensors (#104) --- .../Extensions/RpcExtensions.cs | 6 ++++-- .../RPC/Protos/hassagentsatellite.proto | 1 + src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs | 6 ++++-- .../HASS.Agent/Service/Protos/hassagentsatellite.proto | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs index 9275740a..53f3f88b 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs @@ -108,7 +108,8 @@ public static ConfiguredSensor ConvertToConfiguredSensor(this RpcConfiguredServe Counter = rpcConfiguredSensor.Counter, Instance = rpcConfiguredSensor.Instance, EntityName = rpcConfiguredSensor.EntityName, - Name = rpcConfiguredSensor.Name + Name = rpcConfiguredSensor.Name, + AdvancedSettings = rpcConfiguredSensor.AdvancedSettings, }; return configuredSensor; @@ -149,7 +150,8 @@ public static RpcConfiguredServerSensor ConvertToRpcConfiguredSensor(this Config Counter = configuredSensor.Counter ?? string.Empty, Instance = configuredSensor.Instance ?? string.Empty, Name = configuredSensor.Name ?? string.Empty, - EntityName = configuredSensor?.EntityName ?? string.Empty + EntityName = configuredSensor?.EntityName ?? string.Empty, + AdvancedSettings = configuredSensor?.AdvancedSettings ?? string.Empty, }; return configuredRpcSensor; diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto b/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto index 169b6263..ad2d5d68 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto @@ -134,6 +134,7 @@ message RpcConfiguredServerSensor { string instance = 9; string name = 10; string entityName = 11; + string advancedSettings = 12; } message RpcConfiguredServerCommand { diff --git a/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs b/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs index 0ca8550c..3a7acc82 100644 --- a/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs +++ b/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs @@ -109,7 +109,8 @@ public static ConfiguredSensor ConvertToConfiguredSensor(this RpcConfiguredServe Counter = rpcConfiguredSensor.Counter, Instance = rpcConfiguredSensor.Instance, EntityName = rpcConfiguredSensor.EntityName, - Name = rpcConfiguredSensor.Name + Name = rpcConfiguredSensor.Name, + AdvancedSettings = rpcConfiguredSensor.AdvancedSettings, }; return configuredSensor; @@ -150,7 +151,8 @@ public static RpcConfiguredServerSensor ConvertToRpcConfiguredSensor(this Config Counter = configuredSensor.Counter ?? string.Empty, Instance = configuredSensor.Instance ?? string.Empty, Name = configuredSensor.Name ?? string.Empty, - EntityName = configuredSensor.EntityName ?? string.Empty + EntityName = configuredSensor.EntityName ?? string.Empty, + AdvancedSettings = configuredSensor?.AdvancedSettings ?? string.Empty, }; return configuredRpcSensor; diff --git a/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto b/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto index 169b6263..ad2d5d68 100644 --- a/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto +++ b/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto @@ -134,6 +134,7 @@ message RpcConfiguredServerSensor { string instance = 9; string name = 10; string entityName = 11; + string advancedSettings = 12; } message RpcConfiguredServerCommand { From be6a48c1b832c6a03a7452d8a9a443eea5222bc9 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 24 Jun 2024 19:57:16 +0200 Subject: [PATCH 34/45] fixed advanced settings not being saved properly (#105) --- .../HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs | 3 --- .../Models/HomeAssistant/AbstractSingleValueSensor.cs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs index 14bf596d..1b0b5b5b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs @@ -19,8 +19,6 @@ public class WmiQuerySensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int? Round { get; private set; } - public string AdvancedSettings { get; private set; } - protected readonly ObjectQuery ObjectQuery; protected readonly ManagementObjectSearcher Searcher; @@ -30,7 +28,6 @@ public WmiQuerySensor(string query, string scope = "", bool applyRounding = fals Scope = scope; ApplyRounding = applyRounding; Round = round; - AdvancedSettings = advancedSettings; // prepare query ObjectQuery = new ObjectQuery(Query); diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs index eb7ee64c..e3d3f267 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs @@ -33,6 +33,7 @@ protected AbstractSingleValueSensor(string entityName, string name, int updateIn if (!string.IsNullOrWhiteSpace(advancedSettings)) { + AdvancedSettings = advancedSettings; _advancedInfo = JsonConvert.DeserializeObject(advancedSettings); } } From abec36f28a4c5c20664b4b95f93016d77f73a450 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 24 Jun 2024 20:06:58 +0200 Subject: [PATCH 35/45] Fix: added ignoreversion parameter (#107) --- src/HASS.Agent.Installer/InstallerScript-Service.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss index 694e19ca..eb685ee3 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -52,7 +52,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl" ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Files] ; Service files -Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs +Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Run] Filename: "{sys}\sc.exe"; Parameters: "start {#ServiceName}"; Description: "Start Satellite Service"; Flags: postinstall runhidden runascurrentuser From b577482178b8fc75f0ba0a1f5499c0da52009d8d Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:39:37 +0200 Subject: [PATCH 36/45] Fix: satellite service installer not respecting user selected path (#113) --- src/HASS.Agent.Installer/InstallerScript-Service.iss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss index eb685ee3..f90e087d 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -58,11 +58,11 @@ Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; Filename: "{sys}\sc.exe"; Parameters: "start {#ServiceName}"; Description: "Start Satellite Service"; Flags: postinstall runhidden runascurrentuser [Registry] -Root: HKLM; Subkey: "SOFTWARE\HASSAgent\SatelliteService"; ValueType: string; ValueName: "InstallPath"; ValueData: "{commonpf64}\{#MyAppName}\Service"; Flags: createvalueifdoesntexist uninsdeletevalue +Root: HKLM; Subkey: "SOFTWARE\HASSAgent\SatelliteService"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: createvalueifdoesntexist uninsdeletevalue ; Service registration and removal [Run] -Filename: "{sys}\sc.exe"; Parameters: "create {#ServiceName} binpath= ""{commonpf64}\{#MyAppName}\Service\{#MyAppExeName}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "create {#ServiceName} binpath= ""{app}\{#MyAppExeName}"""; Flags: runhidden Filename: "{sys}\sc.exe"; Parameters: "failure {#ServiceName} reset= 86400 actions= restart/60000/restart/60000//1000"; Flags: runhidden Filename: "{sys}\sc.exe"; Parameters: "description {#ServiceName} ""{#ServiceDescription}"""; Flags: runhidden Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} DisplayName= ""{#ServiceDisplayName}"""; Flags: runhidden From f30f24aa021189af9c65bf5e0ef4a64e871a5607 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:14:56 +0200 Subject: [PATCH 37/45] Fix: audio manager com-thread safety (#122) Devices are no longer cached but rather their IDs - required devices are instantiated as needed. This has a small performance hit but should resolve random issues with audio sensors and commands. --- .../SetApplicationVolumeCommand.cs | 49 +-- .../InternalCommands/SetAudioInputCommand.cs | 17 +- .../InternalCommands/SetAudioOutputCommand.cs | 17 +- .../InternalCommands/SetVolumeCommand.cs | 18 +- .../SingleValue/CurrentVolumeSensor.cs | 7 +- .../Managers/Audio/AudioManager.cs | 322 ++++++++++++------ .../Audio/Internal/InternalAudioDevice.cs | 48 --- .../HASS.Agent/Managers/SystemStateManager.cs | 4 - .../HASS.Agent/Media/MediaManagerCommands.cs | 7 +- .../HASS.Agent/Media/MediaManagerRequests.cs | 14 +- 10 files changed, 238 insertions(+), 265 deletions(-) delete mode 100644 src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs index 0d0e1084..cdb39cdc 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs @@ -34,16 +34,6 @@ public override void TurnOn() TurnOnWithAction(CommandConfig); } - private AudioDevice GetDeviceOrDefault(string deviceName) - { - var device = AudioManager.GetDevices().Where(d => d.FriendlyName == deviceName).FirstOrDefault(); - if (device != null) - return device; - - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); - return AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - } - public override void TurnOnWithAction(string action) { State = "ON"; @@ -59,44 +49,7 @@ public override void TurnOnWithAction(string action) return; } - var audioDevice = GetDeviceOrDefault(actionData.PlaybackDevice); - if (audioDevice == null) - return; - - var applicationAudioSessions = audioDevice.Sessions.Where(s => - s.Application == actionData.ApplicationName - ); - - if (actionData.Volume == -1) - Log.Debug("[SETAPPVOLUME] No volume value provided, only mute has been set for {app}", actionData.ApplicationName); - - - if (string.IsNullOrWhiteSpace(actionData.SessionId)) - { - foreach (var session in applicationAudioSessions) - { - AudioManager.SetMute(session, actionData.Mute); - if (actionData.Volume == -1) - return; - - AudioManager.SetVolume(session, actionData.Volume); - } - } - else - { - var session = applicationAudioSessions.Where(s => s.Id == actionData.SessionId).FirstOrDefault(); - if (session == null) - { - Log.Debug("[SETAPPVOLUME] No session {actionData.SessionId} found for device {device}", actionData.ApplicationName, audioDevice.FriendlyName); - return; - } - - AudioManager.SetMute(session, actionData.Mute); - if (actionData.Volume == -1) - return; - - AudioManager.SetVolume(session, actionData.Volume); - } + AudioManager.SetApplicationProperties(actionData.PlaybackDevice, actionData.ApplicationName, actionData.SessionId, actionData.Volume, actionData.Mute); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs index 2d7de409..1b6b1a35 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs @@ -42,22 +42,7 @@ public override void TurnOnWithAction(string action) try { - var audioDevices = AudioManager.GetDevices(); - var inputDevice = audioDevices - .Where(d => d.Type == DeviceType.Input) - .Where(d => d.FriendlyName == action) - .FirstOrDefault(); - - if (inputDevice == null) - { - Log.Warning("[SETAUDIOIN] No input device {device} found", action); - return; - } - - if(inputDevice.Default) - return; - - AudioManager.Activate(inputDevice); + AudioManager.ActivateDevice(action); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs index 1cefb69a..51769946 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs @@ -42,22 +42,7 @@ public override void TurnOnWithAction(string action) try { - var audioDevices = AudioManager.GetDevices(); - var outputDevice = audioDevices - .Where(d => d.Type == DeviceType.Output) - .Where(d => d.FriendlyName == action) - .FirstOrDefault(); - - if (outputDevice == null) - { - Log.Warning("[SETAUDIOOUT] No input device {device} found", action); - return; - } - - if (outputDevice.Default) - return; - - AudioManager.Activate(outputDevice); + AudioManager.ActivateDevice(action); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs index 96ffda7e..dccacc8e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs @@ -48,16 +48,11 @@ public override void TurnOn() if (_volume == -1f) { Log.Warning("[SETVOLUME] [{name}] Unable to trigger command, it's configured as action-only", EntityName); - - return; - } - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); - var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - if(audioDevice == null) return; + } - AudioManager.SetVolume(audioDevice, _volume); + AudioManager.SetDefaultDeviceProperties(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console, _volume, null); } catch (Exception ex) { @@ -79,16 +74,11 @@ public override void TurnOnWithAction(string action) if (!parsed) { Log.Error("[SETVOLUME] [{name}] Unable to trigger command, the provided action value can't be parsed: {val}", EntityName, action); - - return; - } - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); - var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - if (audioDevice == null) return; + } - AudioManager.SetVolume(audioDevice, volumeInt); + AudioManager.SetDefaultDeviceProperties(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console, _volume, null); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs index e1a03ce9..659db848 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs @@ -39,13 +39,10 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() public override string GetState() { - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); - var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - if (audioDevice == null) - return "0"; + var volume = AudioManager.GetDefaultDeviceVolume(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); // return as percentage - return audioDevice.Volume.ToString(CultureInfo.InvariantCulture); + return volume.ToString(CultureInfo.InvariantCulture); } public override string GetAttributes() => string.Empty; diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs index b6358d45..7f1be72b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs @@ -10,6 +10,8 @@ using HASS.Agent.Shared.Managers.Audio.Internal; using Serilog; using NAudio.CoreAudioApi.Interfaces; +using HidSharp; +using Microsoft.VisualBasic.ApplicationServices; namespace HASS.Agent.Shared.Managers.Audio; public static class AudioManager @@ -19,9 +21,7 @@ public static class AudioManager private static MMDeviceEnumerator _enumerator = null; private static MMNotificationClient _notificationClient = null; - private static readonly ConcurrentDictionary _devices = new(); - private static readonly ConcurrentQueue _devicesToBeRemoved = new(); - private static readonly ConcurrentQueue _devicesToBeAdded = new(); + private static readonly ConcurrentDictionary _devices = new(); private static readonly Dictionary _applicationNameCache = new(); @@ -30,7 +30,7 @@ private static void InitializeDevices() _enumerator = new MMDeviceEnumerator(); foreach (var device in _enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active)) - _devices[device.ID] = new InternalAudioDevice(device); + _devices[device.ID] = device.FriendlyName; _notificationClient = new MMNotificationClient(); _notificationClient.DeviceAdded += DeviceAdded; @@ -41,21 +41,58 @@ private static void InitializeDevices() _initialized = true; } + private static void AddDevice(string deviceId) + { + if (_devices.ContainsKey(deviceId)) + return; + + using var device = _enumerator.GetDevice(deviceId); + _devices[deviceId] = device.FriendlyName; + Log.Debug($"[AUDIOMGR] added device: {_devices[deviceId]}"); + } + + private static void RemoveDevice(string deviceId) + { + _devices.Remove(deviceId, out var removedDeviceName); + if (!string.IsNullOrWhiteSpace(removedDeviceName)) + Log.Debug($"[AUDIOMGR] removed device: {removedDeviceName}"); + } + + private static void DeviceRemoved(object sender, DeviceNotificationEventArgs e) + { + try + { + RemoveDevice(e.DeviceId); + } + catch + { + Log.Error($"[AUDIOMGR] failed to remove device: {e.DeviceId}"); + } + } + private static void DeviceAdded(object sender, DeviceNotificationEventArgs e) + { + try + { + AddDevice(e.DeviceId); + } + catch + { + Log.Error($"[AUDIOMGR] failed to add device: {e.DeviceId}"); + } + } + private static void DeviceStateChanged(object sender, DeviceStateChangedEventArgs e) { switch (e.DeviceState) { case DeviceState.Active: - if (_devices.ContainsKey(e.DeviceId)) - return; - - _devicesToBeAdded.Enqueue(e.DeviceId); + AddDevice(e.DeviceId); break; case DeviceState.NotPresent: case DeviceState.Unplugged: case DeviceState.Disabled: - _devicesToBeRemoved.Enqueue(e.DeviceId); + RemoveDevice(e.DeviceId); break; default: @@ -63,40 +100,14 @@ private static void DeviceStateChanged(object sender, DeviceStateChangedEventArg } } - private static void DeviceRemoved(object sender, DeviceNotificationEventArgs e) - { - _devicesToBeRemoved.Enqueue(e.DeviceId); - } - - private static void DeviceAdded(object sender, DeviceNotificationEventArgs e) - { - if (_devices.ContainsKey(e.DeviceId)) - return; - - _devicesToBeAdded.Enqueue(e.DeviceId); - } - private static bool CheckInitialization() { if (!_initialized) { Log.Warning("[AUDIOMGR] not yet initialized!"); - return false; } - while (_devicesToBeRemoved.TryDequeue(out var deviceId)) - { - if (_devices.Remove(deviceId, out var device)) - device.Dispose(); - } - - while (_devicesToBeAdded.TryDequeue(out var deviceId)) - { - var device = _enumerator.GetDevice(deviceId); - _devices[deviceId] = new InternalAudioDevice(device); - } - return true; } @@ -116,12 +127,17 @@ private static string GetSessionDisplayName(InternalAudioSession session) return process.ProcessName; } - private static List GetDeviceSessions(InternalAudioDevice internalAudioDevice) + private static List GetDeviceSessions(MMDevice mmDevice) + { + using var internalAudioSessionManager = new InternalAudioSessionManager(mmDevice.AudioSessionManager); + return GetDeviceSessions(_devices[mmDevice.ID], internalAudioSessionManager); + } + + private static List GetDeviceSessions(string deviceName, InternalAudioSessionManager internalAudioSessionManager) { var audioSessions = new List(); - internalAudioDevice.Manager.RemoveDisconnectedSessions(); - foreach (var (sessionId, session) in internalAudioDevice.Manager.Sessions) + foreach (var (sessionId, session) in internalAudioSessionManager.Sessions) { try { @@ -136,7 +152,7 @@ private static List GetDeviceSessions(InternalAudioDevice internal { Id = sessionId, Application = displayName, - PlaybackDevice = internalAudioDevice.FriendlyName, + PlaybackDevice = deviceName, Muted = session.Volume.Mute, Active = session.Control.State == AudioSessionState.AudioSessionStateActive, MasterVolume = Convert.ToInt32(session.Volume.Volume * 100), @@ -147,7 +163,7 @@ private static List GetDeviceSessions(InternalAudioDevice internal } catch (Exception ex) { - throw new AudioSessionException($"error retrieving session information for {internalAudioDevice.FriendlyName}", ex); + throw new AudioSessionException($"error retrieving session information for {deviceName}", ex); } } @@ -176,26 +192,11 @@ public static void Initialize() Log.Information("[AUDIOMGR] initialized"); } - public static void ReInitialize() - { - if (_initialized) - CleanupDevices(); - - Log.Debug("[AUDIOMGR] re-initializing"); - - InitializeDevices(); - - Log.Information("[AUDIOMGR] re-initialized"); - } - public static void CleanupDevices() { Log.Debug("[AUDIOMGR] starting cleanup"); _initialized = false; - foreach (var device in _devices.Values) - device.Dispose(); - _notificationClient.DeviceAdded -= DeviceAdded; _notificationClient.DeviceRemoved -= DeviceRemoved; _notificationClient.DeviceStateChanged -= DeviceStateChanged; @@ -222,23 +223,24 @@ public static List GetDevices() var defaultInputDeviceId = defaultInputDevice.ID; var defaultOutputDeviceId = defaultOutputDevice.ID; - foreach (var device in _devices.Values.Where(d => d.MMDevice.State == DeviceState.Active)) + foreach (var (deviceId, deviceName) in _devices) { + using var device = _enumerator.GetDevice(deviceId); var audioSessions = GetDeviceSessions(device); var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); var audioDevice = new AudioDevice { - State = GetReadableState(device.MMDevice.State), - Type = device.MMDevice.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, - Id = device.DeviceId, - FriendlyName = device.FriendlyName, + State = GetReadableState(device.State), + Type = device.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, + Id = device.ID, + FriendlyName = deviceName, Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0)), - Muted = device.MMDevice.AudioEndpointVolume.Mute, + Muted = device.AudioEndpointVolume.Mute, PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, Sessions = audioSessions, - Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId + Default = device.ID == defaultInputDeviceId || device.ID == defaultOutputDeviceId }; audioDevices.Add(audioDevice); @@ -246,7 +248,7 @@ public static List GetDevices() } catch (Exception ex) { - Log.Debug(ex, "[AUDIOMGR] Failed to retrieve devices: {msg}", ex.Message); + Log.Error(ex, "[AUDIOMGR] failed to retrieve devices: {msg}", ex.Message); } return audioDevices; @@ -257,77 +259,205 @@ public static string GetDefaultDeviceId(DeviceType deviceType, DeviceRole device if (!CheckInitialization()) return string.Empty; - var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; - var role = (Role)deviceRole; + var deviceId = string.Empty; + + try + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; + + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); - using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + deviceId = defaultDevice.ID; + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve default device id: {msg}", ex.Message); + } - return defaultDevice.ID; + return deviceId; } - public static void Activate(AudioDevice audioDevice) + public static int GetDefaultDeviceVolume(DeviceType deviceType, DeviceRole deviceRole) { if (!CheckInitialization()) - return; + return 0; - if (!_devices.TryGetValue(audioDevice.Id, out var device)) - return; + var volume = 0; + + try + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; - device.Activate(); + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + + volume = Convert.ToInt32(Math.Round(defaultDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0)); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve default device volume: {msg}", ex.Message); + } + + return volume; } - public static void SetVolume(AudioDevice audioDevice, int volume) + public static bool GetDefaultDeviceMute(DeviceType deviceType, DeviceRole deviceRole) { if (!CheckInitialization()) - return; + return false; - if (!_devices.TryGetValue(audioDevice.Id, out var device)) - return; + var mute = false; - var volumeScalar = volume / 100f; - device.AudioEndpointVolume.MasterVolumeLevelScalar = volumeScalar; + try + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; + + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + + mute = defaultDevice.AudioEndpointVolume.Mute; + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve default device mute: {msg}", ex.Message); + } + + return mute; } - public static void SetVolume(AudioSession audioSession, int volume) + public static void ActivateDevice(string deviceName) { if (!CheckInitialization()) return; - var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); - if (device == null) - return; + try + { + var deviceId = _devices.FirstOrDefault(v => v.Value == deviceName).Key; + if (string.IsNullOrWhiteSpace(deviceId)) + return; - if (!device.Manager.Sessions.TryGetValue(audioSession.Id, out var session)) - return; + using var configClient = new CPolicyConfigVistaClient(); + configClient.SetDefaultDevice(deviceId); - var volumeScalar = volume / 100f; - session.Volume.Volume = volumeScalar; + Log.Debug($"[AUDIOMGR] device '{deviceName}' activated"); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to activate device '{deviceName}': {msg}", deviceName, ex.Message); + } } - public static void SetMute(AudioDevice audioDevice, bool mute) + public static void SetDeviceProperties(string deviceName, int volume, bool? mute) { if (!CheckInitialization()) return; - if (!_devices.TryGetValue(audioDevice.Id, out var device)) - return; + try + { + var deviceId = _devices.FirstOrDefault(v => v.Value == deviceName).Key; + if (string.IsNullOrWhiteSpace(deviceId)) + return; + + using var device = _enumerator.GetDevice(deviceId); + SetDeviceProperties(device, volume, mute); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to set device properties '{deviceName}': {msg}", deviceName, ex.Message); + } + } + + private static void SetDeviceProperties(MMDevice device, int volume, bool? mute) + { + if (mute != null) + { + device.AudioEndpointVolume.Mute = (bool)mute; + Log.Debug($"[AUDIOMGR] mute for '{_devices[device.ID]}' set to '{mute}'"); + } + + if (volume != -1) + { + var volumeScalar = volume / 100f; + device.AudioEndpointVolume.MasterVolumeLevelScalar = volumeScalar; + Log.Debug($"[AUDIOMGR] volume for '{_devices[device.ID]}' set to '{volume}'"); + } + } - device.AudioEndpointVolume.Mute = mute; + public static void SetDefaultDeviceProperties(DeviceType type, DeviceRole deviceRole, int volume, bool? mute) + { + try + { + var flow = type == DeviceType.Output ? DataFlow.Render : DataFlow.Capture; + var role = (Role)deviceRole; + using var device = _enumerator.GetDefaultAudioEndpoint(flow, role); + SetDeviceProperties(device, volume, mute); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to set default device properties: {msg}", ex.Message); + } } - public static void SetMute(AudioSession audioSession, bool mute) + public static void SetApplicationProperties(string deviceName, string applicationName, string sessionId, int volume, bool mute) { if (!CheckInitialization()) return; - var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); - if (device == null) - return; + try + { + var deviceId = _devices.FirstOrDefault(v => v.Value == deviceName).Key; + if (string.IsNullOrWhiteSpace(deviceId)) + return; + + using var device = _enumerator.GetDevice(deviceId); + using var sessionManager = new InternalAudioSessionManager(device.AudioSessionManager); + var sessions = GetDeviceSessions(deviceName, sessionManager); + + var applicationAudioSessions = sessions.Where(s => + s.Application == applicationName + ); + + if (string.IsNullOrWhiteSpace(sessionId)) + { + foreach (var session in applicationAudioSessions) + { + if (!sessionManager.Sessions.TryGetValue(session.Id, out var internalSession)) + return; + + SetSessionProperties(internalSession, volume, mute); + } + } + else + { + if (!sessionManager.Sessions.TryGetValue(sessionId, out var internalSession)) + { + Log.Debug("[AUDIOMGR] no session '{sessionId}' found for device '{device}'", sessionId, deviceName); + return; + } + + SetSessionProperties(internalSession, volume, mute); + } + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to set application properties '{appName}': {msg}", applicationName, ex.Message); + } + } - if (!device.Manager.Sessions.TryGetValue(audioSession.Id, out var session)) + private static void SetSessionProperties(InternalAudioSession internalAudioSession, int volume, bool mute) + { + var displayName = string.IsNullOrWhiteSpace(internalAudioSession.DisplayName) ? GetSessionDisplayName(internalAudioSession) : internalAudioSession.DisplayName; + + internalAudioSession.Volume.Mute = mute; + Log.Debug("[AUDIOMGR] mute for '{sessionName}' ({sessionId}) set to '{mute}'", displayName, internalAudioSession.Control.GetSessionInstanceIdentifier, mute); + + if (volume == -1) return; - session.Volume.Mute = mute; + var volumeScalar = volume / 100f; + internalAudioSession.Volume.Volume = volumeScalar; + Log.Debug("[AUDIOMGR] volume for '{sessionName}' ({sessionId}) set to '{vol}'", displayName, internalAudioSession.Control.GetSessionInstanceIdentifier, volume); } public static void Shutdown() diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs deleted file mode 100644 index b477c2d9..00000000 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioDevice.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NAudio.CoreAudioApi; - -namespace HASS.Agent.Shared.Managers.Audio.Internal; -internal class InternalAudioDevice : IDisposable -{ - public MMDevice MMDevice { get; private set; } - public AudioEndpointVolume AudioEndpointVolume { get; private set; } - public InternalAudioSessionManager Manager { get; private set; } - - public string DeviceId { get; private set; } - public string FriendlyName { get; private set; } - public bool Reinitialized { get; private set; } - - public InternalAudioDevice(MMDevice device) - { - MMDevice = device; - Manager = new InternalAudioSessionManager(device.AudioSessionManager); - AudioEndpointVolume = device.AudioEndpointVolume; - - DeviceId = device.ID; - FriendlyName = device.FriendlyName; - } - - public void Activate() - { - using var configClient = new CPolicyConfigVistaClient(); - configClient.SetDefaultDevice(MMDevice.ID); - } - - public void Dispose() - { - AudioEndpointVolume?.Dispose(); - Manager?.Dispose(); - MMDevice?.Dispose(); - - GC.SuppressFinalize(this); - } - - ~InternalAudioDevice() - { - Dispose(); - } -} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs index a8794708..3354bda2 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs @@ -202,8 +202,6 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync()); LastSystemStateEvent = SystemStateEvent.Resume; - AudioManager.ReInitialize(); - break; case PowerModes.Suspend: @@ -214,8 +212,6 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync(true)); LastSystemStateEvent = SystemStateEvent.Suspend; - AudioManager.CleanupDevices(); - break; } } diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs index 47089ca0..11b6665e 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs @@ -71,12 +71,7 @@ internal static void SetVolume(int volume) if (volume < 0) volume = 0; if (volume > 100) volume = 100; - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); - var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - if (audioDevice == null) - return; - - AudioManager.SetVolume(audioDevice, volume); + AudioManager.SetDefaultDeviceProperties(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console, volume, null); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs index bdf93fca..ceff8721 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs @@ -19,12 +19,7 @@ internal static int GetVolume() { try { - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia); - var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - if (audioDevice == null) - return 0; - - return audioDevice.Volume; + return AudioManager.GetDefaultDeviceVolume(DeviceType.Output, DeviceRole.Multimedia); } catch (Exception ex) { @@ -41,12 +36,7 @@ internal static bool GetMuteState() { try { - var defaultDeviceId = AudioManager.GetDefaultDeviceId(DeviceType.Output, DeviceRole.Multimedia); - var audioDevice = AudioManager.GetDevices().Where(d => d.Id == defaultDeviceId).FirstOrDefault(); - if (audioDevice == null) - return false; - - return audioDevice.Muted; + return AudioManager.GetDefaultDeviceMute(DeviceType.Output, DeviceRole.Multimedia); } catch (Exception ex) { From 1027b1f14b86a3ba6ff25335785b8725da43d3d1 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:20:24 +0200 Subject: [PATCH 38/45] Fix: satellite service sensors/commands not being properly saved (#123) This PR fixes satellite sensors/commands: not saving advanced settings not saving round configuration not saving "ignore availability" option --- .../InstallerScript-Service.iss | 36 +- .../Extensions/RpcExtensions.cs | 6 + .../RPC/Protos/hassagentsatellite.proto | 3 + .../Settings/StoredSensors.cs | 11 + .../HASS.Agent/Extensions/RpcExtensions.cs | 6 + .../Service/Protos/hassagentsatellite.proto | 3 + .../HASS.Agent/Settings/StoredSensors.cs | 713 +++++++++--------- 7 files changed, 421 insertions(+), 357 deletions(-) diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss index f90e087d..fba2c845 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -79,4 +79,38 @@ begin Dependency_ForceX86 := False; Dependency_AddDotNet60Desktop; Result := True; -end; \ No newline at end of file +end; + +//procedure CurStepChanged(CurStep: TSetupStep); +//var +// ProgressPage: TOutputProgressWizardPage; +// I, Step, Wait, ResultCode: Integer; +//begin +// if CurStep = ssInstall then +// begin +// //MsgBox(ExpandConstant('{sys}') + '\sc.exe', mbInformation, MB_OK); +// //MsgBox('stop ' + ExpandConstant('{#ServiceName}'), mbInformation, MB_OK); +// Exec(ExpandConstant('{sys}') + '\sc.exe', 'stop ' + ExpandConstant('{#ServiceName}'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) +// +// //thanks to https://stackoverflow.com/a/39827761 +// Wait := 5000; +// Step := 100; +// ProgressPage := +// CreateOutputProgressPage( +// WizardForm.PageNameLabel.Caption, +// WizardForm.PageDescriptionLabel.Caption); +// ProgressPage.SetText('Making sure the satellite service is stopped...', ''); +// ProgressPage.SetProgress(0, Wait); +// ProgressPage.Show; +// try +// for I := 0 to Wait div Step do +// begin +// ProgressPage.SetProgress(I * Step, Wait); +// Sleep(Step); +// end; +// finally +// ProgressPage.Hide; +// ProgressPage.Free; +// end; +// end; +//end; \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs index 53f3f88b..a57b6c07 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs @@ -110,6 +110,9 @@ public static ConfiguredSensor ConvertToConfiguredSensor(this RpcConfiguredServe EntityName = rpcConfiguredSensor.EntityName, Name = rpcConfiguredSensor.Name, AdvancedSettings = rpcConfiguredSensor.AdvancedSettings, + IgnoreAvailability = rpcConfiguredSensor.IgnoreAvailability, + ApplyRounding = rpcConfiguredSensor.ApplyRounding, + Round = rpcConfiguredSensor.RoundValue, }; return configuredSensor; @@ -152,6 +155,9 @@ public static RpcConfiguredServerSensor ConvertToRpcConfiguredSensor(this Config Name = configuredSensor.Name ?? string.Empty, EntityName = configuredSensor?.EntityName ?? string.Empty, AdvancedSettings = configuredSensor?.AdvancedSettings ?? string.Empty, + IgnoreAvailability = configuredSensor?.IgnoreAvailability ?? false, + ApplyRounding = configuredSensor?.ApplyRounding ?? false, + RoundValue = configuredSensor?.Round ?? 0, }; return configuredRpcSensor; diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto b/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto index ad2d5d68..a21c9157 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto @@ -135,6 +135,9 @@ message RpcConfiguredServerSensor { string name = 10; string entityName = 11; string advancedSettings = 12; + bool applyRounding = 13; + int32 roundValue = 14; + bool ignoreAvailability = 15; } message RpcConfiguredServerCommand { diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs index 5dfdb27a..6ff98745 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs @@ -172,6 +172,9 @@ await Task.Run(delegate break; } + if (abstractSensor != null) + abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; + return abstractSensor; } @@ -231,6 +234,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = wmiSensor.Name, Type = type, UpdateInterval = wmiSensor.UpdateIntervalSeconds, + IgnoreAvailability = wmiSensor.IgnoreAvailability, Scope = wmiSensor.Scope, Query = wmiSensor.Query, ApplyRounding = wmiSensor.ApplyRounding, @@ -249,6 +253,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = namedWindowSensor.EntityName, Type = type, UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, + IgnoreAvailability = namedWindowSensor.IgnoreAvailability, WindowName = namedWindowSensor.WindowName, AdvancedSettings = namedWindowSensor.AdvancedSettings }; @@ -264,6 +269,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = performanceCounterSensor.EntityName, Type = type, UpdateInterval = performanceCounterSensor.UpdateIntervalSeconds, + IgnoreAvailability = performanceCounterSensor.IgnoreAvailability, Category = performanceCounterSensor.CategoryName, Counter = performanceCounterSensor.CounterName, Instance = performanceCounterSensor.InstanceName, @@ -283,6 +289,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = processActiveSensor.EntityName, Type = type, UpdateInterval = processActiveSensor.UpdateIntervalSeconds, + IgnoreAvailability = processActiveSensor.IgnoreAvailability, Query = processActiveSensor.ProcessName, AdvancedSettings = processActiveSensor.AdvancedSettings }; @@ -298,6 +305,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = serviceStateSensor.EntityName, Type = type, UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, + IgnoreAvailability = serviceStateSensor.IgnoreAvailability, Query = serviceStateSensor.ServiceName, AdvancedSettings = serviceStateSensor.AdvancedSettings }; @@ -313,6 +321,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = powershellSensor.EntityName, Type = type, UpdateInterval = powershellSensor.UpdateIntervalSeconds, + IgnoreAvailability = powershellSensor.IgnoreAvailability, Query = powershellSensor.Command, ApplyRounding = powershellSensor.ApplyRounding, Round = powershellSensor.Round, @@ -330,6 +339,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = windowStateSensor.EntityName, Type = type, UpdateInterval = windowStateSensor.UpdateIntervalSeconds, + IgnoreAvailability = windowStateSensor.IgnoreAvailability, Query = windowStateSensor.ProcessName, AdvancedSettings = windowStateSensor.AdvancedSettings }; @@ -345,6 +355,7 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = sensor.EntityName, Type = type, UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability, AdvancedSettings = sensor.AdvancedSettings }; } diff --git a/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs b/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs index 3a7acc82..d36c4879 100644 --- a/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs +++ b/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs @@ -111,6 +111,9 @@ public static ConfiguredSensor ConvertToConfiguredSensor(this RpcConfiguredServe EntityName = rpcConfiguredSensor.EntityName, Name = rpcConfiguredSensor.Name, AdvancedSettings = rpcConfiguredSensor.AdvancedSettings, + IgnoreAvailability = rpcConfiguredSensor.IgnoreAvailability, + ApplyRounding = rpcConfiguredSensor.ApplyRounding, + Round = rpcConfiguredSensor.RoundValue, }; return configuredSensor; @@ -153,6 +156,9 @@ public static RpcConfiguredServerSensor ConvertToRpcConfiguredSensor(this Config Name = configuredSensor.Name ?? string.Empty, EntityName = configuredSensor.EntityName ?? string.Empty, AdvancedSettings = configuredSensor?.AdvancedSettings ?? string.Empty, + IgnoreAvailability = configuredSensor?.IgnoreAvailability ?? false, + ApplyRounding = configuredSensor?.ApplyRounding ?? false, + RoundValue = configuredSensor?.Round ?? 0, }; return configuredRpcSensor; diff --git a/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto b/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto index ad2d5d68..a21c9157 100644 --- a/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto +++ b/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto @@ -135,6 +135,9 @@ message RpcConfiguredServerSensor { string name = 10; string entityName = 11; string advancedSettings = 12; + bool applyRounding = 13; + int32 roundValue = 14; + bool ignoreAvailability = 15; } message RpcConfiguredServerCommand { diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs index bbcd34dc..57c52ed9 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs @@ -21,71 +21,71 @@ namespace HASS.Agent.Settings { - /// - /// Handles loading and storing sensors - /// - internal static class StoredSensors - { - /// - /// Load all stored sensors - /// - /// - internal static async Task LoadAsync() - { - try - { - // set empty lists - Variables.SingleValueSensors = new List(); - Variables.MultiValueSensors = new List(); - - // check for existing file - if (!File.Exists(Variables.SensorsFile)) - { - // none yet - Log.Information("[SETTINGS_SENSORS] Config not found, no entities loaded"); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); - return true; - } - - // read the content - var sensorsRaw = await File.ReadAllTextAsync(Variables.SensorsFile); - if (string.IsNullOrWhiteSpace(sensorsRaw)) - { - Log.Information("[SETTINGS_SENSORS] Config is empty, no entities loaded"); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); - return true; - } - - // deserialize - var configuredSensors = JsonConvert.DeserializeObject>(sensorsRaw); - - // null-check - if (configuredSensors == null) - { - Log.Error("[SETTINGS_SENSORS] Error loading entities: returned null object"); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); - return false; - } - - // convert to abstract sensors - await Task.Run(delegate - { - foreach (var sensor in configuredSensors) - { - if (sensor.IsSingleValue()) Variables.SingleValueSensors.Add(ConvertConfiguredToAbstractSingleValue(sensor)); - else Variables.MultiValueSensors.Add(ConvertConfiguredToAbstractMultiValue(sensor)); - } - }); - - // all good - Log.Information("[SETTINGS_SENSORS] Loaded {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); - return true; - } - catch (Exception ex) - { - Log.Fatal(ex, "[SETTINGS_SENSORS] Error loading entities: {err}", ex.Message); - Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Load_MessageBox1, ex.Message), true); + /// + /// Handles loading and storing sensors + /// + internal static class StoredSensors + { + /// + /// Load all stored sensors + /// + /// + internal static async Task LoadAsync() + { + try + { + // set empty lists + Variables.SingleValueSensors = new List(); + Variables.MultiValueSensors = new List(); + + // check for existing file + if (!File.Exists(Variables.SensorsFile)) + { + // none yet + Log.Information("[SETTINGS_SENSORS] Config not found, no entities loaded"); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); + return true; + } + + // read the content + var sensorsRaw = await File.ReadAllTextAsync(Variables.SensorsFile); + if (string.IsNullOrWhiteSpace(sensorsRaw)) + { + Log.Information("[SETTINGS_SENSORS] Config is empty, no entities loaded"); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); + return true; + } + + // deserialize + var configuredSensors = JsonConvert.DeserializeObject>(sensorsRaw); + + // null-check + if (configuredSensors == null) + { + Log.Error("[SETTINGS_SENSORS] Error loading entities: returned null object"); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); + return false; + } + + // convert to abstract sensors + await Task.Run(delegate + { + foreach (var sensor in configuredSensors) + { + if (sensor.IsSingleValue()) Variables.SingleValueSensors.Add(ConvertConfiguredToAbstractSingleValue(sensor)); + else Variables.MultiValueSensors.Add(ConvertConfiguredToAbstractMultiValue(sensor)); + } + }); + + // all good + Log.Information("[SETTINGS_SENSORS] Loaded {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); + return true; + } + catch (Exception ex) + { + Log.Fatal(ex, "[SETTINGS_SENSORS] Error loading entities: {err}", ex.Message); + Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Load_MessageBox1, ex.Message), true); Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); return false; @@ -127,10 +127,10 @@ internal static AbstractSingleValueSensor ConvertConfiguredToAbstractSingleValue case SensorType.NamedWindowSensor: abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; - case SensorType.LastActiveSensor: - abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); - break; - case SensorType.LastSystemStateChangeSensor: + case SensorType.LastActiveSensor: + abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); + break; + case SensorType.LastSystemStateChangeSensor: abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastBootSensor: @@ -207,47 +207,48 @@ internal static AbstractSingleValueSensor ConvertConfiguredToAbstractSingleValue break; } - abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; + if (abstractSensor != null) + abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; return abstractSensor; } - /// - /// Convert a multi-value 'ConfiguredSensor' (local storage, UI) to an 'AbstractSensor' (MQTT) - /// - /// - /// - internal static AbstractMultiValueSensor ConvertConfiguredToAbstractMultiValue(ConfiguredSensor sensor) - { - AbstractMultiValueSensor abstractSensor = null; - - switch (sensor.Type) - { - case SensorType.StorageSensors: - abstractSensor = new StorageSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.NetworkSensors: - abstractSensor = new NetworkSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Query, sensor.Id.ToString()); - break; - case SensorType.WindowsUpdatesSensors: - abstractSensor = new WindowsUpdatesSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.BatterySensors: - abstractSensor = new BatterySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.DisplaySensors: - abstractSensor = new DisplaySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.AudioSensors: - abstractSensor = new AudioSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.PrintersSensors: - abstractSensor = new PrintersSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - default: - Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured multi-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); - break; - } + /// + /// Convert a multi-value 'ConfiguredSensor' (local storage, UI) to an 'AbstractSensor' (MQTT) + /// + /// + /// + internal static AbstractMultiValueSensor ConvertConfiguredToAbstractMultiValue(ConfiguredSensor sensor) + { + AbstractMultiValueSensor abstractSensor = null; + + switch (sensor.Type) + { + case SensorType.StorageSensors: + abstractSensor = new StorageSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.NetworkSensors: + abstractSensor = new NetworkSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Query, sensor.Id.ToString()); + break; + case SensorType.WindowsUpdatesSensors: + abstractSensor = new WindowsUpdatesSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.BatterySensors: + abstractSensor = new BatterySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.DisplaySensors: + abstractSensor = new DisplaySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.AudioSensors: + abstractSensor = new AudioSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.PrintersSensors: + abstractSensor = new PrintersSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + default: + Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured multi-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); + break; + } abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; @@ -264,142 +265,142 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract switch (sensor) { case WmiQuerySensor wmiSensor: - { - _ = Enum.TryParse(wmiSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(wmiSensor.Id), - EntityName = wmiSensor.EntityName, - Name = wmiSensor.Name, - Type = type, - UpdateInterval = wmiSensor.UpdateIntervalSeconds, - IgnoreAvailability = wmiSensor.IgnoreAvailability, - Scope = wmiSensor.Scope, - Query = wmiSensor.Query, - ApplyRounding = wmiSensor.ApplyRounding, - Round= wmiSensor.Round, - AdvancedSettings = wmiSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(wmiSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(wmiSensor.Id), + EntityName = wmiSensor.EntityName, + Name = wmiSensor.Name, + Type = type, + UpdateInterval = wmiSensor.UpdateIntervalSeconds, + IgnoreAvailability = wmiSensor.IgnoreAvailability, + Scope = wmiSensor.Scope, + Query = wmiSensor.Query, + ApplyRounding = wmiSensor.ApplyRounding, + Round = wmiSensor.Round, + AdvancedSettings = wmiSensor.AdvancedSettings + }; + } case NamedWindowSensor namedWindowSensor: - { - _ = Enum.TryParse(namedWindowSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(namedWindowSensor.Id), - EntityName = namedWindowSensor.EntityName, - Name = namedWindowSensor.Name, - Type = type, - UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, - IgnoreAvailability = namedWindowSensor.IgnoreAvailability, - WindowName = namedWindowSensor.WindowName, - AdvancedSettings = namedWindowSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(namedWindowSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(namedWindowSensor.Id), + EntityName = namedWindowSensor.EntityName, + Name = namedWindowSensor.Name, + Type = type, + UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, + IgnoreAvailability = namedWindowSensor.IgnoreAvailability, + WindowName = namedWindowSensor.WindowName, + AdvancedSettings = namedWindowSensor.AdvancedSettings + }; + } case PerformanceCounterSensor performanceCounterSensor: - { - _ = Enum.TryParse(performanceCounterSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(performanceCounterSensor.Id), - EntityName = performanceCounterSensor.EntityName, - Name = performanceCounterSensor.Name, - Type = type, - UpdateInterval = performanceCounterSensor.UpdateIntervalSeconds, - IgnoreAvailability = performanceCounterSensor.IgnoreAvailability, - Category = performanceCounterSensor.CategoryName, - Counter = performanceCounterSensor.CounterName, - Instance = performanceCounterSensor.InstanceName, - ApplyRounding = performanceCounterSensor.ApplyRounding, - Round = performanceCounterSensor.Round, - AdvancedSettings = performanceCounterSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(performanceCounterSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(performanceCounterSensor.Id), + EntityName = performanceCounterSensor.EntityName, + Name = performanceCounterSensor.Name, + Type = type, + UpdateInterval = performanceCounterSensor.UpdateIntervalSeconds, + IgnoreAvailability = performanceCounterSensor.IgnoreAvailability, + Category = performanceCounterSensor.CategoryName, + Counter = performanceCounterSensor.CounterName, + Instance = performanceCounterSensor.InstanceName, + ApplyRounding = performanceCounterSensor.ApplyRounding, + Round = performanceCounterSensor.Round, + AdvancedSettings = performanceCounterSensor.AdvancedSettings + }; + } case ProcessActiveSensor processActiveSensor: - { - _ = Enum.TryParse(processActiveSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(processActiveSensor.Id), - EntityName = processActiveSensor.EntityName, - Name = processActiveSensor.Name, - Type = type, - UpdateInterval = processActiveSensor.UpdateIntervalSeconds, - IgnoreAvailability = processActiveSensor.IgnoreAvailability, - Query = processActiveSensor.ProcessName, - AdvancedSettings = processActiveSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(processActiveSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(processActiveSensor.Id), + EntityName = processActiveSensor.EntityName, + Name = processActiveSensor.Name, + Type = type, + UpdateInterval = processActiveSensor.UpdateIntervalSeconds, + IgnoreAvailability = processActiveSensor.IgnoreAvailability, + Query = processActiveSensor.ProcessName, + AdvancedSettings = processActiveSensor.AdvancedSettings + }; + } case ServiceStateSensor serviceStateSensor: - { - _ = Enum.TryParse(serviceStateSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(serviceStateSensor.Id), - EntityName = serviceStateSensor.EntityName, - Name = serviceStateSensor.Name, - Type = type, - UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, - IgnoreAvailability = serviceStateSensor.IgnoreAvailability, - Query = serviceStateSensor.ServiceName, - AdvancedSettings = serviceStateSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(serviceStateSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(serviceStateSensor.Id), + EntityName = serviceStateSensor.EntityName, + Name = serviceStateSensor.Name, + Type = type, + UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, + IgnoreAvailability = serviceStateSensor.IgnoreAvailability, + Query = serviceStateSensor.ServiceName, + AdvancedSettings = serviceStateSensor.AdvancedSettings + }; + } case PowershellSensor powershellSensor: - { - _ = Enum.TryParse(powershellSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(powershellSensor.Id), - EntityName = powershellSensor.EntityName, - Name = powershellSensor.Name, - Type = type, - UpdateInterval = powershellSensor.UpdateIntervalSeconds, - IgnoreAvailability = powershellSensor.IgnoreAvailability, - Query = powershellSensor.Command, - ApplyRounding = powershellSensor.ApplyRounding, - Round = powershellSensor.Round, - AdvancedSettings = powershellSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(powershellSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(powershellSensor.Id), + EntityName = powershellSensor.EntityName, + Name = powershellSensor.Name, + Type = type, + UpdateInterval = powershellSensor.UpdateIntervalSeconds, + IgnoreAvailability = powershellSensor.IgnoreAvailability, + Query = powershellSensor.Command, + ApplyRounding = powershellSensor.ApplyRounding, + Round = powershellSensor.Round, + AdvancedSettings = powershellSensor.AdvancedSettings + }; + } - case LastActiveSensor lastActiveSensor: - { - _ = Enum.TryParse(lastActiveSensor.GetType().Name, out var type); - return new ConfiguredSensor - { - Id = Guid.Parse(lastActiveSensor.Id), - EntityName = lastActiveSensor.EntityName, - Name = lastActiveSensor.Name, - Type = type, - UpdateInterval = lastActiveSensor.UpdateIntervalSeconds, - IgnoreAvailability = lastActiveSensor.IgnoreAvailability, - ApplyRounding = lastActiveSensor.ApplyRounding, - Round = lastActiveSensor.Round, + case LastActiveSensor lastActiveSensor: + { + _ = Enum.TryParse(lastActiveSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(lastActiveSensor.Id), + EntityName = lastActiveSensor.EntityName, + Name = lastActiveSensor.Name, + Type = type, + UpdateInterval = lastActiveSensor.UpdateIntervalSeconds, + IgnoreAvailability = lastActiveSensor.IgnoreAvailability, + ApplyRounding = lastActiveSensor.ApplyRounding, + Round = lastActiveSensor.Round, AdvancedSettings = lastActiveSensor.AdvancedSettings }; - } + } - case WindowStateSensor windowStateSensor: - { - _ = Enum.TryParse(windowStateSensor.GetType().Name, out var type); - return new ConfiguredSensor + case WindowStateSensor windowStateSensor: { - Id = Guid.Parse(windowStateSensor.Id), - EntityName = windowStateSensor.EntityName, - Name = windowStateSensor.Name, - Type = type, - UpdateInterval = windowStateSensor.UpdateIntervalSeconds, - IgnoreAvailability = windowStateSensor.IgnoreAvailability, - Query = windowStateSensor.ProcessName, - AdvancedSettings = windowStateSensor.AdvancedSettings - }; - } + _ = Enum.TryParse(windowStateSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(windowStateSensor.Id), + EntityName = windowStateSensor.EntityName, + Name = windowStateSensor.Name, + Type = type, + UpdateInterval = windowStateSensor.UpdateIntervalSeconds, + IgnoreAvailability = windowStateSensor.IgnoreAvailability, + Query = windowStateSensor.ProcessName, + AdvancedSettings = windowStateSensor.AdvancedSettings + }; + } case HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor internalDeviceSensor: { @@ -411,8 +412,8 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = internalDeviceSensor.Name, Type = type, UpdateInterval = internalDeviceSensor.UpdateIntervalSeconds, - IgnoreAvailability = internalDeviceSensor.IgnoreAvailability, - Query = internalDeviceSensor.SensorType.ToString(), + IgnoreAvailability = internalDeviceSensor.IgnoreAvailability, + Query = internalDeviceSensor.SensorType.ToString(), AdvancedSettings = internalDeviceSensor.AdvancedSettings }; } @@ -434,19 +435,19 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract } default: - { - _ = Enum.TryParse(sensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.Name, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability, - AdvancedSettings = sensor.AdvancedSettings - }; - } + _ = Enum.TryParse(sensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.Name, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability, + AdvancedSettings = sensor.AdvancedSettings + }; + } } } @@ -460,147 +461,147 @@ internal static ConfiguredSensor ConvertAbstractMultiValueToConfigured(AbstractM switch (sensor) { case StorageSensors storageSensors: - { - _ = Enum.TryParse(storageSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(storageSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case NetworkSensors networkSensors: - { - _ = Enum.TryParse(networkSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Query = networkSensors.NetworkCard, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(networkSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Query = networkSensors.NetworkCard, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case WindowsUpdatesSensors windowsUpdatesSensors: - { - _ = Enum.TryParse(windowsUpdatesSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(windowsUpdatesSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case BatterySensors batterySensors: - { - _ = Enum.TryParse(batterySensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(batterySensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case DisplaySensors displaySensors: - { - _ = Enum.TryParse(displaySensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(displaySensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case AudioSensors audioSensors: - { - _ = Enum.TryParse(audioSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(audioSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case PrintersSensors printersSensors: - { - _ = Enum.TryParse(printersSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; + _ = Enum.TryParse(printersSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } + } + + return null; + } + + /// + /// Store all current sensors + /// + /// + internal static bool Store() + { + try + { + // check config dir + if (!Directory.Exists(Variables.ConfigPath)) + { + // create + Directory.CreateDirectory(Variables.ConfigPath); } + + // convert single-value sensors + var configuredSensors = Variables.SingleValueSensors.Select(ConvertAbstractSingleValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); + + // convert multi-value sensors + var configuredMultiValueSensors = Variables.MultiValueSensors.Select(ConvertAbstractMultiValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); + configuredSensors = configuredSensors.Concat(configuredMultiValueSensors).ToList(); + + // serialize to file + var sensors = JsonConvert.SerializeObject(configuredSensors, Formatting.Indented); + File.WriteAllText(Variables.SensorsFile, sensors); + + // done + Log.Information("[SETTINGS_SENSORS] Stored {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); + return true; } + catch (Exception ex) + { + Log.Fatal(ex, "[SETTINGS_SENSORS] Error storing entities: {err}", ex.Message); + Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Store_MessageBox1, ex.Message), true); - return null; - } - - /// - /// Store all current sensors - /// - /// - internal static bool Store() - { - try - { - // check config dir - if (!Directory.Exists(Variables.ConfigPath)) - { - // create - Directory.CreateDirectory(Variables.ConfigPath); - } - - // convert single-value sensors - var configuredSensors = Variables.SingleValueSensors.Select(ConvertAbstractSingleValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); - - // convert multi-value sensors - var configuredMultiValueSensors = Variables.MultiValueSensors.Select(ConvertAbstractMultiValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); - configuredSensors = configuredSensors.Concat(configuredMultiValueSensors).ToList(); - - // serialize to file - var sensors = JsonConvert.SerializeObject(configuredSensors, Formatting.Indented); - File.WriteAllText(Variables.SensorsFile, sensors); - - // done - Log.Information("[SETTINGS_SENSORS] Stored {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); - return true; - } - catch (Exception ex) - { - Log.Fatal(ex, "[SETTINGS_SENSORS] Error storing entities: {err}", ex.Message); - Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Store_MessageBox1, ex.Message), true); - - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); - return false; - } - } - } + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); + return false; + } + } + } } From 5161f6f5ef59c2005027c3b7a3d0de5dbc6358e4 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:23:16 +0200 Subject: [PATCH 39/45] Feature: tray webview command (#124) This PR adds feature requested via #116 - a command (best working as switch) that allows to show/hide the configured tray web view. --- .../HASS.Agent.Shared/Enums/CommandType.cs | 4 ++ .../Localization/Languages.Designer.cs | 9 +++ .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 3 + .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3 + .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + .../HASS.Agent/Commands/CommandsManager.cs | 8 +++ src/HASS.Agent/HASS.Agent/Forms/Main.cs | 11 +--- src/HASS.Agent/HASS.Agent/Forms/WebView.cs | 3 +- .../HASS.Agent/Functions/HelperFunctions.cs | 29 ++++++++-- .../InternalCommands/TrayWebViewCommand.cs | 58 +++++++++++++++++++ .../Localization/Languages.Designer.cs | 10 ++++ .../Resources/Localization/Languages.de.resx | 4 ++ .../Resources/Localization/Languages.en.resx | 4 ++ .../Resources/Localization/Languages.es.resx | 4 ++ .../Resources/Localization/Languages.fr.resx | 4 ++ .../Resources/Localization/Languages.nl.resx | 4 ++ .../Resources/Localization/Languages.pl.resx | 4 ++ .../Localization/Languages.pt-br.resx | 4 ++ .../Resources/Localization/Languages.resx | 4 ++ .../Resources/Localization/Languages.ru.resx | 4 ++ .../Resources/Localization/Languages.sl.resx | 4 ++ .../Resources/Localization/Languages.tr.resx | 4 ++ .../HASS.Agent/Settings/StoredCommands.cs | 3 + 31 files changed, 196 insertions(+), 16 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs index e92db473..a25ca5b1 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs @@ -121,6 +121,10 @@ public enum CommandType [EnumMember(Value = "WebViewCommand")] WebViewCommand, + [LocalizedDescription("CommandType_TrayWebViewCommand", typeof(Languages))] + [EnumMember(Value = "TrayWebViewCommand")] + TrayWebViewCommand, + [LocalizedDescription("CommandType_RadioCommand", typeof(Languages))] [EnumMember(Value = "RadioCommand")] RadioCommand diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index be913c63..dac0f1dc 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -1338,6 +1338,15 @@ internal static string CommandType_SwitchDesktopCommand { } } + /// + /// Looks up a localized string similar to TrayWebView. + /// + internal static string CommandType_TrayWebViewCommand { + get { + return ResourceManager.GetString("CommandType_TrayWebViewCommand", resourceCulture); + } + } + /// /// Looks up a localized string similar to WebView. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index 736f2957..09bbf764 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3364,4 +3364,7 @@ Willst Du den Runtime Installer herunterladen? Legen Sie den Audioeingabebefehl fest + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index decc96d4..997c345c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3240,4 +3240,7 @@ Do you want to download the runtime installer? SetAudioInputCommand + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 58a31529..86f336a6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3240,4 +3240,7 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. Establecer comando de entrada de audio + + Vista web de la bandeja + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index 32f1ca61..39449077 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3273,4 +3273,7 @@ Do you want to download the runtime installer? Définir la commande d'entrée audio + + Vue Web de la barre d'état + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 664534da..31addb36 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3262,4 +3262,7 @@ Wil je de runtime installatie downloaden? Stel het audio-invoercommando in + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index c6ffb694..3da9e587 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3350,4 +3350,7 @@ Czy chcesz pobrać plik instalacyjny? Ustaw wejście audio + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index 76c7b10d..dae75e54 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3287,4 +3287,7 @@ Deseja baixar o Microsoft WebView2 runtime? Definir comando de entrada de áudio + + BandejaWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index 94dad3f7..fef61f2c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -3223,4 +3223,7 @@ Do you want to download the runtime installer? SetAudioInputCommand + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index 758aa8bd..bbeaa1ad 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3308,4 +3308,7 @@ Home Assistant. Установить команду аудиовхода + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index 77cf6153..5f3406dc 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3389,4 +3389,7 @@ Ali želite prenesti runtime installer? Nastavite ukaz za zvočni vhod + + TrayWebView + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index e2c8d79d..243bb449 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2847,4 +2847,7 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku Ses Giriş Komutunu Ayarla + + TepsiWebGörünümü + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs index f3646e59..811b574d 100644 --- a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs @@ -525,6 +525,14 @@ internal static void LoadCommandInfo() // ================================= + commandInfoCard = new CommandInfoCard(CommandType.TrayWebViewCommand, + Languages.CommandsManager_TrayWebViewCommandDescription, + true, false, false); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.RadioCommand, Languages.CommandsManager_RadioCommandDescription, true, false, true); diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.cs b/src/HASS.Agent/HASS.Agent/Forms/Main.cs index e09e8933..0f8467ff 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.cs @@ -961,16 +961,7 @@ private void NotifyIcon_MouseClick(object sender, MouseEventArgs e) return; } - // prepare the webview - var webView = new WebViewInfo - { - Url = Variables.AppSettings.TrayIconWebViewUrl, - Height = Variables.AppSettings.TrayIconWebViewHeight, - Width = Variables.AppSettings.TrayIconWebViewWidth, - }; - - // show it - HelperFunctions.LaunchTrayIconWebView(webView); + HelperFunctions.LaunchTrayIconWebView(); } private async void PbDonate_Click(object sender, EventArgs e) diff --git a/src/HASS.Agent/HASS.Agent/Forms/WebView.cs b/src/HASS.Agent/HASS.Agent/Forms/WebView.cs index b5983909..90444df0 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/WebView.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/WebView.cs @@ -274,10 +274,11 @@ private void WebView_FormClosing(object sender, FormClosingEventArgs e) return; } + Opacity = 0; + // do we need to stay open? if (_isTrayIcon) { - Opacity = 0; e.Cancel = true; return; } diff --git a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs index db1138ef..ab6d7cb9 100644 --- a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs +++ b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs @@ -447,7 +447,7 @@ internal static void PrepareTrayIconWebView() Variables.MainForm.Invoke(new MethodInvoker(delegate { // optionally close an existing one - if (Variables.TrayIconWebView != null) Variables.TrayIconWebView.ForceClose(); + Variables.TrayIconWebView?.ForceClose(); // bind the new one Variables.TrayIconWebView = new WebView(webViewInfo); @@ -456,6 +456,22 @@ internal static void PrepareTrayIconWebView() })); } + /// + /// Shows the user-configured webview form near the tray icon + /// + /// + internal static void LaunchTrayIconWebView() + { + var webView = new WebViewInfo + { + Url = Variables.AppSettings.TrayIconWebViewUrl, + Height = Variables.AppSettings.TrayIconWebViewHeight, + Width = Variables.AppSettings.TrayIconWebViewWidth, + }; + + LaunchTrayIconWebView(webView); + } + /// /// Shows a new webview form near the tray icon /// @@ -491,7 +507,8 @@ private static void LaunchTrayIconBackgroundLoadedWebView() Variables.MainForm.Invoke(new MethodInvoker(delegate { // make sure it's ready - if (Variables.TrayIconWebView == null || Variables.TrayIconWebView.IsDisposed) PrepareTrayIconWebView(); + if (Variables.TrayIconWebView == null || Variables.TrayIconWebView.IsDisposed) + PrepareTrayIconWebView(); // show it Variables.TrayIconWebView?.MakeVisible(); @@ -514,6 +531,8 @@ private static void LaunchTrayIconCustomWebView(WebViewInfo webViewInfo) var webView = new WebView(webViewInfo); webView.Opacity = 0; + + Variables.TrayIconWebView = webView; webView.Show(); })); } @@ -545,12 +564,12 @@ internal static bool InputLanguageCheckDiffers(out bool knownToCollide, out stri // check for known OK languages if (KnownOkInputLanguage.ContainsKey(inputLanguage)) return false; - + // check for known NOT OK languages var germanLayoutDetected = false; - foreach(InputLanguage language in InputLanguage.InstalledInputLanguages) + foreach (InputLanguage language in InputLanguage.InstalledInputLanguages) { - if(language.Culture.TwoLetterISOLanguageName == "de") + if (language.Culture.TwoLetterISOLanguageName == "de") { germanLayoutDetected = true; break; diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs new file mode 100644 index 00000000..46dbd99d --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs @@ -0,0 +1,58 @@ +using HASS.Agent.Functions; +using HASS.Agent.Models.Internal; +using HASS.Agent.Resources.Localization; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.HomeAssistant.Commands; +using Newtonsoft.Json; +using Serilog; +using Syncfusion.Windows.Forms; + +namespace HASS.Agent.HomeAssistant.Commands.InternalCommands +{ + internal class TrayWebViewCommand : InternalCommand + { + private const string DefaultName = "traywebview"; + + internal TrayWebViewCommand(string entityName = DefaultName, string name = DefaultName, CommandEntityType entityType = CommandEntityType.Switch, string id = default) : base(entityName ?? DefaultName, name ?? null, string.Empty, entityType, id) + { + State = "OFF"; + } + + public override void TurnOn() + { + if (string.IsNullOrEmpty(Variables.AppSettings.TrayIconWebViewUrl)) + { + Log.Warning("[TRAYWEBVIEW] [{name}] No webview URL is configured in 'Configuration -> Tray Icon'", EntityName); + return; + } + + HelperFunctions.LaunchTrayIconWebView(); + } + + public override void TurnOff() + { + Variables.MainForm.Invoke(() => + { + if(Variables.TrayIconWebView == null) + return; + + Variables.TrayIconWebView.Opacity = 0; + + if (!Variables.AppSettings.TrayIconWebViewBackgroundLoading) { + Variables.TrayIconWebView.ForceClose(); + Variables.TrayIconWebView.Dispose(); + } + }); + } + + public override string GetState() + { + return Variables.TrayIconWebView != null && Variables.TrayIconWebView.Opacity != 0 ? "ON" : "OFF"; + } + + public override void TurnOnWithAction(string action) + { + Log.Warning("[TRAYWEBVIEW] [{name}] Launching with action is not supported", EntityName); + } + } +} diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index 04fc72c3..c895acce 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -715,6 +715,16 @@ internal static string CommandsManager_SwitchDesktopCommandDescription { } } + /// + /// Looks up a localized string similar to Shows / hides the Tray Icon Web View. + ///Requires it to be configured in "Configuration -> Tray Icon". + /// + internal static string CommandsManager_TrayWebViewCommandDescription { + get { + return ResourceManager.GetString("CommandsManager_TrayWebViewCommandDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Shows a window with the provided URL. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index 08861f67..38cecbfb 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3515,4 +3515,8 @@ Hinweis: Bei Verwendung im Satellitendienst werden keine Userspace-Anwendungen e Achtung: Das Hinzufügen der folgenden Einstellungen (Überschreibungen) kann zur Beschädigung des Sensors führen. Gehen Sie daher vorsichtig vor! + + Zeigt/verbirgt das Tray-Icon-Web-View. +Muss unter „Konfiguration -> Tray-Icon“ konfiguriert werden. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index a96a6a15..382a168f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3420,4 +3420,8 @@ Note: if used in the satellite service, it won't detect userspace applications.< Advanced Settings + + Shows / hides the Tray Icon Web View. +Requires it to be configured in "Configuration -> Tray Icon" + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 11bc91fd..d72f64e8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3391,4 +3391,8 @@ Nota: si se usa en el servicio satelital, no detectará aplicaciones del espacio Advertencia, agregar las siguientes configuraciones (anulaciones) puede dañar el sensor, ¡úselo con precaución! + + Muestra u oculta la vista web del icono de la bandeja. +Requiere que se configure en "Configuración -> Icono de la bandeja" + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 036c40c4..5280d3c2 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3424,4 +3424,8 @@ Remarque : s'il est utilisé dans le service satellite, il ne détectera pas les Attention, l'ajout des paramètres suivants (remplacements) peut casser le capteur, à utiliser avec prudence ! + + Affiche/masque l'affichage Web de l'icône de la barre d'état système. +Nécessite sa configuration dans « Configuration -> Icône de la barre d'état système » + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 7b6f0a41..acb7c723 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3412,4 +3412,8 @@ Opmerking: indien gebruikt in de satellietdienst, zal het geen gebruikersruimtet Waarschuwing: het toevoegen van de volgende instellingen (overschrijvingen) kan de sensor kapot maken, wees voorzichtig! + + Toont/verbergt het Tray Icon Web View. +Vereist dat het geconfigureerd is in "Configuratie -> Tray Icon" + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 684d9e02..e3ad5c06 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3501,4 +3501,8 @@ Uwaga: jeśli jest używany w usłudze satelitarnej, nie wykryje aplikacji przes Uwaga, dodanie następujących ustawień (nadpisań) może spowodować uszkodzenie czujnika, zachowaj ostrożność! + + Pokazuje/ukrywa Tray Icon Web View. +Wymaga konfiguracji w „Configuration -> Tray Icon” + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index bdebb522..a94fa6ad 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3437,4 +3437,8 @@ Nota: se usado no serviço de satélite, não detectará aplicações no espaço Atenção, adicionar as seguintes configurações (substituições) pode quebrar o sensor, use com cuidado! + + Mostra/oculta o Tray Icon Web View. +Requer que seja configurado em "Configuration -> Tray Icon" + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 8c25f36e..c7f9e248 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3413,4 +3413,8 @@ Requires audio device name as a payload. Advanced Settings + + Shows / hides the Tray Icon Web View. +Requires it to be configured in "Configuration -> Tray Icon" + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index b34d9222..6baf2b09 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3460,4 +3460,8 @@ Home Assistant. Внимание! Добавление следующих настроек (переопределений) может привести к поломке датчика, используйте с осторожностью! + + Показывает/скрывает веб-представление значка в трее. +Требует настройки в разделе «Конфигурация -> Значок в трее» + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index e8554e97..43005ddb 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3540,4 +3540,8 @@ Opomba: če se uporablja v satelitski storitvi, ne bo zaznal aplikacij uporabni Opozorilo, dodajanje naslednjih nastavitev (preglasitev) lahko pokvari senzor, uporabljajte previdno! + + Prikaže/skrije spletni pogled ikone pladnja. +Zahteva, da je konfiguriran v "Konfiguracija -> Ikona pladnja" + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index edc26869..e5dda1e4 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -3007,4 +3007,8 @@ Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılam Uyarı, aşağıdaki ayarların (geçersiz kılmaların) eklenmesi sensörü bozabilir, dikkatli kullanın! + + Tepsi Simgesi Web Görünümünü gösterir/gizler. +"Yapılandırma -> Tepsi Simgesi"nde yapılandırılması gerekir + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs index 9ddb228a..15305e16 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs @@ -160,6 +160,9 @@ internal static AbstractCommand ConvertConfiguredToAbstract(ConfiguredCommand co case CommandType.WebViewCommand: abstractCommand = new WebViewCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); break; + case CommandType.TrayWebViewCommand: + abstractCommand = new TrayWebViewCommand(command.EntityName, command.Name, command.EntityType, command.Id.ToString()); + break; case CommandType.MonitorSleepCommand: abstractCommand = new MonitorSleepCommand(command.EntityName, command.Name, command.EntityType, command.Id.ToString()); break; From 6b474c2a6602c4605b61529fbb7f0a03d0dc4943 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:09:47 +0200 Subject: [PATCH 40/45] Feature: quick action automation trigger (#125) https://github.com/hass-agent/HASS.Agent/pull/125 --- src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs | 7 ++++++- .../Resources/Localization/Languages.Designer.cs | 9 +++++++++ .../Resources/Localization/Languages.de.resx | 3 +++ .../Resources/Localization/Languages.en.resx | 3 +++ .../Resources/Localization/Languages.es.resx | 3 +++ .../Resources/Localization/Languages.fr.resx | 3 +++ .../Resources/Localization/Languages.nl.resx | 3 +++ .../Resources/Localization/Languages.pl.resx | 3 +++ .../Resources/Localization/Languages.pt-br.resx | 3 +++ .../Resources/Localization/Languages.resx | 3 +++ .../Resources/Localization/Languages.ru.resx | 3 +++ .../Resources/Localization/Languages.sl.resx | 3 +++ .../Resources/Localization/Languages.tr.resx | 3 +++ .../Resources/Localization/Languages.Designer.cs | 9 +++++++++ .../HASS.Agent/Resources/Localization/Languages.de.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.en.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.es.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.fr.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.nl.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.pl.resx | 3 +++ .../Resources/Localization/Languages.pt-br.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.ru.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.sl.resx | 3 +++ .../HASS.Agent/Resources/Localization/Languages.tr.resx | 3 +++ 25 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs index ad01d554..cf0f6b0a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs @@ -45,6 +45,11 @@ public enum HassAction [LocalizedDescription("HassAction_Toggle", typeof(Languages))] [Category("toggle")] [EnumMember(Value = "Toggle")] - Toggle + Toggle, + + [LocalizedDescription("HassAction_Trigger", typeof(Languages))] + [Category("trigger")] + [EnumMember(Value = "Trigger")] + Trigger } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index dac0f1dc..4faf9516 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -3380,6 +3380,15 @@ internal static string HassAction_Toggle { } } + /// + /// Looks up a localized string similar to Trigger. + /// + internal static string HassAction_Trigger { + get { + return ResourceManager.GetString("HassAction_Trigger", resourceCulture); + } + } + /// /// Looks up a localized string similar to Client certificate file not found.. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index 09bbf764..b38b81b2 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3367,4 +3367,7 @@ Willst Du den Runtime Installer herunterladen? TrayWebView + + Auslösen + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index 997c345c..7e73bd49 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3243,4 +3243,7 @@ Do you want to download the runtime installer? TrayWebView + + Trigger + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 86f336a6..0ae04d4f 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3243,4 +3243,7 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. Vista web de la bandeja + + Desencadenar + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index 39449077..a4b1bac5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3276,4 +3276,7 @@ Do you want to download the runtime installer? Vue Web de la barre d'état + + Déclencher + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 31addb36..787d2fe9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3265,4 +3265,7 @@ Wil je de runtime installatie downloaden? TrayWebView + + Activering + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index 3da9e587..08a6a802 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3353,4 +3353,7 @@ Czy chcesz pobrać plik instalacyjny? TrayWebView + + Wyzwól + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index dae75e54..c2a18493 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3290,4 +3290,7 @@ Deseja baixar o Microsoft WebView2 runtime? BandejaWebView + + Acionar + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index fef61f2c..eb13eb78 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -3226,4 +3226,7 @@ Do you want to download the runtime installer? TrayWebView + + Trigger + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index bbeaa1ad..f731ff08 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3311,4 +3311,7 @@ Home Assistant. TrayWebView + + Вызывать + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index 5f3406dc..7de61d8e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3392,4 +3392,7 @@ Ali želite prenesti runtime installer? TrayWebView + + Sprožilec + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index 243bb449..703db654 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2850,4 +2850,7 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku TepsiWebGörünümü + + Tetiklemek + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index c895acce..ea0b00cd 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -3689,6 +3689,15 @@ internal static string HassAction_Toggle { } } + /// + /// Looks up a localized string similar to Trigger. + /// + internal static string HassAction_Trigger { + get { + return ResourceManager.GetString("HassAction_Trigger", resourceCulture); + } + } + /// /// Looks up a localized string similar to Client certificate file not found.. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index 38cecbfb..e1737ea8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3519,4 +3519,7 @@ Hinweis: Bei Verwendung im Satellitendienst werden keine Userspace-Anwendungen e Zeigt/verbirgt das Tray-Icon-Web-View. Muss unter „Konfiguration -> Tray-Icon“ konfiguriert werden. + + Auslösen + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 382a168f..615a5750 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3424,4 +3424,7 @@ Note: if used in the satellite service, it won't detect userspace applications.< Shows / hides the Tray Icon Web View. Requires it to be configured in "Configuration -> Tray Icon" + + Trigger + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index d72f64e8..7a8fa344 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3395,4 +3395,7 @@ Nota: si se usa en el servicio satelital, no detectará aplicaciones del espacio Muestra u oculta la vista web del icono de la bandeja. Requiere que se configure en "Configuración -> Icono de la bandeja" + + Desencadenar + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 5280d3c2..bbf2e98c 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3428,4 +3428,7 @@ Remarque : s'il est utilisé dans le service satellite, il ne détectera pas les Affiche/masque l'affichage Web de l'icône de la barre d'état système. Nécessite sa configuration dans « Configuration -> Icône de la barre d'état système » + + Déclencher + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index acb7c723..8d767c18 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3416,4 +3416,7 @@ Opmerking: indien gebruikt in de satellietdienst, zal het geen gebruikersruimtet Toont/verbergt het Tray Icon Web View. Vereist dat het geconfigureerd is in "Configuratie -> Tray Icon" + + Activering + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index e3ad5c06..4326d1e4 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3505,4 +3505,7 @@ Uwaga: jeśli jest używany w usłudze satelitarnej, nie wykryje aplikacji przes Pokazuje/ukrywa Tray Icon Web View. Wymaga konfiguracji w „Configuration -> Tray Icon” + + Wyzwól + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index a94fa6ad..535978b8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3441,4 +3441,7 @@ Nota: se usado no serviço de satélite, não detectará aplicações no espaço Mostra/oculta o Tray Icon Web View. Requer que seja configurado em "Configuration -> Tray Icon" + + Acionar + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index c7f9e248..16c55408 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3417,4 +3417,7 @@ Requires audio device name as a payload. Shows / hides the Tray Icon Web View. Requires it to be configured in "Configuration -> Tray Icon" + + Trigger + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 6baf2b09..20e97fb9 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3464,4 +3464,7 @@ Home Assistant. Показывает/скрывает веб-представление значка в трее. Требует настройки в разделе «Конфигурация -> Значок в трее» + + Вызывать + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 43005ddb..5057bb6e 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3544,4 +3544,7 @@ Opomba: če se uporablja v satelitski storitvi, ne bo zaznal aplikacij uporabni Prikaže/skrije spletni pogled ikone pladnja. Zahteva, da je konfiguriran v "Konfiguracija -> Ikona pladnja" + + Sprožilec + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index e5dda1e4..f2da35f2 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -3011,4 +3011,7 @@ Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılam Tepsi Simgesi Web Görünümünü gösterir/gizler. "Yapılandırma -> Tepsi Simgesi"nde yapılandırılması gerekir + + Tetiklemek + \ No newline at end of file From 5082f67435def0730f51a4e600482beb322ea991 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:38:48 +0200 Subject: [PATCH 41/45] Feature: quick action button press (#126) https://github.com/hass-agent/HASS.Agent/pull/126 --- .../HASS.Agent.Shared/Enums/HassAction.cs | 7 ++++++- .../HASS.Agent.Shared/Enums/HassDomain.cs | 7 ++++++- .../Localization/Languages.Designer.cs | 18 ++++++++++++++++++ .../Resources/Localization/Languages.de.resx | 6 ++++++ .../Resources/Localization/Languages.en.resx | 6 ++++++ .../Resources/Localization/Languages.es.resx | 6 ++++++ .../Resources/Localization/Languages.fr.resx | 6 ++++++ .../Resources/Localization/Languages.nl.resx | 6 ++++++ .../Resources/Localization/Languages.pl.resx | 6 ++++++ .../Localization/Languages.pt-br.resx | 6 ++++++ .../Resources/Localization/Languages.resx | 6 ++++++ .../Resources/Localization/Languages.ru.resx | 6 ++++++ .../Resources/Localization/Languages.sl.resx | 6 ++++++ .../Resources/Localization/Languages.tr.resx | 6 ++++++ .../Forms/QuickActions/QuickActionsMod.cs | 8 ++++++++ .../HASS.Agent/HomeAssistant/HassApiManager.cs | 3 +++ .../Localization/Languages.Designer.cs | 18 ++++++++++++++++++ .../Resources/Localization/Languages.de.resx | 6 ++++++ .../Resources/Localization/Languages.en.resx | 6 ++++++ .../Resources/Localization/Languages.es.resx | 6 ++++++ .../Resources/Localization/Languages.fr.resx | 6 ++++++ .../Resources/Localization/Languages.nl.resx | 6 ++++++ .../Resources/Localization/Languages.pl.resx | 6 ++++++ .../Localization/Languages.pt-br.resx | 6 ++++++ .../Resources/Localization/Languages.resx | 6 ++++++ .../Resources/Localization/Languages.ru.resx | 6 ++++++ .../Resources/Localization/Languages.sl.resx | 6 ++++++ .../Resources/Localization/Languages.tr.resx | 6 ++++++ 28 files changed, 191 insertions(+), 2 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs index cf0f6b0a..6bead640 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs @@ -50,6 +50,11 @@ public enum HassAction [LocalizedDescription("HassAction_Trigger", typeof(Languages))] [Category("trigger")] [EnumMember(Value = "Trigger")] - Trigger + Trigger, + + [LocalizedDescription("HassAction_Press", typeof(Languages))] + [Category("press")] + [EnumMember(Value = "Press")] + Press } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs index 2d153255..4b453b34 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs @@ -57,6 +57,11 @@ public enum HassDomain [LocalizedDescription("HassDomain_Switch", typeof(Languages))] [Category("switch")] [EnumMember(Value = "Switch")] - Switch + Switch, + + [LocalizedDescription("HassDomain_Button", typeof(Languages))] + [Category("button")] + [EnumMember(Value = "Button")] + Button } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index 4faf9516..931858fa 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -3362,6 +3362,15 @@ internal static string HassAction_Play { } } + /// + /// Looks up a localized string similar to Press. + /// + internal static string HassAction_Press { + get { + return ResourceManager.GetString("HassAction_Press", resourceCulture); + } + } + /// /// Looks up a localized string similar to Stop. /// @@ -3479,6 +3488,15 @@ internal static string HassDomain_Automation { } } + /// + /// Looks up a localized string similar to Button. + /// + internal static string HassDomain_Button { + get { + return ResourceManager.GetString("HassDomain_Button", resourceCulture); + } + } + /// /// Looks up a localized string similar to Climate. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index b38b81b2..c5179acd 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3370,4 +3370,10 @@ Willst Du den Runtime Installer herunterladen? Auslösen + + Knopf + + + Presse + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index 7e73bd49..f881bf80 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3246,4 +3246,10 @@ Do you want to download the runtime installer? Trigger + + Press + + + Button + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 0ae04d4f..2ea0d91a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3246,4 +3246,10 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. Desencadenar + + Botón + + + Prensa + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index a4b1bac5..27c3ec38 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3279,4 +3279,10 @@ Do you want to download the runtime installer? Déclencher + + Knop + + + Presse + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 787d2fe9..3a9871c4 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3268,4 +3268,10 @@ Wil je de runtime installatie downloaden? Activering + + Knop + + + Pers + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index 08a6a802..6947c51d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3356,4 +3356,10 @@ Czy chcesz pobrać plik instalacyjny? Wyzwól + + Przycisk + + + Naciśnij + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index c2a18493..a7b67ecb 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3293,4 +3293,10 @@ Deseja baixar o Microsoft WebView2 runtime? Acionar + + Botão + + + Imprensa + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index eb13eb78..8fc95014 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -3229,4 +3229,10 @@ Do you want to download the runtime installer? Trigger + + Press + + + Button + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index f731ff08..6f5d7c6e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3314,4 +3314,10 @@ Home Assistant. Вызывать + + Button + + + Нажимать + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index 7de61d8e..1edd421c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3395,4 +3395,10 @@ Ali želite prenesti runtime installer? Sprožilec + + Gumb + + + Pritisnite + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index 703db654..28956a1c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2853,4 +2853,10 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku Tetiklemek + + Buton + + + Basmak + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs index 827c5c06..dcb776fb 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs @@ -388,6 +388,14 @@ private void LoadEntityList() CbEntity.Items.Add(item.EntityName); } break; + + case HassDomain.Button: + foreach (var item in HassApiManager.ButtonList) + { + CbEntity.AutoCompleteCustomSource.Add(item); + CbEntity.Items.Add(item); + } + break; } } diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs index 524e6a4d..36a49af8 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs @@ -43,6 +43,7 @@ internal static class HassApiManager internal static List CoverList = new(); internal static List ClimateList = new(); internal static List MediaPlayerList = new(); + internal static List ButtonList = new(); private static readonly string[] OnStates = { "on", "playing", "open", "opening" }; private static readonly string[] OffStates = { "off", "idle", "paused", "stopped", "closed", "closing" }; @@ -389,6 +390,7 @@ private static async Task LoadEntitiesAsync(bool clearCurrent = false) CoverList.Clear(); ClimateList.Clear(); MediaPlayerList.Clear(); + ButtonList.Clear(); } try @@ -402,6 +404,7 @@ private static async Task LoadEntitiesAsync(bool clearCurrent = false) await LoadDomain("cover", CoverList); await LoadDomain("climate", ClimateList); await LoadDomain("media_player", MediaPlayerList); + await LoadDomain("button", ButtonList); if (ManagerStatus != HassManagerStatus.Failed) return; diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index ea0b00cd..269df973 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -3671,6 +3671,15 @@ internal static string HassAction_Play { } } + /// + /// Looks up a localized string similar to Press. + /// + internal static string HassAction_Press { + get { + return ResourceManager.GetString("HassAction_Press", resourceCulture); + } + } + /// /// Looks up a localized string similar to Stop. /// @@ -3788,6 +3797,15 @@ internal static string HassDomain_Automation { } } + /// + /// Looks up a localized string similar to Button. + /// + internal static string HassDomain_Button { + get { + return ResourceManager.GetString("HassDomain_Button", resourceCulture); + } + } + /// /// Looks up a localized string similar to Climate. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index e1737ea8..63a460db 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3522,4 +3522,10 @@ Muss unter „Konfiguration -> Tray-Icon“ konfiguriert werden. Auslösen + + Knopf + + + Presse + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 615a5750..8dc6f1b7 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3427,4 +3427,10 @@ Requires it to be configured in "Configuration -> Tray Icon" Trigger + + Button + + + Press + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 7a8fa344..16b9e7d7 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3398,4 +3398,10 @@ Requiere que se configure en "Configuración -> Icono de la bandeja" Desencadenar + + Botón + + + Prensa + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index bbf2e98c..1bfdf5df 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3431,4 +3431,10 @@ Nécessite sa configuration dans « Configuration -> Icône de la barre d'ét Déclencher + + Knop + + + Presse + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 8d767c18..ea161750 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3419,4 +3419,10 @@ Vereist dat het geconfigureerd is in "Configuratie -> Tray Icon" Activering + + Knop + + + Pers + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 4326d1e4..b8a5706d 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3508,4 +3508,10 @@ Wymaga konfiguracji w „Configuration -> Tray Icon” Wyzwól + + Przycisk + + + Naciśnij + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 535978b8..f171b0c2 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3444,4 +3444,10 @@ Requer que seja configurado em "Configuration -> Tray Icon" Acionar + + Botão + + + Imprensa + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 16c55408..da531004 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -3420,4 +3420,10 @@ Requires it to be configured in "Configuration -> Tray Icon" Trigger + + Press + + + Button + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 20e97fb9..24b2bb4f 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3467,4 +3467,10 @@ Home Assistant. Вызывать + + Button + + + Нажимать + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 5057bb6e..0c0728a7 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3547,4 +3547,10 @@ Zahteva, da je konfiguriran v "Konfiguracija -> Ikona pladnja" Sprožilec + + Gumb + + + Pritisnite + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index f2da35f2..e1d3413b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -3014,4 +3014,10 @@ Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılam Tetiklemek + + Buton + + + Basmak + \ No newline at end of file From 3fd5fda1e53ceebfd5df8bc3489301e71ffd322a Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:27:37 +0200 Subject: [PATCH 42/45] Fix: readded service stop logic (#133) --- .../InstallerScript-Service.iss | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss index fba2c845..96c5c501 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -81,36 +81,34 @@ begin Result := True; end; -//procedure CurStepChanged(CurStep: TSetupStep); -//var -// ProgressPage: TOutputProgressWizardPage; -// I, Step, Wait, ResultCode: Integer; -//begin -// if CurStep = ssInstall then -// begin -// //MsgBox(ExpandConstant('{sys}') + '\sc.exe', mbInformation, MB_OK); -// //MsgBox('stop ' + ExpandConstant('{#ServiceName}'), mbInformation, MB_OK); -// Exec(ExpandConstant('{sys}') + '\sc.exe', 'stop ' + ExpandConstant('{#ServiceName}'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) -// -// //thanks to https://stackoverflow.com/a/39827761 -// Wait := 5000; -// Step := 100; -// ProgressPage := -// CreateOutputProgressPage( -// WizardForm.PageNameLabel.Caption, -// WizardForm.PageDescriptionLabel.Caption); -// ProgressPage.SetText('Making sure the satellite service is stopped...', ''); -// ProgressPage.SetProgress(0, Wait); -// ProgressPage.Show; -// try -// for I := 0 to Wait div Step do -// begin -// ProgressPage.SetProgress(I * Step, Wait); -// Sleep(Step); -// end; -// finally -// ProgressPage.Hide; -// ProgressPage.Free; -// end; -// end; -//end; \ No newline at end of file +procedure CurStepChanged(CurStep: TSetupStep); +var + ProgressPage: TOutputProgressWizardPage; + I, Step, Wait, ResultCode: Integer; +begin + if CurStep = ssInstall then + begin + Exec(ExpandConstant('{sys}') + '\sc.exe', 'stop ' + ExpandConstant('{#ServiceName}'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) + + //thanks to https://stackoverflow.com/a/39827761 + Wait := 5000; + Step := 100; + ProgressPage := + CreateOutputProgressPage( + WizardForm.PageNameLabel.Caption, + WizardForm.PageDescriptionLabel.Caption); + ProgressPage.SetText('Making sure the satellite service is stopped...', ''); + ProgressPage.SetProgress(0, Wait); + ProgressPage.Show; + try + for I := 0 to Wait div Step do + begin + ProgressPage.SetProgress(I * Step, Wait); + Sleep(Step); + end; + finally + ProgressPage.Hide; + ProgressPage.Free; + end; + end; +end; \ No newline at end of file From ee6c38fae65a04814ee0676781efefa9e5be79ac Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:05:40 +0200 Subject: [PATCH 43/45] Fix: added additional null check for malfunctioning sensors (#134) --- .../Managers/DeviceSensors/AccelerometerSensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs | 6 +++++- .../HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs | 6 +++++- .../HASS.Agent/Managers/DeviceSensors/CompassSensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs | 7 +++++-- .../Managers/DeviceSensors/InclinometerSensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/LightSensor.cs | 6 +++++- .../Managers/DeviceSensors/MagnetometerSensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs | 3 +++ .../HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs | 5 ++++- .../HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs | 6 +++++- 13 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs index eb7806c5..a8e3013b 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs @@ -29,6 +29,9 @@ public string Measurement return null; var sensorReading = _accelerometer.GetCurrentReading(); + if(sensorReading == null) + return null; + var accX = Math.Round((decimal)sensorReading.AccelerationX, 2); var accY = Math.Round((decimal)sensorReading.AccelerationX, 2); var accZ = Math.Round((decimal)sensorReading.AccelerationX, 2); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs index be2956d1..c02d0a7b 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs @@ -27,6 +27,9 @@ public string Measurement return null; var sensorReading = _activitySensor.GetCurrentReadingAsync().AsTask().Result; + if (sensorReading == null) + return null; + _attributes[AttributeConfidence] = sensorReading.Confidence.ToString(); _attributes[AttributeTimestamp] = sensorReading.Timestamp.ToLocalTime().ToString(); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs index 958b9370..2662e6ae 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs @@ -23,7 +23,11 @@ public string Measurement if (!Available) return null; - return _altimeter.GetCurrentReading().AltitudeChangeInMeters.ToString(); + var sensorReading = _altimeter.GetCurrentReading(); + if (sensorReading == null) + return null; + + return sensorReading.AltitudeChangeInMeters.ToString(); } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs index 231261fd..189883fc 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs @@ -23,7 +23,11 @@ public string Measurement if (!Available) return null; - return _barometer.GetCurrentReading().StationPressureInHectopascals.ToString(); + var sensorReading = _barometer.GetCurrentReading(); + if (sensorReading == null) + return null; + + return sensorReading.StationPressureInHectopascals.ToString(); } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs index ec1a5040..2f94d5b6 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs @@ -26,6 +26,9 @@ public string Measurement return null; var sensorReading = _compass.GetCurrentReading(); + if (sensorReading == null) + return null; + _attributes[AttributeMagneticNorth] = Math.Round((decimal)sensorReading.HeadingMagneticNorth, 2).ToString(); return Math.Round((decimal)sensorReading.HeadingTrueNorth, 2).ToString(); } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs index 209c73c8..39b4144b 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs @@ -28,6 +28,9 @@ public string Measurement return null; var sensorReading = _gyrometer.GetCurrentReading(); + if (sensorReading == null) + return null; + var angVelX = Math.Round((decimal)sensorReading.AngularVelocityX, 2); var angVelY = Math.Round((decimal)sensorReading.AngularVelocityY, 2); var angVelZ = Math.Round((decimal)sensorReading.AngularVelocityZ, 2); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs index 30c748b3..4844c0a9 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs @@ -23,8 +23,11 @@ public string Measurement if (!Available) return null; - var sensorReading = _hingeAngelSensor.GetCurrentReadingAsync().AsTask().Result.AngleInDegrees; - return Math.Round((decimal)sensorReading, 2).ToString(); + var sensorReading = _hingeAngelSensor.GetCurrentReadingAsync().AsTask().Result; + if (sensorReading == null) + return null; + + return Math.Round((decimal)sensorReading.AngleInDegrees, 2).ToString(); } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs index ec582c38..a3d1e497 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs @@ -30,6 +30,9 @@ public string Measurement return null; var sensorReading = _inclinometer.GetCurrentReading(); + if (sensorReading == null) + return null; + _attributes[AttributePitchDegrees] = Math.Round((decimal)sensorReading.PitchDegrees, 2).ToString(); _attributes[AttributeYawDegrees] = Math.Round((decimal)sensorReading.YawDegrees, 2).ToString(); _attributes[AttributeRollDegrees] = Math.Round((decimal)sensorReading.RollDegrees, 2).ToString(); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs index 167d8cd7..74733911 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs @@ -22,8 +22,12 @@ public string Measurement { if (!Available) return null; + + var sensorReading = _lightSensor.GetCurrentReading(); + if (sensorReading == null) + return null; - return Math.Round((decimal)_lightSensor.GetCurrentReading().IlluminanceInLux, 2).ToString(); + return Math.Round((decimal)sensorReading.IlluminanceInLux, 2).ToString(); } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs index 61b1f538..d85a5bfe 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs @@ -28,6 +28,9 @@ public string Measurement return null; var sensorReading = _magnetometer.GetCurrentReading(); + if (sensorReading == null) + return null; + var magFieldX = Math.Round((decimal)sensorReading.MagneticFieldX,2); var magFieldY = Math.Round((decimal)sensorReading.MagneticFieldY, 2); var magFieldZ = Math.Round((decimal)sensorReading.MagneticFieldZ, 2); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs index d57b1446..f8b46f17 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs @@ -28,6 +28,9 @@ public string Measurement return null; var sensorReading = _orientationSensor.GetCurrentReading(); + if (sensorReading == null) + return null; + _attributes[AttributeRotationMatrix] = JsonConvert.SerializeObject(sensorReading.RotationMatrix); _attributes[AttributeYawAccuracy] = sensorReading.YawAccuracy.ToString(); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs index 013b60c4..f4011458 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs @@ -26,7 +26,10 @@ public string Measurement var totalStepCount = 0; var sensorReadings = _pedometer.GetCurrentReadings(); - foreach(var sensorReading in sensorReadings) + if (sensorReadings == null) + return null; + + foreach (var sensorReading in sensorReadings) { var attributeCumulativeSteps = $"{sensorReading.Key}CumulativeSteps"; _attributes[attributeCumulativeSteps] = sensorReading.Value.CumulativeSteps.ToString(); diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs index de60c167..3d46f893 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs @@ -23,7 +23,11 @@ public string Measurement if (!Available) return null; - return _proximitySensor.GetCurrentReading().DistanceInMillimeters.ToString(); + var sensorReading = _proximitySensor.GetCurrentReading(); + if (sensorReading == null) + return null; + + return sensorReading.DistanceInMillimeters.ToString(); } } From cdb26c69903f54465e46636992626ddecc07201f Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:17:50 +0200 Subject: [PATCH 44/45] Release: 2.1.0-beta3 (#135) * changed version to beta3 * changed version to beta3 * added note to the migration task to be used only once --- src/HASS.Agent.Installer/InstallerScript-Service.iss | 2 +- src/HASS.Agent.Installer/InstallerScript.iss | 4 ++-- .../HASS.Agent.Satellite.Service.csproj | 2 +- src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj | 2 +- src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss index 96c5c501..f630602d 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent Satellite Service" -#define MyAppVersion "2.1.0-beta2" +#define MyAppVersion "2.1.0-beta3" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.Satellite.Service.exe" diff --git a/src/HASS.Agent.Installer/InstallerScript.iss b/src/HASS.Agent.Installer/InstallerScript.iss index d1425a74..52f1b638 100644 --- a/src/HASS.Agent.Installer/InstallerScript.iss +++ b/src/HASS.Agent.Installer/InstallerScript.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent" -#define MyAppVersion "2.1.0-beta2" +#define MyAppVersion "2.1.0-beta3" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.exe" @@ -62,7 +62,7 @@ Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon [Run] -Filename: "{app}\{#MyAppExeName}"; Parameters: "compat_migrate"; Description: "Try to migrate configuration (administrative permissions required)"; Flags: postinstall skipifsilent runascurrentuser unchecked +Filename: "{app}\{#MyAppExeName}"; Parameters: "compat_migrate"; Description: "Try to migrate configuration - use only once (administrative permissions required)"; Flags: postinstall skipifsilent runascurrentuser unchecked Filename: "{tmp}\HASS.Agent.Service.Installer.exe"; Description: "Install Satellite Service (administrative permissions required)"; Flags: postinstall runascurrentuser Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: postinstall skipifsilent nowait diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index 243ebf47..007702cc 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 x64 x64;x86 - 2.1.0-beta2 + 2.1.0-beta3 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index bd41bf7b..968e8ba9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -12,7 +12,7 @@ https://github.com/hass-agent/HASS.Agent 2.1.0 2.1.0 - 2.1.0-beta2 + 2.1.0-beta3 logo_128.png True hassagent.ico diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index b4d922eb..1612eadf 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -12,7 +12,7 @@ x64 x64;x86 full - 2.1.0-beta2 + 2.1.0-beta3 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. From ec6cf762347113e3af4e1bab2d68e9e672260e6b Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 17 Sep 2024 16:01:21 +0200 Subject: [PATCH 45/45] Add Device Location Track Sensor, and minor code cleanups --- .../HASS.Agent.Shared/Enums/SensorType.cs | 4 + .../Extensions/CommandExtensions.cs | 25 +- .../Extensions/SensorExtensions.cs | 3 +- .../Managers/PowershellManager.cs | 5 +- .../Localization/Languages.Designer.cs | 9 + .../Resources/Localization/Languages.de.resx | 3 + .../Resources/Localization/Languages.en.resx | 3 + .../Resources/Localization/Languages.es.resx | 3 + .../Resources/Localization/Languages.fr.resx | 3 + .../Resources/Localization/Languages.nl.resx | 3 + .../Resources/Localization/Languages.pl.resx | 3 + .../Localization/Languages.pt-br.resx | 3 + .../Resources/Localization/Languages.resx | 3717 +++++++++-------- .../Resources/Localization/Languages.ru.resx | 3 + .../Resources/Localization/Languages.sl.resx | 3 + .../Resources/Localization/Languages.tr.resx | 3 + src/HASS.Agent/HASS.Agent/HASS.Agent.csproj | 40 +- .../SingleValue/DeviceTrackerSensor.cs | 104 + .../Models/Internal/GeolocationInfo.cs | 37 + .../Localization/Languages.Designer.cs | 13 + .../Resources/Localization/Languages.de.resx | 6 + .../Resources/Localization/Languages.en.resx | 7 + .../Resources/Localization/Languages.es.resx | 7 + .../Resources/Localization/Languages.fr.resx | 7 + .../Resources/Localization/Languages.nl.resx | 7 + .../Resources/Localization/Languages.pl.resx | 7 + .../Localization/Languages.pt-br.resx | 7 + .../Resources/Localization/Languages.resx | 7 + .../Resources/Localization/Languages.ru.resx | 7 + .../Resources/Localization/Languages.sl.resx | 7 + .../Resources/Localization/Languages.tr.resx | 2011 ++++----- .../HASS.Agent/Sensors/SensorsManager.cs | 7 + .../HASS.Agent/Settings/StoredSensors.cs | 3 + 33 files changed, 3192 insertions(+), 2885 deletions(-) create mode 100644 src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs create mode 100644 src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs index 65348afa..4e2bbb53 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs @@ -58,6 +58,10 @@ public enum SensorType [LocalizedDescription("SensorType_GeoLocationSensor", typeof(Languages))] [EnumMember(Value = "GeoLocationSensor")] GeoLocationSensor, + + [LocalizedDescription("SensorType_DeviceTrackerSensor", typeof(Languages))] + [EnumMember(Value = "DeviceTrackerSensor")] + DeviceTrackerSensor, [LocalizedDescription("SensorType_GpuLoadSensor", typeof(Languages))] [EnumMember(Value = "GpuLoadSensor")] diff --git a/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs b/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs index 0038d6d3..c7983404 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs @@ -21,18 +21,19 @@ public static string GetCommandName(this CommandType commandType) //TODO: remove after tests - /// - /// Returns the name of the commandtype, based on the provided devicename - /// - /// - /// - /// - /* public static string GetCommandName(this CommandType commandType, string deviceName) - { - var (_, name) = commandType.GetLocalizedDescriptionAndKey(); - var commandName = name.ToLower(); + /* + /// + /// Returns the name of the commandtype, based on the provided devicename + /// + /// + /// + /// + public static string GetCommandName(this CommandType commandType, string deviceName) + { + var (_, name) = commandType.GetLocalizedDescriptionAndKey(); + var commandName = name.ToLower(); - return $"{SharedHelperFunctions.GetSafeValue(deviceName)}_{commandName}"; - }*/ + return $"{SharedHelperFunctions.GetSafeValue(deviceName)}_{commandName}"; + }*/ } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs b/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs index ef91a026..2dd5617c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs @@ -21,13 +21,14 @@ public static string GetSensorName(this SensorType sensorType) //TODO: remove after tests + /* /// /// Returns the name of the sensortype, based on the provided devicename /// /// /// /// -/* public static string GetSensorName(this SensorType sensorType, string deviceName) + public static string GetSensorName(this SensorType sensorType, string deviceName) { var (_, name) = sensorType.GetLocalizedDescriptionAndKey(); var sensorName = name.ToLower(); diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs index f28df5d7..37e13b4d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs @@ -2,10 +2,8 @@ using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; using System.Text; -using System.Text.RegularExpressions; -using CliWrap; + using Newtonsoft.Json; using Serilog; @@ -105,6 +103,7 @@ private static bool ExecuteHeadless(string command, string parameters, bool isSc /// Executes a Powershell script, logs the output if it fails /// /// + /// /// /// public static bool ExecuteScript(string script, string parameters, TimeSpan timeout) => Execute(script, parameters, true, timeout); diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index 931858fa..0dd73ba8 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -6591,6 +6591,15 @@ internal static string SensorType_CurrentVolumeSensor { } } + /// + /// Looks up a localized string similar to DeviceLocationTracker. + /// + internal static string SensorType_DeviceTrackerSensor { + get { + return ResourceManager.GetString("SensorType_DeviceTrackerSensor", resourceCulture); + } + } + /// /// Looks up a localized string similar to Display. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index c5179acd..e1fa5ece 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3376,4 +3376,7 @@ Willst Du den Runtime Installer herunterladen? Presse + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index f881bf80..ef59f0ef 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3252,4 +3252,7 @@ Do you want to download the runtime installer? Button + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 2ea0d91a..30e9d133 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3252,4 +3252,7 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. Prensa + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index 27c3ec38..b1fdcaaa 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3285,4 +3285,7 @@ Do you want to download the runtime installer? Presse + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 3a9871c4..c43f0a20 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3274,4 +3274,7 @@ Wil je de runtime installatie downloaden? Pers + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index 6947c51d..64ffb845 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3362,4 +3362,7 @@ Czy chcesz pobrać plik instalacyjny? Naciśnij + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index a7b67ecb..e6e688a5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3299,4 +3299,7 @@ Deseja baixar o Microsoft WebView2 runtime? Imprensa + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index 8fc95014..cdf119f3 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -1,76 +1,96 @@  + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, ... + System.Resources.ResXResourceWriter, System.Windows.Forms, ... + this is my long stringthis is a comment + Blue + + [base64 mime encoded serialized .NET Framework object] + + + [base64 mime encoded string representing a byte array form of the .NET Framework object] + This is a comment + + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + + + + + + + + + + + + + + + + + + - + + @@ -89,296 +109,296 @@ text/microsoft-resx - 1.3 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - This page allows you to configure bindings with external tools. - + This page allows you to configure bindings with external tools. + - Clear Image Cache - + Clear Image Cache + - Open Folder - + Open Folder + - Image Cache Location - + Image Cache Location + - days - + days + - Port - + Port + - Show Test Notification - + Show Test Notification + - Execute Port Reservation - + Execute Port Reservation + - Note: 5115 is the default port, only change it if you changed it in Home Assistant. - + Note: 5115 is the default port, only change it if you changed it in Home Assistant. + - Yes, accept notifications on port - + Yes, accept notifications on port + - HASS.Agent can receive notifications from Home Assistant, using text and/or images. + HASS.Agent can receive notifications from Home Assistant, using text and/or images. Do you want to enable this function? - + - HASS.Agent-Notifier GitHub Page - + HASS.Agent-Notifier GitHub Page + - Make sure you follow these steps: + Make sure you follow these steps: - Install HASS.Agent-Notifier integration - Restart Home Assistant - Configure a notifier entity - Restart Home Assistant - + - To use notifications, you need to install and configure the HASS.Agent-notifier integration in + To use notifications, you need to install and configure the HASS.Agent-notifier integration in Home Assistant. This is very easy using HACS but may also be installed manually, visit the link below for more information. - + - Name - + Name + - Type - + Type + - Apply - + Apply + - Stored! - + Stored! + - Name - + Name + - Type - + Type + - HASS.Agent Port Reservation - + HASS.Agent Port Reservation + - HASS.Agent Post Update - + HASS.Agent Post Update + - HASS.Agent Restarter - + HASS.Agent Restarter + - Name - + Name + - Type - + Type + - Type - + Type + - Domain - + Domain + - Entity - + Entity + - Action - + Action + - Hotkey - + Hotkey + - Description - + Description + - Domain - + Domain + - enable hotkey - + enable hotkey + - Name - + Name + - Type - + Type + - Sensors Configuration - + Sensors Configuration + - setting 1 - + setting 1 + - Setting 2 - + Setting 2 + - Setting 3 - + Setting 3 + - Type - + Type + - General - + General + - MQTT - + MQTT + - Commands - + Commands + - Sensors - + Sensors + - General - + General + - External Tools - + External Tools + - Home Assistant API - + Home Assistant API + - Hotkey - + Hotkey + - Local Storage - + Local Storage + - Logging - + Logging + - MQTT - + MQTT + - Notifications - + Notifications + - Satellite Service - + Satellite Service + - Startup - + Startup + - Updates - + Updates + - Browse HASS.Agent documentation and usage examples. - + Browse HASS.Agent documentation and usage examples. + - Show HASS.Agent - + Show HASS.Agent + - Show Quick Actions - + Show Quick Actions + - Configuration - + Configuration + - Manage Quick Actions - + Manage Quick Actions + - Manage Local Sensors - + Manage Local Sensors + - Manage Commands - + Manage Commands + - Check for Updates - + Check for Updates + - Donate - + Donate + - Help && Contact - + Help && Contact + - About - + About + - Exit HASS.Agent - + Exit HASS.Agent + - Loading.. - + Loading.. + - Loading.. - + Loading.. + - notification api: - + notification api: + - HASS.Agent Onboarding - + HASS.Agent Onboarding + - Fetching info, please wait.. - + Fetching info, please wait.. + - Execute a custom command. + Execute a custom command. These commands run without special elevation. To run elevated, create a Scheduled Task, and use 'schtasks /Run /TN "TaskName"' as the command to execute your task. Or enable 'run as low integrity' for even stricter execution. - + - Executes the command through the configured custom executor (in Configuration -> External Tools). + Executes the command through the configured custom executor (in Configuration -> External Tools). Your command is provided as an argument 'as is', so you have to supply your own quotes etc. if necessary. - + - Sets the machine in hibernation. - + Sets the machine in hibernation. + - Simulates a single keypress. + Simulates a single keypress. Click on the 'keycode' textbox and press the key you want simulated. The corresponding keycode will be entered for you. If you need more keys and/or modifiers like CTRL, use the MultipleKeys command. - + - Launches the provided URL, by default in your default browser. + Launches the provided URL, by default in your default browser. To use 'incognito', provide a specific browser in Configuration -> External Tools. If you just want a window with a specific URL (not an entire browser), use a 'WebView' command. - + - Locks the current session. - + Locks the current session. + - Logs off the current session. - + Logs off the current session. + - Simulates 'Mute' key. - + Simulates 'Mute' key. + - Simulates 'Media Next' key. - + Simulates 'Media Next' key. + - Simulates 'Media Pause/Play' key. - + Simulates 'Media Pause/Play' key. + - Simulates 'Media Previous' key. - + Simulates 'Media Previous' key. + - Simulates 'Volume Down' key. - + Simulates 'Volume Down' key. + - Simulates 'Volume Up' key. - + Simulates 'Volume Up' key. + - Simulates pressing mulitple keys. + Simulates pressing mulitple keys. You need to put [ ] between every key, otherwise HASS.Agent can't tell them apart. So say you want to press X TAB Y SHIFT-Z, it'd be [X] [{TAB}] [Y] [+Z]. @@ -393,803 +413,803 @@ There are a few tricks you can use: - For multiple presses, use {z 15}, which means Z will get pressed 15 times. More info: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.sendkeys - + - Execute a Powershell command or script. + Execute a Powershell command or script. You can either provide the location of a script (*.ps1), or a single-line command. This will run without special elevation. - + - Resets all sensor checks, forcing all sensors to process and send their value. + Resets all sensor checks, forcing all sensors to process and send their value. Useful for example if you want to force HASS.Agent to update all your sensors after a HA reboot. - + - Restarts the machine after one minute. + Restarts the machine after one minute. Tip: Accidentally triggered? Run 'shutdown /a' to abort shutdown. - + - Shuts down the machine after one minute. + Shuts down the machine after one minute. Tip: Accidentally triggered? Run 'shutdown /a' to abort shutdown. - + - Puts the machine to sleep. + Puts the machine to sleep. Note: due to a limitation in Windows, this only works if hibernation is disabled, otherwise it will just hibernate. You can use something like NirCmd (http://www.nirsoft.net/utils/nircmd.html) to circumvent this. - + - Please enter the location of your browser's binary! (.exe file) - + Please enter the location of your browser's binary! (.exe file) + - The browser binary provided could not be found, please ensure the path is correct and try again. - + The browser binary provided could not be found, please ensure the path is correct and try again. + - No incognito arguments were provided so the browser will likely launch normally. + No incognito arguments were provided so the browser will likely launch normally. Do you want to continue? - + - Something went wrong while launching your browser in incognito mode! + Something went wrong while launching your browser in incognito mode! Please check the logs for more information. - + - Please enter a valid API key! - + Please enter a valid API key! + - Please enter a value for your Home Assistant's URI. - + Please enter a value for your Home Assistant's URI. + - Unable to connect, the following error was returned: + Unable to connect, the following error was returned: {0} - + - Connection OK! + Connection OK! Home Assistant version: {0} - + - Image cache has been cleared! - + Image cache has been cleared! + - Cleaning.. - + Cleaning.. + - Notifications are currently disabled, please enable them and restart the HASS.Agent, then try again. - + Notifications are currently disabled, please enable them and restart the HASS.Agent, then try again. + - The test notification should have appeared, if you did not receive it please check the logs or consult the documentation for troubleshooting tips. + The test notification should have appeared, if you did not receive it please check the logs or consult the documentation for troubleshooting tips. Note: This only tests locally whether notifications can be shown! - + - This is a test notification! - + This is a test notification! + - Executing, please wait.. - + Executing, please wait.. + - Something went wrong whilst reserving the port! + Something went wrong whilst reserving the port! Manual execution is required and a command has been copied to your clipboard, please open an elevated terminal and paste the command. Additionally, remember to change your Firewall Rules port! - + - Not Installed - + Not Installed + - Disabled - + Disabled + - Running - + Running + - Stopped - + Stopped + - Failed - + Failed + - Something went wrong whilst stopping the service, did you allow the UAC prompt? + Something went wrong whilst stopping the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - The service is set to 'disabled', so it cannot be started. + The service is set to 'disabled', so it cannot be started. Please enable the service first and try again. - + - Something went wrong whilst starting the service, did you allow the UAC prompt? + Something went wrong whilst starting the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst disabling the service, did you allow the UAC prompt? + Something went wrong whilst disabling the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst enabling the service, did you allow the UAC prompt? + Something went wrong whilst enabling the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst reinstalling the service, did you allow the UAC prompt? + Something went wrong whilst reinstalling the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst disabling Start-on-Login, please check the logs for more information. - + Something went wrong whilst disabling Start-on-Login, please check the logs for more information. + - Something went wrong whilst disabling Start-on-Login, please check the logs for more information. - + Something went wrong whilst disabling Start-on-Login, please check the logs for more information. + - Enabled - + Enabled + - Disable Start-on-Login - + Disable Start-on-Login + - Disabled - + Disabled + - Enable Start-on-Login - + Enable Start-on-Login + - Start-on-Login has been activated! - + Start-on-Login has been activated! + - Do you want to enable Start-on-Login now? - + Do you want to enable Start-on-Login now? + - Start-on-Login is already activated, all set! - + Start-on-Login is already activated, all set! + - Activating Start-on-Login.. - + Activating Start-on-Login.. + - Something went wrong. You can try again, or skip to the next page and retry after HASS.Agent's restart. - + Something went wrong. You can try again, or skip to the next page and retry after HASS.Agent's restart. + - Enable Start-on-Login - + Enable Start-on-Login + - Please provide a valid API key. - + Please provide a valid API key. + - Please enter your Home Assistant's URI. - + Please enter your Home Assistant's URI. + - Unable to connect, the following error was returned: + Unable to connect, the following error was returned: {0} - + - Connection OK! + Connection OK! Home Assistant version: {0} - + - Testing.. - + Testing.. + - An error occurred whilst saving your commands, please check the logs for more information. - + An error occurred whilst saving your commands, please check the logs for more information. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - Connecting with satellite service, please wait.. - + Connecting with satellite service, please wait.. + - Connecting to the service has failed! - + Connecting to the service has failed! + - The service hasn't been found! You can install and manage it from the configuration panel. + The service hasn't been found! You can install and manage it from the configuration panel. When it's up and running, come back here to configure the commands and sensors. - + - Communicating with the service has failed! - + Communicating with the service has failed! + - Unable to communicate with the service. Check the logs for more info. + Unable to communicate with the service. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Unauthorized - + Unauthorized + - You are not authorized to contact the service. + You are not authorized to contact the service. If you have the correct auth ID, you can set it now and try again. - + - Fetching settings failed! - + Fetching settings failed! + - The service returned an error while requesting its settings. Check the logs for more info. + The service returned an error while requesting its settings. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Fetching MQTT settings failed! - + Fetching MQTT settings failed! + - The service returned an error while requesting its MQTT settings. Check the logs for more info. + The service returned an error while requesting its MQTT settings. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Fetching configured commands failed! - + Fetching configured commands failed! + - The service returned an error while requesting its configured commands. Check the logs for more info. + The service returned an error while requesting its configured commands. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Fetching configured sensors failed! - + Fetching configured sensors failed! + - The service returned an error while requesting its configured sensors. Check the logs for more info. + The service returned an error while requesting its configured sensors. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Storing an empty auth ID will allow all HASS.Agent to access the service. + Storing an empty auth ID will allow all HASS.Agent to access the service. Are you sure you want this? - + - An error occurred whilst saving, check the logs for more information. - + An error occurred whilst saving, check the logs for more information. + - Please provide a device name! - + Please provide a device name! + - Please select an executor first. (Tip: Double click to Browse) - + Please select an executor first. (Tip: Double click to Browse) + - The selected executor could not be found, please ensure the path provided is correct and try again. - + The selected executor could not be found, please ensure the path provided is correct and try again. + - Set an auth ID if you don't want every instance of HASS.Agent on this PC to connect with the satellite service. + Set an auth ID if you don't want every instance of HASS.Agent on this PC to connect with the satellite service. Only the instances that have the correct ID, can connect. Leave empty to allow all to connect. - + - This is the name with which the satellite service registers itself on Home Assistant. + This is the name with which the satellite service registers itself on Home Assistant. By default, it's your PC's name plus '-satellite'. - + - The amount of time the satellite service will wait before reporting a lost connection to the MQTT broker. - + The amount of time the satellite service will wait before reporting a lost connection to the MQTT broker. + - Error fetching status, please check logs for information. - + Error fetching status, please check logs for information. + - An error occurred whilst saving the configuration, please check the logs for more information. - + An error occurred whilst saving the configuration, please check the logs for more information. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - An error occurred whilst saving the sensors, please check the logs for more information. - + An error occurred whilst saving the sensors, please check the logs for more information. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - Not all steps completed succesfully. Please consult the logs for more information. - + Not all steps completed succesfully. Please consult the logs for more information. + - Not all steps completed succesfully. Please consult the logs for more information. - + Not all steps completed succesfully. Please consult the logs for more information. + - HASS.Agent is still active after {0} seconds. Please close all instances and restart manually. + HASS.Agent is still active after {0} seconds. Please close all instances and restart manually. Check the logs for more info, and optionally inform the developers. - + - Not all steps completed successfully, please check the logs for more information. - + Not all steps completed successfully, please check the logs for more information. + - Enable Satellite Service - + Enable Satellite Service + - Disable Satellite Service - + Disable Satellite Service + - Start Satellite Service - + Start Satellite Service + - Stop Satellite Service - + Stop Satellite Service + - Something went wrong while processing the desired service state. + Something went wrong while processing the desired service state. Please consult the logs for more information. - + - Topic copied to clipboard! - + Topic copied to clipboard! + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - An error occurred whilst saving commands, please check the logs for more information. - + An error occurred whilst saving commands, please check the logs for more information. + - New Command - + New Command + - Mod Command - + Mod Command + - Please select a command type! - + Please select a command type! + - Please select a valid command type! - + Please select a valid command type! + - Select a valid entity type first. - + Select a valid entity type first. + - Please provide a name! - + Please provide a name! + - A command with that name already exists, are you sure you want to continue? - + A command with that name already exists, are you sure you want to continue? + - If a command is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. + If a command is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. Are you sure you want to proceed? - + - If you don't enter a command or script, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. + If you don't enter a command or script, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. Are you sure you want this? - + - Please enter a key code! - + Please enter a key code! + - Checking keys failed: {0} - + Checking keys failed: {0} + - If a URL is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. + If a URL is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. Are you sure you want to proceed? - + - Command - + Command + - Command or Script - + Command or Script + - Keycode - + Keycode + - Keycodes - + Keycodes + - Launch in Incognito Mode - + Launch in Incognito Mode + - Browser: Default + Browser: Default Please configure a custom browser to enable incognito mode. - + - URL - + URL + - Browser: {0} - + Browser: {0} + - Executor: None + Executor: None Please configure an executor or your command will not run. - + - Executor: {0} - + Executor: {0} + - Low integrity means your command will be executed with restricted privileges. - + Low integrity means your command will be executed with restricted privileges. + - This means it will only be able to save and modify files in certain locations, - + This means it will only be able to save and modify files in certain locations, + - such as the '%USERPROFILE%\AppData\LocalLow' folder or - + such as the '%USERPROFILE%\AppData\LocalLow' folder or + - the 'HKEY_CURRENT_USER\Software\AppDataLow' registry key. - + the 'HKEY_CURRENT_USER\Software\AppDataLow' registry key. + - You should test your command to make sure it's not influenced by this! - + You should test your command to make sure it's not influenced by this! + - {0} only! - + {0} only! + - The MQTT manager hasn't been configured properly, or hasn't yet completed its startup. - + The MQTT manager hasn't been configured properly, or hasn't yet completed its startup. + - Unable to fetch your entities because of missing config, please enter the required values in the config screen. - + Unable to fetch your entities because of missing config, please enter the required values in the config screen. + - There was an error trying to fetch your entities! - + There was an error trying to fetch your entities! + - New Quick Action - + New Quick Action + - Mod Quick Action - + Mod Quick Action + - Unable to fetch your entities because of missing config, please enter the required values in the config screen. - + Unable to fetch your entities because of missing config, please enter the required values in the config screen. + - There was an error trying to fetch your entities. - + There was an error trying to fetch your entities. + - Please select an entity! - + Please select an entity! + - Unknown action, please select a valid one. - + Unknown action, please select a valid one. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - An error occurred whilst saving the sensors, please check the logs for more information. - + An error occurred whilst saving the sensors, please check the logs for more information. + - New Sensor - + New Sensor + - Mod Sensor - + Mod Sensor + - Window Name - + Window Name + - WMI Query - + WMI Query + - WMI Scope (optional) - + WMI Scope (optional) + - Category - + Category + - Counter - + Counter + - Instance (optional) - + Instance (optional) + - Process - + Process + - Service - + Service + - Please select a sensor type! - + Please select a sensor type! + - Please select a valid sensor type! - + Please select a valid sensor type! + - Please provide a name! - + Please provide a name! + - A single-value sensor already exists with that name, are you sure you want to proceed? - + A single-value sensor already exists with that name, are you sure you want to proceed? + - A multi-value sensor already exists with that name, are you sure you want to proceed? - + A multi-value sensor already exists with that name, are you sure you want to proceed? + - Please provide an interval between 1 and 43200 (12 hours)! - + Please provide an interval between 1 and 43200 (12 hours)! + - Please enter a window name! - + Please enter a window name! + - Please enter a query! - + Please enter a query! + - Please enter a category and instance! - + Please enter a category and instance! + - Please enter the name of a process! - + Please enter the name of a process! + - Please enter the name of a service! - + Please enter the name of a service! + - {0} only! - + {0} only! + - You've changed your device's name. + You've changed your device's name. All your sensors and commands will now be unpublished, and HASS.Agent will restart afterwards to republish them. Don't worry, they'll keep their current names, so your automations or scripts will keep working. Note: the name will get 'sanitized', which means everything except letters, digits and whitespace get replaced by an underscore. This is required by HA. - + - You've changed the local API's port. This new port needs to be reserved. + You've changed the local API's port. This new port needs to be reserved. You'll get an UAC request to do so, please approve. - + - Something went wrong! + Something went wrong! Please manually execute the required command. It has been copied onto your clipboard, you just need to paste it into an elevated command prompt. Remember to change your firewall rule's port as well. - + - The port has succesfully been reserved! + The port has succesfully been reserved! HASS.Agent will now restart to activate the new configuration. - + - Something went wrong while preparing to restart. + Something went wrong while preparing to restart. Please restart manually. - + - Your configuration has been saved. Most changes require HASS.Agent to restart before they take effect. + Your configuration has been saved. Most changes require HASS.Agent to restart before they take effect. Do you want to restart now? - + - Something went wrong while loading your settings. + Something went wrong while loading your settings. Check appsettings.json in the 'config' subfolder, or just delete it to start fresh. - + - There was an error launching HASS.Agent. + There was an error launching HASS.Agent. Please check the logs and make a bug report on GitHub. - + - &Sensors - + &Sensors + - &Commands - + &Commands + - Checking.. - + Checking.. + - You're running the latest version: {0}{1} - + You're running the latest version: {0}{1} + - There's a new BETA release available: - + There's a new BETA release available: + - HASS.Agent BETA Update - + HASS.Agent BETA Update + - Do you want to &download and launch the installer? - + Do you want to &download and launch the installer? + - Do you want to &navigate to the release page? - + Do you want to &navigate to the release page? + - Install Update - + Install Update + - Install Beta Release - + Install Beta Release + - Open Release Page - + Open Release Page + - Open Beta Release Page - + Open Beta Release Page + - Processing request, please wait.. - + Processing request, please wait.. + - Processing.. - + Processing.. + - HASS.Agent Onboarding: Start [{0}/{1}] - + HASS.Agent Onboarding: Start [{0}/{1}] + - HASS.Agent Onboarding: Startup [{0}/{1}] - + HASS.Agent Onboarding: Startup [{0}/{1}] + - HASS.Agent Onboarding: Notifications [{0}/{1}] - + HASS.Agent Onboarding: Notifications [{0}/{1}] + - HASS.Agent Onboarding: Integration [{0}/{1}] - + HASS.Agent Onboarding: Integration [{0}/{1}] + - HASS.Agent Onboarding: API [{0}/{1}] - + HASS.Agent Onboarding: API [{0}/{1}] + - HASS.Agent Onboarding: MQTT [{0}/{1}] - + HASS.Agent Onboarding: MQTT [{0}/{1}] + - HASS.Agent Onboarding: HotKey [{0}/{1}] - + HASS.Agent Onboarding: HotKey [{0}/{1}] + - HASS.Agent Onboarding: Updates [{0}/{1}] - + HASS.Agent Onboarding: Updates [{0}/{1}] + - HASS.Agent Onboarding: Completed [{0}/{1}] - + HASS.Agent Onboarding: Completed [{0}/{1}] + - Are you sure you want to abort the onboarding process? + Are you sure you want to abort the onboarding process? Your progress will not be saved, and it will not be shown again on next launch. - + - Error fetching info, please check logs for more information. - + Error fetching info, please check logs for more information. + - Unable to prepare downloading the update, check the logs for more info. + Unable to prepare downloading the update, check the logs for more info. The release page will now open instead. - + - Unable to download the update, check the logs for more info. + Unable to download the update, check the logs for more info. The release page will now open instead. - + - The downloaded file FAILED the certificate check. + The downloaded file FAILED the certificate check. This could be a technical error, but also a tampered file! Please check the logs, and post a ticket with the findings. - + - Unable to launch the installer (did you approve the UAC prompt?), check the logs for more info. + Unable to launch the installer (did you approve the UAC prompt?), check the logs for more info. The release page will now open instead. - + - HASS API: Connection setup failed. - + HASS API: Connection setup failed. + - HASS API: Initial connection failed. - + HASS API: Initial connection failed. + - HASS API: Connection failed. - + HASS API: Connection failed. + - Client certificate file not found. - + Client certificate file not found. + - Unable to connect, check URI. - + Unable to connect, check URI. + - Unable to fetch configuration, please check API key. - + Unable to fetch configuration, please check API key. + - Unable to connect, please check URI and configuration. - + Unable to connect, please check URI and configuration. + - quick action: action failed, check the logs for info - + quick action: action failed, check the logs for info + - quick action: action failed, entity not found - + quick action: action failed, entity not found + - MQTT: Error while connecting - + MQTT: Error while connecting + - MQTT: Failed to connect - + MQTT: Failed to connect + - MQTT: Disconnected - + MQTT: Disconnected + - Error trying to bind the API to port {0}. + Error trying to bind the API to port {0}. Make sure no other instance of HASS.Agent is running and the port is available and registered. - + - Provides the title of the current active window. - + Provides the title of the current active window. + - Provides information various aspects of your device's audio: + Provides information various aspects of your device's audio: Current peak volume level (can be used as a simple 'is something playing' value). Default audio device: name, state and volume. Summary of your audio sessions: application name, muted state, volume and current peak volume. - + - Provides a sensor with the current charging status, estimated amount of minutes on a full charge, remaining charge as a percentage, remaining charge in minutes and the powerline status. - + Provides a sensor with the current charging status, estimated amount of minutes on a full charge, remaining charge as a percentage, remaining charge in minutes and the powerline status. + - Provides the current load of the first CPU as a percentage. - + Provides the current load of the first CPU as a percentage. + - Provides the current clockspeed of the first CPU. - + Provides the current clockspeed of the first CPU. + - Provides the current volume level as a percentage. + Provides the current volume level as a percentage. Currently takes the volume of your default device. - + - Provides a sensor with the amount of displays, name of the primary display, and per display its name, resolution and bits per pixel. - + Provides a sensor with the amount of displays, name of the primary display, and per display its name, resolution and bits per pixel. + - Dummy sensor for testing purposes, sends a random integer value between 0 and 100. - + Dummy sensor for testing purposes, sends a random integer value between 0 and 100. + - Provides the current load of the first GPU as a percentage. - + Provides the current load of the first GPU as a percentage. + - Provides the current temperature of the first GPU. - + Provides the current temperature of the first GPU. + - Provides a datetime value containing the last moment the user provided any input. - + Provides a datetime value containing the last moment the user provided any input. + - Provides a datetime value containing the last moment the system (re)booted. + Provides a datetime value containing the last moment the system (re)booted. Important: Windows' FastBoot option can throw this value off, because that's a form of hibernation. You can disable it through Power Options -> 'Choose what the power buttons do' -> uncheck 'Turn on fast start-up'. It doesn't make much difference for modern machines with SSDs, but disabling makes sure you get a clean state after rebooting. - + - Provides the last system state change: + Provides the last system state change: ApplicationStarted, Logoff, SystemShutdown, Resume, Suspend, ConsoleConnect, ConsoleDisconnect, RemoteConnect, RemoteDisconnect, SessionLock, SessionLogoff, SessionLogon, SessionRemoteControl and SessionUnlock. - + - Returns the name of the currently logged user. + Returns the name of the currently logged user. This will only show active users, and falls back to 'Empty' if there are none. If there are multiple, the first will be used. - + - Returns a json-formatted list of currently logged users. + Returns a json-formatted list of currently logged users. This will also contain users that aren't active. If you only want the current active user, use the LoggedUser sensor instead. - + - Provides the amount of used memory as a percentage. - + Provides the amount of used memory as a percentage. + - Provides a bool value based on whether the microphone is currently being used. + Provides a bool value based on whether the microphone is currently being used. Note: if used in the satellite service, it won't detect userspace applications. - + - Provides an ON/OFF value based on whether the window is currently open (doesn't have to be active). - + Provides an ON/OFF value based on whether the window is currently open (doesn't have to be active). + - Provides card info, configuration, transfer- & package statistics and addresses (ip, mac, dhcp, dns) of the selected network card(s). + Provides card info, configuration, transfer- & package statistics and addresses (ip, mac, dhcp, dns) of the selected network card(s). This is a multi-value sensor. - + - Provides the values of a performance counter. + Provides the values of a performance counter. For example, the built-in CPU load sensor uses these values: @@ -1198,415 +1218,415 @@ Counter: % Processor Time Instance: _Total You can explore the counters through Windows' 'perfmon.exe' tool. - + - Provides the number of active instances of the process. + Provides the number of active instances of the process. Note: don't add the extension (eg. notepad.exe becomes notepad). - + - Returns the state of the provided service: NotFound, Stopped, StartPending, StopPending, Running, ContinuePending, PausePending or Paused. + Returns the state of the provided service: NotFound, Stopped, StartPending, StopPending, Running, ContinuePending, PausePending or Paused. Make sure to provide the 'Service name', not the 'Display name'. - + - Provides the current session state: + Provides the current session state: Locked, Unlocked or Unknown. Use a LastSystemStateChangeSensor to monitor session state changes. - + - Provides the labels, total size (MB), available space (MB), used space (MB) and file system of all present non-removable disks. - + Provides the labels, total size (MB), available space (MB), used space (MB) and file system of all present non-removable disks. + - Provides the current user state: + Provides the current user state: NotPresent, Busy, RunningDirect3dFullScreen, PresentationMode, AcceptsNotifications, QuietTime or RunningWindowsStoreApp. Can for instance be used to determine whether to send notifications or TTS messages. - + - Provides a bool value based on whether the webcam is currently being used. + Provides a bool value based on whether the webcam is currently being used. Note: if used in the satellite service, it won't detect userspace applications. - + - Provides a sensor with the amount of pending driver updates, a sensor with the amount of pending software updates, a sensor containing all pending driver updates information (title, kb article id's, hidden, type and categories) and a sensor containing the same for pending software updates. + Provides a sensor with the amount of pending driver updates, a sensor with the amount of pending software updates, a sensor containing all pending driver updates information (title, kb article id's, hidden, type and categories) and a sensor containing the same for pending software updates. This is a costly request, so the recommended interval is 15 minutes (900 seconds). But it's capped at 10 minutes, if you provide a lower value, you'll get the last known list. - + - Provides the result of the WMI query. - + Provides the result of the WMI query. + - Error loading settings: + Error loading settings: {0} - + - Error storing initial settings: + Error storing initial settings: {0} - + - Error storing settings: + Error storing settings: {0} - + - Error loading commands: + Error loading commands: {0} - + - Error storing commands: + Error storing commands: {0} - + - Error loading quick actions: + Error loading quick actions: {0} - + - Error storing quick actions: + Error storing quick actions: {0} - + - Error loading sensors: + Error loading sensors: {0} - + - Error storing sensors: + Error storing sensors: {0} - + - Wiki - + Wiki + - Busy, please wait.. - + Busy, please wait.. + - Finish - + Finish + - Connected - + Connected + - Connecting.. - + Connecting.. + - Configuration missing - + Configuration missing + - Disconnected - + Disconnected + - Error - + Error + - Custom - + Custom + - CustomExecutor - + CustomExecutor + - Hibernate - + Hibernate + - Key - + Key + - LaunchUrl - + LaunchUrl + - Lock - + Lock + - LogOff - + LogOff + - MediaMute - + MediaMute + - MediaNext - + MediaNext + - MediaPlayPause - + MediaPlayPause + - MediaPrevious - + MediaPrevious + - MediaVolumeDown - + MediaVolumeDown + - MediaVolumeUp - + MediaVolumeUp + - MultipleKeys - + MultipleKeys + - Powershell - + Powershell + - PublishAllSensors - + PublishAllSensors + - Restart - + Restart + - Shutdown - + Shutdown + - Sleep - + Sleep + - Button - + Button + - Light - + Light + - Lock - + Lock + - Siren - + Siren + - Switch - + Switch + - Locked - + Locked + - Unknown - + Unknown + - Unlocked - + Unlocked + - ActiveWindow - + ActiveWindow + - Audio - + Audio + - Battery - + Battery + - CpuLoad - + CpuLoad + - CurrentClockSpeed - + CurrentClockSpeed + - CurrentVolume - + CurrentVolume + - Display - + Display + - Dummy - + Dummy + - GpuLoad - + GpuLoad + - GpuTemperature - + GpuTemperature + - LastActive - + LastActive + - LastBoot - + LastBoot + - LastSystemStateChange - + LastSystemStateChange + - LoggedUser - + LoggedUser + - LoggedUsers - + LoggedUsers + - MemoryUsage - + MemoryUsage + - MicrophoneActive - + MicrophoneActive + - NamedWindow - + NamedWindow + - Network - + Network + - PerformanceCounter - + PerformanceCounter + - ProcessActive - + ProcessActive + - ServiceState - + ServiceState + - SessionState - + SessionState + - Storage - + Storage + - UserNotification - + UserNotification + - WebcamActive - + WebcamActive + - WindowsUpdates - + WindowsUpdates + - WmiQuery - + WmiQuery + - ApplicationStarted - + ApplicationStarted + - Logoff - + Logoff + - SystemShutdown - + SystemShutdown + - Resume - + Resume + - Suspend - + Suspend + - ConsoleConnect - + ConsoleConnect + - ConsoleDisconnect - + ConsoleDisconnect + - RemoteConnect - + RemoteConnect + - RemoteDisconnect - + RemoteDisconnect + - SessionLock - + SessionLock + - SessionLogoff - + SessionLogoff + - SessionLogon - + SessionLogon + - SessionRemoteControl - + SessionRemoteControl + - SessionUnlock - + SessionUnlock + - NotPresent - + NotPresent + - Busy - + Busy + - RunningDirect3dFullScreen - + RunningDirect3dFullScreen + - PresentationMode - + PresentationMode + - AcceptsNotifications - + AcceptsNotifications + - QuietTime - + QuietTime + - RunningWindowsStoreApp - + RunningWindowsStoreApp + - On - + On + - Off - + Off + - Open - + Open + - Close - + Close + - Play - + Play + - Pause - + Pause + - Stop - + Stop + - Toggle - + Toggle + - Automation - + Automation + - Climate - + Climate + - Cover - + Cover + - InputBoolean - + InputBoolean + - Light - + Light + - MediaPlayer - + MediaPlayer + - Scene - + Scene + - Script - + Script + - Switch - + Switch + - Connecting.. - + Connecting.. + - Failed - + Failed + - Loading.. - + Loading.. + - Running - + Running + - Stopped - + Stopped + - Disabled - + Disabled + - It looks like your scope is malformed, it should probably start like this: + It looks like your scope is malformed, it should probably start like this: \\.\ROOT\ @@ -1617,1588 +1637,1588 @@ The scope you entered: Tip: make sure you haven't switched the scope and query fields around. Do you still want to use the current values? - + - Enter a WMI query first. - + Enter a WMI query first. + - Query succesfully executed, result value: + Query succesfully executed, result value: {0} - + - The query failed to execute: + The query failed to execute: {0} Do you want to open the logs folder? - + - Enter a category and counter first. - + Enter a category and counter first. + - Test succesfully executed, result value: + Test succesfully executed, result value: {0} - + - The test failed to execute: + The test failed to execute: {0} Do you want to open the logs folder? - + - Test WMI Query - + Test WMI Query + - Test Performance Counter - + Test Performance Counter + - GeoLocation - + GeoLocation + - All - + All + - Network Card - + Network Card + - Last Known Value - + Last Known Value + - SendWindowToFront - + SendWindowToFront + - Cleaning.. - + Cleaning.. + - The audio cache has been cleared! - + The audio cache has been cleared! + - Cleaning.. - + Cleaning.. + - The WebView cache has been cleared! - + The WebView cache has been cleared! + - WebView - + WebView + - Looks for the specified process, and tries to send its main window to the front. + Looks for the specified process, and tries to send its main window to the front. If the application is minimized, it'll get restored. Example: if you want to send VLC to the foreground, use 'vlc'. - + - Shows a window with the provided URL. + Shows a window with the provided URL. This differs from the 'LaunchUrl' command in that it doesn't load a full-fledged browser, just the provided URL in its own window. You can use this to for instance quickly show Home Assistant's dashboard. By default, it stores cookies indefinitely so you only have to log in once. - + - Unable to load the stored command settings, resetting to default. - + Unable to load the stored command settings, resetting to default. + - If you do not configure the command, you may only use this entity with an 'action' value via Home Assistant and it will appear using the default settings, running it as-is will have no action. + If you do not configure the command, you may only use this entity with an 'action' value via Home Assistant and it will appear using the default settings, running it as-is will have no action. Are you sure you want to do this? - + - Please select an domain! - + Please select an domain! + - HASS.Agent Commands - + HASS.Agent Commands + - It looks like you're using an alternative scaling. This may result in some parts of HASS.Agent not looking as intended. + It looks like you're using an alternative scaling. This may result in some parts of HASS.Agent not looking as intended. Please report any unusable aspects on GitHub. Thanks! Note: this message only shows once. - + - Your device name contained some characters that are not allowed by HA (only letters, digits and whitespace). + Your device name contained some characters that are not allowed by HA (only letters, digits and whitespace). The final name is: {0} Do you want to use that version? - + - No keys found - + No keys found + - brackets missing, start and close all keys with [ ] - + brackets missing, start and close all keys with [ ] + - Error while parsing keys, please check the logs for more information. - + Error while parsing keys, please check the logs for more information. + - Your input language '{0}' is known to collide with the default CTRL-ALT-Q hotkey. Please set your own. - + Your input language '{0}' is known to collide with the default CTRL-ALT-Q hotkey. Please set your own. + - Your input language '{0}' is unknown, and might collide with the default CTRL-ALT-Q hotkey. Please check to be sure. If it does, consider opening a ticket on GitHub so it can be added to the list. - + Your input language '{0}' is unknown, and might collide with the default CTRL-ALT-Q hotkey. Please check to be sure. If it does, consider opening a ticket on GitHub so it can be added to the list. + - The number of open brackets ('[') does not correspond to the number of closed brackets. (']')! ({0} to {1}) - + The number of open brackets ('[') does not correspond to the number of closed brackets. (']')! ({0} to {1}) + - &Hide - + &Hide + - Browser Name - + Browser Name + - Browser Binary - + Browser Binary + - Additional Launch Arguments - + Additional Launch Arguments + - Custom Executor Binary - + Custom Executor Binary + - Tip: Double-click to Browse - + Tip: Double-click to Browse + - &Test - + &Test + - Seconds - + Seconds + - Disconnected Grace &Period - + Disconnected Grace &Period + - This page contains general configuration settings, for more settings you can browse the tabs on the left. - + This page contains general configuration settings, for more settings you can browse the tabs on the left. + - Device &Name - + Device &Name + - Interface &Language - + Interface &Language + - Tip: Double-click this field to browse - + Tip: Double-click this field to browse + - Client &Certificate - + Client &Certificate + - Use &automatic client certificate selection - + Use &automatic client certificate selection + - &Test Connection - + &Test Connection + - &API Token - + &API Token + - Server &URI - + Server &URI + - &Clear - + &Clear + - An easy way to pull up your quick actions is to use a global hotkey. + An easy way to pull up your quick actions is to use a global hotkey. This way, whatever you're doing on your machine, you can always interact with Home Assistant. - + - &Enable Quick Actions Hotkey - + &Enable Quick Actions Hotkey + - &Hotkey Combination - + &Hotkey Combination + - &Port - + &Port + - Execute Port &Reservation - + Execute Port &Reservation + - &Enable Local API - + &Enable Local API + - To be able to listen to the requests, HASS.Agent needs to have its port reserved and opened in your firewall. You can use this button to have it done for you. - + To be able to listen to the requests, HASS.Agent needs to have its port reserved and opened in your firewall. You can use this button to have it done for you. + - Image Cache Location - + Image Cache Location + - days - + days + - Some items like images shown in notifications have to be temporarily stored locally. You can + Some items like images shown in notifications have to be temporarily stored locally. You can configure the amount of days they should be kept before HASS.Agent deletes them. Enter '0' to keep them permanently. - + - Keep images for - + Keep images for + - Keep audio for - + Keep audio for + - Clear Audio Cache - + Clear Audio Cache + - Audio Cache Location - + Audio Cache Location + - Clear WebView Cache - + Clear WebView Cache + - WebView Cache Location - + WebView Cache Location + - Clear cache every - + Clear cache every + - Extended logging provides more verbose and in-depth logging, in case the default logging isn't + Extended logging provides more verbose and in-depth logging, in case the default logging isn't sufficient. Please note that enabling this can cause the logfiles to grow large, and should only be used when you suspect something's wrong with HASS.Agent itself or when asked by the developers. - + - &Enable Extended Logging - + &Enable Extended Logging + - &Open Logs Folder - + &Open Logs Folder + - Media Player &Documentation - + Media Player &Documentation + - &Enable Media Player Functionality - + &Enable Media Player Functionality + - Tip: Double-click these fields to browse - + Tip: Double-click these fields to browse + - Client Certificate - + Client Certificate + - Root Certificate - + Root Certificate + - Use &Retain Flag - + Use &Retain Flag + - &Allow Untrusted Certificates - + &Allow Untrusted Certificates + - &Clear Configuration - + &Clear Configuration + - (leave default if unsure) - + (leave default if unsure) + - Discovery Prefix - + Discovery Prefix + - &TLS - + &TLS + - Password - + Password + - Username - + Username + - Port - + Port + - Broker IP Address or Hostname - + Broker IP Address or Hostname + - (leave empty to auto generate) - + (leave empty to auto generate) + - Client ID - + Client ID + - Notifications &Documentation - + Notifications &Documentation + - &Accept Notifications - + &Accept Notifications + - &Ignore certificate errors for images - + &Ignore certificate errors for images + - The local API is disabled however the media player requires it in order to function. - + The local API is disabled however the media player requires it in order to function. + - Service Status: - + Service Status: + - S&tart Service - + S&tart Service + - &Disable Service - + &Disable Service + - &Stop Service - + &Stop Service + - &Enable Service - + &Enable Service + - &Reinstall Service - + &Reinstall Service + - If you do not configure the service, it won't do anything. However, you can still decide to disable it as well. + If you do not configure the service, it won't do anything. However, you can still decide to disable it as well. The installer will leave the disabled service alone(if you remove the service, the installer will reinstall it). - + - You can try reinstalling the service if it's not working correctly. + You can try reinstalling the service if it's not working correctly. Your configuration and entities won't be removed. - + - Open Service &Logs Folder - + Open Service &Logs Folder + - If the service still fails after reinstalling, please open a ticket and send the content of the latest log. - + If the service still fails after reinstalling, please open a ticket and send the content of the latest log. + - HASS.Agent can start when you login by creating an entry in your user profile's registry. + HASS.Agent can start when you login by creating an entry in your user profile's registry. Since HASS.Agent is user based, if you want to launch for another user, just install and config HASS.Agent there. - + - &Enable Start-on-Login - + &Enable Start-on-Login + - Start-on-Login Status: - + Start-on-Login Status: + - Control the behaviour of the tray icon when it is right-clicked. - + Control the behaviour of the tray icon when it is right-clicked. + - Show &Default Menu - + Show &Default Menu + - Show &WebView - + Show &WebView + - &WebView URL (For instance, your Home Assistant Dashboard URL) - + &WebView URL (For instance, your Home Assistant Dashboard URL) + - Size (px) - + Size (px) + - Show &Preview - + Show &Preview + - &Keep page loaded in the background - + &Keep page loaded in the background + - (This uses extra resources, but reduces loading time.) - + (This uses extra resources, but reduces loading time.) + - Notify me of &beta releases - + Notify me of &beta releases + - When a new update is available, HASS.Agent can download the installer and launch it for you. + When a new update is available, HASS.Agent can download the installer and launch it for you. The certificate of the downloaded file will get checked before running,you will still get to review the release notes and manually approve the update. - + - Automatically &download future updates - + Automatically &download future updates + - HASS.Agent checks for updates in the background if enabled. + HASS.Agent checks for updates in the background if enabled. You will be sent a push notification if a new update is discovered, letting you know a new version is ready to be installed. - + - Notify me when a new &release is available - + Notify me when a new &release is available + - Welcome to the HASS.Agent! It looks like this is the first time you are launching the agent. + Welcome to the HASS.Agent! It looks like this is the first time you are launching the agent. To assist you with a first time setup, proceed with the configuration steps below or alternatively, click 'Close'. - + - The device name is used to identify your machine on Home Assistant, it is also used as a suggested prefix for your commands and sensors. - + The device name is used to identify your machine on Home Assistant, it is also used as a suggested prefix for your commands and sensors. + - Device &Name - + Device &Name + - Interface &Language - + Interface &Language + - Yes, &start HASS.Agent on System Login - + Yes, &start HASS.Agent on System Login + - HASS.Agent can start with your system, this allows for any sensors and data transmission between your device and Home Assistant to begin as soon as you login. + HASS.Agent can start with your system, this allows for any sensors and data transmission between your device and Home Assistant to begin as soon as you login. This setting can be changed any time later in the HASS.Agent configuration window. - + - Fetching current state, please wait.. - + Fetching current state, please wait.. + - Note: 5115 is the default port, only change it if you changed it in Home Assistant. - + Note: 5115 is the default port, only change it if you changed it in Home Assistant. + - Yes, &enable the local API on port - + Yes, &enable the local API on port + - HASS.Agent has its own internal API, so Home Assistant can send requests (like notifications or text-to-speech). + HASS.Agent has its own internal API, so Home Assistant can send requests (like notifications or text-to-speech). Do you want to enable it? - + - Enable &Notifications - + Enable &Notifications + - Enable &Media Player and text-to-speech (TTS) - + Enable &Media Player and text-to-speech (TTS) + - You can choose what modules you want to enable. They require HA integrations, but don't worry, the next page will give you more info on how to set them up. - + You can choose what modules you want to enable. They require HA integrations, but don't worry, the next page will give you more info on how to set them up. + - To use notifications, you need to install and configure the HASS.Agent-notifier integration in + To use notifications, you need to install and configure the HASS.Agent-notifier integration in Home Assistant. This is very easy using HACS, but you can also install manually. Visit the link below for more information. - + - HASS.Agent-MediaPlayer GitHub Page - + HASS.Agent-MediaPlayer GitHub Page + - The same goes for the media player, this integration allows you to control your device as a media_player entity, see what's playing and send text-to-speech. - + The same goes for the media player, this integration allows you to control your device as a media_player entity, see what's playing and send text-to-speech. + - API &Token - + API &Token + - Server &URI (should be ok like this) - + Server &URI (should be ok like this) + - Test &Connection - + Test &Connection + - Tip: Specialized settings can be found in the Configuration Window. - + Tip: Specialized settings can be found in the Configuration Window. + - &TLS - + &TLS + - Discovery Prefix - + Discovery Prefix + - (leave default if not sure) - + (leave default if not sure) + - Tip: Specialized settings can be found in the Configuration Window. - + Tip: Specialized settings can be found in the Configuration Window. + - &Hotkey Combination - + &Hotkey Combination + - An easy way to pull up your quick actions is to use a global hotkey. + An easy way to pull up your quick actions is to use a global hotkey. This way, whatever you're doing on your machine, you can always interact with Home Assistant. - + - &Clear - + &Clear + - Yes, notify me on new &updates - + Yes, notify me on new &updates + - Yes, &download and launch the installer for me - + Yes, &download and launch the installer for me + - HASS.Agent GitHub page - + HASS.Agent GitHub page + - Low Integrity - + Low Integrity + - &Remove - + &Remove + - &Modify - + &Modify + - &Add New - + &Add New + - &Send && Activate Commands - + &Send && Activate Commands + - commands stored! - + commands stored! + - &Apply - + &Apply + - Auth &ID - + Auth &ID + - Authenticate - + Authenticate + - Connect with service - + Connect with service + - Connecting satellite service, please wait.. - + Connecting satellite service, please wait.. + - Fetch Configuration - + Fetch Configuration + - (leave empty to auto generate) - + (leave empty to auto generate) + - Client ID - + Client ID + - Tip: Double-click these fields to browse - + Tip: Double-click these fields to browse + - Client Certificate - + Client Certificate + - Root Certificate - + Root Certificate + - Use &Retain Flag - + Use &Retain Flag + - &Allow Untrusted Certificates - + &Allow Untrusted Certificates + - &Clear Configuration - + &Clear Configuration + - (leave default if not sure) - + (leave default if not sure) + - Commands and sensors are sent through MQTT. Please provide credentials for your server. If you're using the HA addon, + Commands and sensors are sent through MQTT. Please provide credentials for your server. If you're using the HA addon, you can probably use the preset address. - + - Discovery Prefix - + Discovery Prefix + - &TLS - + &TLS + - Password - + Password + - Username - + Username + - Port - + Port + - Broker IP Address or Hostname - + Broker IP Address or Hostname + - &Send && Activate Configuration - + &Send && Activate Configuration + - Copy from &HASS.Agent - + Copy from &HASS.Agent + - Configuration stored! - + Configuration stored! + - Status - + Status + - Querying.. - + Querying.. + - Refresh - + Refresh + - &Remove - + &Remove + - &Add New - + &Add New + - &Modify - + &Modify + - &Send && Activate Sensors - + &Send && Activate Sensors + - Sensors stored! - + Sensors stored! + - Please wait a bit while the task is performed .. - + Please wait a bit while the task is performed .. + - Create API Port Binding - + Create API Port Binding + - Set Firewall Rule - + Set Firewall Rule + - Please wait a bit while some post-update tasks are performed .. - + Please wait a bit while some post-update tasks are performed .. + - Configuring Satellite Service - + Configuring Satellite Service + - Create API Port Binding - + Create API Port Binding + - Please wait while HASS.Agent restarts.. - + Please wait while HASS.Agent restarts.. + - Waiting for previous instance to close.. - + Waiting for previous instance to close.. + - Relaunch HASS.Agent - + Relaunch HASS.Agent + - Please wait while the satellite service is re-installed.. - + Please wait while the satellite service is re-installed.. + - Remove Satellite Service - + Remove Satellite Service + - Install Satellite Service - + Install Satellite Service + - HASS.Agent Reinstall Satellite Service - + HASS.Agent Reinstall Satellite Service + - Please wait while the satellite service is configured.. - + Please wait while the satellite service is configured.. + - Enable Satellite Service - + Enable Satellite Service + - HASS.Agent Configure Satellite Service - + HASS.Agent Configure Satellite Service + - &Close - + &Close + - This is the MQTT topic on which you can publish action commands: - + This is the MQTT topic on which you can publish action commands: + - Copy &to Clipboard - + Copy &to Clipboard + - help and examples - + help and examples + - MQTT Action Topic - + MQTT Action Topic + - &Save - + &Save + - Drag and resize this window to set the size and location of your webview command. - + Drag and resize this window to set the size and location of your webview command. + - &URL - + &URL + - Size - + Size + - Location - + Location + - Show the window's &title bar - + Show the window's &title bar + - &Always show centered in screen - + &Always show centered in screen + - Set window as 'Always on &Top' - + Set window as 'Always on &Top' + - Tip: Press ESCAPE to close a WebView. - + Tip: Press ESCAPE to close a WebView. + - WebView Configuration - + WebView Configuration + - &Remove - + &Remove + - &Modify - + &Modify + - &Add New - + &Add New + - &Store and Activate Commands - + &Store and Activate Commands + - Low Integrity - + Low Integrity + - Action - + Action + - Commands Config - + Commands Config + - &Store Command - + &Store Command + - &Configuration - + &Configuration + - &Name - + &Name + - Description - + Description + - &Run as 'Low Integrity' - + &Run as 'Low Integrity' + - What's this? - + What's this? + - Service - + Service + - agent - + agent + - Configure Command &Parameters - + Configure Command &Parameters + - Command - + Command + - Retrieving entities, please wait.. - + Retrieving entities, please wait.. + - Quick Actions - + Quick Actions + - &Store Quick Actions - + &Store Quick Actions + - &Add New - + &Add New + - &Modify - + &Modify + - &Remove - + &Remove + - &Preview - + &Preview + - Hotkey Enabled - + Hotkey Enabled + - Quick Actions Configuration - + Quick Actions Configuration + - &Store Quick Action - + &Store Quick Action + - &Entity - + &Entity + - Desired &Action - + Desired &Action + - &Description - + &Description + - Retrieving entities, please wait.. - + Retrieving entities, please wait.. + - &hotkey combination - + &hotkey combination + - (optional, will be used instead of entity name) - + (optional, will be used instead of entity name) + - Quick Action - + Quick Action + - &Remove - + &Remove + - &Modify - + &Modify + - &Add New - + &Add New + - &Store && Activate Sensors - + &Store && Activate Sensors + - Refresh - + Refresh + - &Store Sensor - + &Store Sensor + - Selected Type - + Selected Type + - &Name - + &Name + - &Update every - + &Update every + - seconds - + seconds + - Description - + Description + - Agent - + Agent + - Service - + Service + - HASS.Agent only! - + HASS.Agent only! + - &Test - + &Test + - Sensor - + Sensor + - Satellite Service Configuration - + Satellite Service Configuration + - &Close - + &Close + - A Windows-based client for the Home Assistant platform. - + A Windows-based client for the Home Assistant platform. + - This application is open source and completely free, please check the project pages of + This application is open source and completely free, please check the project pages of the used components for their individual licenses: - + - A big 'thank you' to the developers of these projects, who were kind enough to share + A big 'thank you' to the developers of these projects, who were kind enough to share their hard work with the rest of us mere mortals. - + - And of course; thanks to Paulus Shoutsen and the entire team of developers that + And of course; thanks to Paulus Shoutsen and the entire team of developers that created and maintain Home Assistant :-) - + - Created with love by - + Created with love by + - or - + or + - About - + About + - Local API - + Local API + - Media Player - + Media Player + - Tray Icon - + Tray Icon + - &About - + &About + - &Help && Contact - + &Help && Contact + - &Save Configuration - + &Save Configuration + - Close &Without Saving - + Close &Without Saving + - Configuration - + Configuration + - What would you like to do? - + What would you like to do? + - &Restart - + &Restart + - &Hide - + &Hide + - &Exit - + &Exit + - Exit Dialog - + Exit Dialog + - &Close - + &Close + - If you are having trouble with HASS.Agent and require support + If you are having trouble with HASS.Agent and require support with any sensors, commands, or for general support and feedback, there are few ways you can reach us: - + - About - + About + - Home Assistant Forum - + Home Assistant Forum + - GitHub Issues - + GitHub Issues + - Bit of everything, with the addition that other + Bit of everything, with the addition that other HA users can help you out too! - + - Report bugs, post feature requests, see latest changes, etc. - + Report bugs, post feature requests, see latest changes, etc. + - Get help with setting up and using HASS.Agent, + Get help with setting up and using HASS.Agent, report bugs or get involved in general chit-chat! - + - Documentation and Usage Examples - + Documentation and Usage Examples + - Documentation - + Documentation + - Help - + Help + - Manage Satellite Service - + Manage Satellite Service + - Controls - + Controls + - S&atellite Service - + S&atellite Service + - C&onfiguration - + C&onfiguration + - &Quick Actions - + &Quick Actions + - System Status - + System Status + - Satellite Service: - + Satellite Service: + - MQTT: - + MQTT: + - Commands: - + Commands: + - Sensors: - + Sensors: + - Quick Actions: - + Quick Actions: + - Home Assistant API: - + Home Assistant API: + - Local API: - + Local API: + - Check for &Updates - + Check for &Updates + - &Next - + &Next + - &Close - + &Close + - &Previous - + &Previous + - HASS.Agent Onboarding - + HASS.Agent Onboarding + - There's a new release available: - + There's a new release available: + - Release notes - + Release notes + - &Ignore Update - + &Ignore Update + - Release Page - + Release Page + - HASS.Agent Update - + HASS.Agent Update + - WebView - + WebView + - By default HASS.Agent will launch URLs using your default browser. You can also configure + By default HASS.Agent will launch URLs using your default browser. You can also configure a specific browser to be used instead along with launch arguments to run in private mode. - + - Make sure you follow these steps: + Make sure you follow these steps: - Install the HASS.Agent-Notifier and / or HASS.Agent-MediaPlayer integration - Restart Home Assistant -Configure a notifier and / or media_player entity -Restart Home Assistant - + - To learn which entities you have configured and to send quick actions, HASS.Agent uses + To learn which entities you have configured and to send quick actions, HASS.Agent uses Home Assistant's API. Please provide a long-lived access token and the address of your Home Assistant instance. You can get a token in Home Assistant by clicking your profile picture at the bottom-left and navigating to the bottom of the page until you see the 'CREATE TOKEN' button. - + - When a new update is available, HASS.Agent can download the installer and launch it for you. + When a new update is available, HASS.Agent can download the installer and launch it for you. The certificate of the downloaded file will get checked before running,you will still get to review the release notes and manually approve the update. - + - HASS.Agent checks for updates in the background if enabled. + HASS.Agent checks for updates in the background if enabled. You will be sent a push notification if a new update is discovered, letting you know a new version is ready to be installed. Do you want to enable this automatic update checks? - + - You can configure the HASS.Agent to use a specific interpreter such as Perl or Python. + You can configure the HASS.Agent to use a specific interpreter such as Perl or Python. Use the 'custom executor' command to launch this executor. - + - Custom Executor Name - + Custom Executor Name + - HASS.Agent will wait a grace period before notifying you of disconnects from MQTT or HA's API. + HASS.Agent will wait a grace period before notifying you of disconnects from MQTT or HA's API. You can set the amount of seconds to wait in this grace period below. - + - IMPORTANT: if you change this value, HASS.Agent will unpublish all your sensors, commands and force a restart of itself so they can be republished under the new device name. + IMPORTANT: if you change this value, HASS.Agent will unpublish all your sensors, commands and force a restart of itself so they can be republished under the new device name. Your automations and scripts will keep working. - + - The device name is used to identify your machine on Home Assistant. + The device name is used to identify your machine on Home Assistant. It is also used as a prefix for your command/sensor names (this can be changed per entity). - + - The local API is disabled however the media player requires it in order to function. - + The local API is disabled however the media player requires it in order to function. + - Password - + Password + - Username - + Username + - Port - + Port + - IP Address or Hostname - + IP Address or Hostname + - This page contains general configuration settings, for MQTT settings, commands, and sensors, browse the different tabs above. - + This page contains general configuration settings, for MQTT settings, commands, and sensors, browse the different tabs above. + - Auth &ID - + Auth &ID + - Device &Name - + Device &Name + - Tip: Double-click these fields to browse - + Tip: Double-click these fields to browse + - Custom Executor &Binary - + Custom Executor &Binary + - Custom &Executor Name - + Custom &Executor Name + - seconds - + seconds + - Disconnected Grace &Period - + Disconnected Grace &Period + - Version - + Version + - Tip: Double-click to generate random - + Tip: Double-click to generate random + - stored! - + stored! + - You can use the satellite service to run sensors and commands without having to be logged in. Not all types are available, for instance the 'LaunchUrl' command can only be added as a regular command. - + You can use the satellite service to run sensors and commands without having to be logged in. Not all types are available, for instance the 'LaunchUrl' command can only be added as a regular command. + - The keycode you have provided is not a valid number! + The keycode you have provided is not a valid number! Please ensure the keycode field is in focus and press the key you want simulated, the keycode should then be generated for you. - + - HASS.Agent will sanitize your device name to make sure HA will accept it, you can overrule this rule below if you're sure that your name will be accepted as-is. - + HASS.Agent will sanitize your device name to make sure HA will accept it, you can overrule this rule below if you're sure that your name will be accepted as-is. + - Enable Device Name &Sanitation - + Enable Device Name &Sanitation + - You've changed your device's name. + You've changed your device's name. All your sensors and commands will now be unpublished and published again after the HASS.Agent restarts. Don't worry! they'll keep their current names so your automations and scripts will continue to work. Note: You disabled sanitation, so make sure your device name is accepted by Home Assistant. - + - Enable State Notifications - + Enable State Notifications + - HASS.Agent sends notifications when the state of a module changes, you can adjust whether or not you want to receive these notifications below. - + HASS.Agent sends notifications when the state of a module changes, you can adjust whether or not you want to receive these notifications below. + - Error trying to bind the API to port {0}. + Error trying to bind the API to port {0}. Make sure no other instance of HASS.Agent is running and the port is available and registered. - + - Printers - + Printers + - No URL has been set! Please configure the webview through Configuration -> Tray Icon. - + No URL has been set! Please configure the webview through Configuration -> Tray Icon. + - MonitorSleep - + MonitorSleep + - MonitorWake - + MonitorWake + - PowerOn - + PowerOn + - PowerOff - + PowerOff + - Dimmed - + Dimmed + - Unknown - + Unknown + - MonitorPowerState - + MonitorPowerState + - PowershellSensor - + PowershellSensor + - powershell command or script - + powershell command or script + - Test Command/Script - + Test Command/Script + - Test succesfully executed, result value: + Test succesfully executed, result value: {0} - + - Please enter a command or script! - + Please enter a command or script! + - The test failed to execute: + The test failed to execute: {0} Do you want to open the logs folder? - + - SetVolume - + SetVolume + - Please enter a value between 0-100 as the desired volume level! - + Please enter a value between 0-100 as the desired volume level! + - If you don't enter a volume value, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. + If you don't enter a volume value, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. Are you sure you want this? - + - Maximized - + Maximized + - Minimized - + Minimized + - Normal - + Normal + - Unknown - + Unknown + - Hidden - + Hidden + - WindowState - + WindowState + - Puts all monitors in sleep (low power) mode. - + Puts all monitors in sleep (low power) mode. + - Tries to wake up all monitors by simulating a 'arrow up' keypress. - + Tries to wake up all monitors by simulating a 'arrow up' keypress. + - Sets the volume of the current default audiodevice to the specified level. - + Sets the volume of the current default audiodevice to the specified level. + - Returns your current latitude, longitude and altitude as a comma-seperated value. + Returns your current latitude, longitude and altitude as a comma-seperated value. Make sure Windows' location services are enabled! Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. - + - Provides the last monitor power state change: + Provides the last monitor power state change: Dimmed, PowerOff, PowerOn and Unkown. - + - Returns the result of the provided Powershell command or script. + Returns the result of the provided Powershell command or script. Converts the outcome to text. - + - Provides information about all installed printers and their queues. - + Provides information about all installed printers and their queues. + - Provides the current state of the process' window: + Provides the current state of the process' window: Hidden, Maximized, Minimized, Normal and Unknown. - + - Testing.. - + Testing.. + - WebcamProcess - + WebcamProcess + - MicrophoneProcess - + MicrophoneProcess + - Provides the name of the process that's currently using the webcam. + Provides the name of the process that's currently using the webcam. Note: if used in the satellite service, it won't detect userspace applications. - + - Provides the name of the process that's currently using the microphone. + Provides the name of the process that's currently using the microphone. Note: if used in the satellite service, it won't detect userspace applications. - + - BluetoothDevices - + BluetoothDevices + - BluetoothLeDevices - + BluetoothLeDevices + - Provides a sensor with the amount of bluetooth devices found. + Provides a sensor with the amount of bluetooth devices found. The devices and their connected state are added as attributes. - + - Provides a sensors with the amount of bluetooth LE devices found. + Provides a sensors with the amount of bluetooth LE devices found. The devices and their connected state are added as attributes. Only shows devices that were seen since the last report, ie. when the sensor publishes, the list clears. - + - The service is currently stopped and cannot be configured. + The service is currently stopped and cannot be configured. Please start the service first in order to configure it. - + - The name you provided contains unsupported characters and won't work. The suggested version is: + The name you provided contains unsupported characters and won't work. The suggested version is: {0} Do you want to use this version? - + - The name you provided contains unsupported characters and won't work. The suggested version is: + The name you provided contains unsupported characters and won't work. The suggested version is: {0} Do you want to use this version? - + - To learn which entities you have configured and to send quick actions, HASS.Agent uses + To learn which entities you have configured and to send quick actions, HASS.Agent uses Home Assistant's API. Please provide a long-lived access token and the address of your Home Assistant instance. You can get a token in Home Assistant by clicking your profile picture at the bottom-left and navigating to the bottom of the page until you see the 'CREATE TOKEN' button. - + - HASS.Agent has its own local API, so Home Assistant can send requests (for instance to send a notification). You can configure it globally here, and afterwards you can configure the dependent sections (currently notifications and mediaplayer). + HASS.Agent has its own local API, so Home Assistant can send requests (for instance to send a notification). You can configure it globally here, and afterwards you can configure the dependent sections (currently notifications and mediaplayer). Note: this is not required for the new integration to function. Only enable and use it if you don't use MQTT. - + - If something is not working, make sure you try the following steps: + If something is not working, make sure you try the following steps: - Install the HASS.Agent integration - Restart Home Assistant - Make sure HASS.Agent is active with MQTT enabled! - Your device should get detected and added as an entity automatically - Optionally: manually add it using the local API - + - HASS.Agent can act as a media player for Home Assistant, so you'll be able to see and control any media that's playing, and send text-to-speech. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. - + HASS.Agent can act as a media player for Home Assistant, so you'll be able to see and control any media that's playing, and send text-to-speech. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. + - both the local API and MQTT are disabled, but the integration needs at least one for it to work - + both the local API and MQTT are disabled, but the integration needs at least one for it to work + - Commands and sensors use MQTT, as well as notifications and media player functions when using the new integration. + Commands and sensors use MQTT, as well as notifications and media player functions when using the new integration. Please provide credentials for your broker, if you're using the HA Mosquitto addon, you can probably use the preset address. Note: these settings (excluding the Client ID) will also be applied to the satellite service. - + - Enable MQTT - + Enable MQTT + - If MQTT is not enabled, commands and sensors will not work! - + If MQTT is not enabled, commands and sensors will not work! + - If something is not working, make sure you try the following steps: + If something is not working, make sure you try the following steps: - Install the HASS.Agent integration - Restart Home Assistant - Make sure HASS.Agent is active with MQTT enabled! - Your device should get detected and added as an entity automatically - Optionally: manually add it using the local API - + - HASS.Agent can receive notifications from Home Assistant, using text, images and actions. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. - + HASS.Agent can receive notifications from Home Assistant, using text, images and actions. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. + - both the local API and MQTT are disabled, but the integration needs at least one for it to work - + both the local API and MQTT are disabled, but the integration needs at least one for it to work + - The satellite service allows you to run sensors and commands even when no user's logged in. + The satellite service allows you to run sensors and commands even when no user's logged in. Use the 'satellite service' button on the main window to manage it. - + - If you want to manage the service (add commands and sensor, change settings) you can do so here, or by using the 'satellite service' button on the main window. - + If you want to manage the service (add commands and sensor, change settings) you can do so here, or by using the 'satellite service' button on the main window. + - &Manage Service - + &Manage Service + - Show default menu on mouse left-click - + Show default menu on mouse left-click + - Commands and sensors are sent through MQTT. The notifications- and media player integration also make use of them. + Commands and sensors are sent through MQTT. The notifications- and media player integration also make use of them. Tip: if you're using the HA addon, you can probably use the preset address - just provide credentials. - + - Enable MQTT - + Enable MQTT + - HASS.Agent-Integration GitHub Page - + HASS.Agent-Integration GitHub Page + - Enable &Media Player (including text-to-speech) - + Enable &Media Player (including text-to-speech) + - Enable &Notifications - + Enable &Notifications + - Developing and maintaining this tool (and everything that surrounds it) takes up a lot of time. Like most developers, I run on caffeïne - so if you can spare it, a cup of coffee is always very much appreciated! - + Developing and maintaining this tool (and everything that surrounds it) takes up a lot of time. Like most developers, I run on caffeïne - so if you can spare it, a cup of coffee is always very much appreciated! + - There's a lot more to tinker with, so make sure you take a look at the Configuration Wwindow! + There's a lot more to tinker with, so make sure you take a look at the Configuration Wwindow! Thank you for using HASS.Agent, hopefully it'll be useful for you :-) - + - HASS.Agent will now restart to apply your configuration changes. - + HASS.Agent will now restart to apply your configuration changes. + - Yay, done! - + Yay, done! + - Tip: Other donation methods are available on the About Window. - + Tip: Other donation methods are available on the About Window. + - HASS.Agent Post Update - + HASS.Agent Post Update + - Command - + Command + - Like this tool? Support us (read: keep us awake) by buying a cup of coffee: - + Like this tool? Support us (read: keep us awake) by buying a cup of coffee: + - HASS.Agent is completely free, and will always stay that way without restrictions! + HASS.Agent is completely free, and will always stay that way without restrictions! However, developing and maintaining this tool (and everything that surrounds it, like support and the docs) takes up a lot of time. Like most developers, I run on caffeïne - so if you can spare it, a cup of coffee is always very much appreciated! - + - &Close - + &Close + - I already donated, hide the button on the main window. - + I already donated, hide the button on the main window. + - Donate - + Donate + - Check for Updates - + Check for Updates + - The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. + The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. Are you sure you want to use this key anyway? - + - The URI you have provided does not appear to be valid, a valid URI may look like either of the following: + The URI you have provided does not appear to be valid, a valid URI may look like either of the following: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Are you sure you want to use this URI anyway? - + - The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. + The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. Are you sure you want to use this key anyway? - + - The URI you have provided does not appear to be valid, a valid URI may look like either of the following: + The URI you have provided does not appear to be valid, a valid URI may look like either of the following: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Are you sure you want to use this URI anyway? - + - Your Home Assistant API token doesn't look right. Make sure you selected the entire token (don't use CTRL+A or doubleclick). + Your Home Assistant API token doesn't look right. Make sure you selected the entire token (don't use CTRL+A or doubleclick). It should contain three sections (seperated by two dots). Are you sure you want to use it like this? - + - Your Home Assistant URI doesn't look right. It should look something like 'http://homeassistant.local:8123' or 'https://192.168.0.1:8123'. + Your Home Assistant URI doesn't look right. It should look something like 'http://homeassistant.local:8123' or 'https://192.168.0.1:8123'. Are you sure you want to use it like this? - + - Your MQTT broker URI doesn't look right. It should look something like 'homeassistant.local' or '192.168.0.1'. + Your MQTT broker URI doesn't look right. It should look something like 'homeassistant.local' or '192.168.0.1'. Are you sure you want to use it like this? - + - Microsoft's WebView2 runtime isn't found on your machine. Usually this is handled by the installer, but you can install it manually. + Microsoft's WebView2 runtime isn't found on your machine. Usually this is handled by the installer, but you can install it manually. Do you want to download the runtime installer? - + - Something went wrong while initializing the WebView! Please check your logs and open a GitHub issue for further assistance. - + Something went wrong while initializing the WebView! Please check your logs and open a GitHub issue for further assistance. + - unable to open Service Manager - + unable to open Service Manager + - unable to open service - + unable to open service + - Error configuring startup mode, please check the logs for more information. - + Error configuring startup mode, please check the logs for more information. + - Error setting startup mode, please check the logs for more information. - + Error setting startup mode, please check the logs for more information. + - Timeout expired - + Timeout expired + - Fatal error, please check logs for information! - + Fatal error, please check logs for information! + - unknown reason - + unknown reason + - HassAgentSatelliteServiceStarted - + HassAgentSatelliteServiceStarted + - HassAgentStarted - + HassAgentStarted + - Selected Type - + Selected Type + - HASS.Agent only! - + HASS.Agent only! + - &Entity Type - + &Entity Type + - Show MQTT Action Topic - + Show MQTT Action Topic + - Action - + Action + - Multivalue - + Multivalue + - domain - + domain + InternalDeviceSensor @@ -3235,4 +3255,7 @@ Do you want to download the runtime installer? Button + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index 6f5d7c6e..9dadee0b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3320,4 +3320,7 @@ Home Assistant. Нажимать + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index 1edd421c..3e4b4bd0 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3401,4 +3401,7 @@ Ali želite prenesti runtime installer? Pritisnite + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index 28956a1c..8d211787 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2859,4 +2859,7 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku Basmak + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index 1612eadf..7c1b50ef 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -1,11 +1,11 @@ - - + + WinExe net6.0-windows10.0.19041.0 disable true - true + true enable HASS.Agent hassagent.ico @@ -28,25 +28,31 @@ None win10-x64;win10-x86 true - true - false - false - false + true + false + false + false - + + 4.0 + en-US + true + true + + + + + - - - - + + - @@ -75,13 +81,12 @@ + - - Libraries\HADotNet.Core.dll @@ -90,7 +95,6 @@ Libraries\HotkeyListener.dll - UserControl @@ -144,7 +148,6 @@ Languages.resx - ConfigExternalTools.resx @@ -1844,7 +1847,6 @@ Languages.resx - PreserveNewest @@ -1854,9 +1856,7 @@ \ - - \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs new file mode 100644 index 00000000..9a6029d8 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs @@ -0,0 +1,104 @@ +#nullable enable +using System.Runtime.Caching; +using HASS.Agent.Shared.Extensions; +using HASS.Agent.Shared.Models.HomeAssistant; +using Windows.Devices.Geolocation; +using HASS.Agent.Models.Internal; +using Newtonsoft.Json; + +namespace HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue +{ + /// + /// Sensor containing the coördinates of the device + /// XXXX Track location change event, and make non breaking + /// + public class DeviceTrackerSensor : AbstractSingleValueSensor + { + private const string DefaultName = "devicetracksensor"; + + public DeviceTrackerSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, + string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, + updateInterval ?? 30, id, advancedSettings: advancedSettings) + { + UseAttributes = true; + } + + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + if (Variables.MqttManager == null) return null; + + var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); + if (deviceConfig == null) return null; + + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() + { + EntityName = EntityName, + Name = Name, + Unique_id = Id, + Device = deviceConfig, + State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + Icon = "mdi:earth", + Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes", + }); + } + + public override string GetState() + { + var state = "ON"; + return state; + } + + public override string? GetAttributes() + { + ObjectCache cache = System.Runtime.Caching.MemoryCache.Default; + GeolocationInfo? fileContents = cache["location_" + this.Id] as GeolocationInfo; + string jsonPayload = ""; + + if (fileContents != null) + { + jsonPayload = JsonConvert.SerializeObject(fileContents, Formatting.Indented); + return jsonPayload; + } + + GeolocationInfo gli = new GeolocationInfo(); + + var accessStatus = Geolocator.RequestAccessAsync().GetAwaiter().GetResult(); + switch (accessStatus) + { + case GeolocationAccessStatus.Allowed: + // https://community.home-assistant.io/t/attributes-latitude-and-longitude-in-a-lovelace-map/318760 + var geolocator = new Geolocator(); + + var position = geolocator.GetGeopositionAsync().GetAwaiter().GetResult(); + var lat = position.Coordinate.Latitude.ConvertToStringDotDecimalSeperator(); + var lon = position.Coordinate.Longitude.ConvertToStringDotDecimalSeperator(); + var alt = position.Coordinate.Altitude?.ConvertToStringDotDecimalSeperator(); + var accuracy = position.Coordinate.Accuracy.ConvertToStringDotDecimalSeperator(); + var sourceType = position.Coordinate.PositionSource; + + gli = new GeolocationInfo(lon, lat, sourceType) + { + altitude = alt, + gps_accuracy = accuracy, + not_permitted = "false" + }; + + cache.Set("location_" + Id, gli, DateTimeOffset.Now.AddSeconds(UpdateIntervalSeconds - 1)); + break; + + case GeolocationAccessStatus.Denied: + // notify user: Access to location is denied + gli.not_permitted = "true - enable location access"; + break; + + case GeolocationAccessStatus.Unspecified: + // notify user: Unspecified error + gli.not_permitted = "true"; + break; + } + + jsonPayload = JsonConvert.SerializeObject(gli, Formatting.Indented); + return jsonPayload; + } + } +} diff --git a/src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs b/src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs new file mode 100644 index 00000000..045500cc --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs @@ -0,0 +1,37 @@ +using Windows.Devices.Geolocation; + +namespace HASS.Agent.Models.Internal +{ + public class GeolocationInfo + { + public string latitude { get; set; } + public string longitude { get; set; } + public string altitude { get; set; } + public string source_type { get; set; } + public string gps_accuracy { get; set; } + public string not_permitted { get; set; } + + public GeolocationInfo() + { + + } + + public GeolocationInfo(string lon, string lat, PositionSource source) + { + longitude = lon; + latitude = lat; + source_type = ""; + gps_accuracy = "1.2"; + + switch (source) + { + case PositionSource.Satellite: + source_type = "gps"; + break; + case PositionSource.WiFi: + source_type = "router"; + break; + } + } + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index 269df973..14e09776 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -5990,6 +5990,19 @@ internal static string SensorsManager_CurrentVolumeSensorDescription { } } + /// + /// Looks up a localized string similar to Returns your current latitude, longitude and altitude as as location/device tracker, to directly display and track on a map. + /// + ///Make sure Windows' location services are enabled! + /// + ///Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'.. + /// + internal static string SensorsManager_DeviceTrackerSensorDescription { + get { + return ResourceManager.GetString("SensorsManager_DeviceTrackerSensorDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Provides a sensor with the amount of displays, name of the primary display, and per display its name, resolution and bits per pixel.. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index 63a460db..2664b871 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3528,4 +3528,10 @@ Muss unter „Konfiguration -> Tray-Icon“ konfiguriert werden. Presse + + Gibt den aktuellen Breitengrad, Längengrad und die Höhe als Location zurück, um sie direkt auf einer Karte anzuzeigen und zu verfolgen. + +Stelle sicher, dass die Ortungsdienste von Windows aktiviert sind! +Abhängig von Ihrer Windows-Version finden Sie dies in der neuen Systemsteuerung > "Datenschutz und Sicherheit" > "Standort". + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 8dc6f1b7..33bdce83 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3433,4 +3433,11 @@ Requires it to be configured in "Configuration -> Tray Icon" Press + + Returns your current latitude, longitude and altitude as as location/device tracker, to directly display and track on a map. + +Make sure Windows' location services are enabled! + +Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 16b9e7d7..aea7b6f8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3404,4 +3404,11 @@ Requiere que se configure en "Configuración -> Icono de la bandeja" Prensa + + Devuelve su latitud, longitud y altitud actuales como rastreador de ubicación / dispositivo, para mostrar y rastrear directamente en un mapa. + +¡Asegúrate de que los servicios de ubicación de Windows estén habilitados! + +Dependiendo de su versión de Windows, esto se puede encontrar en el nuevo panel de control > 'privacidad y seguridad' > 'ubicación'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 1bfdf5df..b4ba691b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3437,4 +3437,11 @@ Nécessite sa configuration dans « Configuration -> Icône de la barre d'ét Presse + + Renvoie votre latitude, longitude et altitude actuelles sous forme de traqueur de localisation/appareil, pour les afficher et les suivre directement sur une carte. + +Assurez-vous que les services de localisation de Windows sont activés ! + +Selon votre version de Windows, vous pouvez le trouver dans le nouveau panneau de configuration > 'confidentialité et sécurité' > 'localisation'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index ea161750..dd4d12fe 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3425,4 +3425,11 @@ Vereist dat het geconfigureerd is in "Configuratie -> Tray Icon" Pers + + Retourneert uw huidige breedtegraad, lengtegraad en hoogte als locatie-/apparaattracker, om direct weer te geven en te volgen op een kaart. + +Zorg ervoor dat de locatieservices van Windows zijn ingeschakeld! + +Afhankelijk van je Windows-versie is dit terug te vinden in het nieuwe configuratiescherm > 'privacy en beveiliging' > 'locatie'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index b8a5706d..9ed5d707 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3514,4 +3514,11 @@ Wymaga konfiguracji w „Configuration -> Tray Icon” Naciśnij + + Zwraca aktualną szerokość, długość i wysokość jako lokalizację/urządzenie śledzące, aby bezpośrednio wyświetlić i śledzić na mapie. + +Upewnij się, że usługi lokalizacyjne systemu Windows są włączone! + +W zależności od wersji systemu Windows można to znaleźć w nowym panelu sterowania > "prywatność i bezpieczeństwo" > "lokalizacja". + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index f171b0c2..d46cecb5 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -3450,4 +3450,11 @@ Requer que seja configurado em "Configuration -> Tray Icon" Imprensa + + Retorna sua latitude, longitude e altitude atuais como rastreador de localização/dispositivo, para exibir e rastrear diretamente em um mapa. + +Certifique-se de que os serviços de localização do Windows estejam ativados! + +Dependendo da sua versão do Windows, isso pode ser encontrado no novo painel de controle > 'privacidade e segurança' > 'localização'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index da531004..df9e9686 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -2916,6 +2916,13 @@ Are you sure you want this? Make sure Windows' location services are enabled! +Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. + + + Returns your current latitude, longitude and altitude as as location/device tracker, to directly display and track on a map. + +Make sure Windows' location services are enabled! + Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index 24b2bb4f..bb90c870 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3473,4 +3473,11 @@ Home Assistant. Нажимать + + Возвращает текущую широту, долготу и высоту в виде отслеживания местоположения/устройства для непосредственного отображения и отслеживания на карте. + +Убедитесь, что службы определения местоположения Windows включены! + +В зависимости от вашей версии Windows, это можно найти в новой панели управления > разделах «Конфиденциальность и безопасность» > «Местоположение». + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index 0c0728a7..884780a1 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -3553,4 +3553,11 @@ Zahteva, da je konfiguriran v "Konfiguracija -> Ikona pladnja" Pritisnite + + Vráti vašu aktuálnu zemepisnú šírku, dĺžku a nadmorskú výšku ako sledovanie polohy/zariadenia na priame zobrazenie a sledovanie na mape. + +Uistite sa, že sú povolené lokalizačné služby systému Windows! + +V závislosti od verzie systému Windows ho nájdete v novom ovládacom paneli > "súkromie a zabezpečenie" > "umiestnenie". + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index e1d3413b..9d30ffb8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Bu sayfa, dış araçlarla bağlantı kurmanızı sağlar. - + Tarayıcı adı - + Varsayılan olarak HASS.Agent, varsayılan tarayıcınızı kullanarak URL'leri başlatır. Ayrıca, özel modda çalışacak başlatma argümanlarıyla birlikte kullanılacak belirli bir tarayıcıyı da yapılandırabilirsiniz. Fuzzy - + Tarayıcı İkili - + Ek Başlatma Argümanları - + Özel Yürütücü İkili Dosyası - + HASS.Agent'ı Perl veya Python gibi belirli bir yorumlayıcı kullanacak şekilde yapılandırabilirsiniz. Bu yürütücüyü başlatmak için 'özel yürütücü' komutunu kullanın. - + Özel Yürütücü Adı - + İpucu: Göz atmak için çift tıklayın - + &Ölçek - + HASS.Agent, MQTT veya HA'nın API'si ile olan bağlantı kesintilerini size bildirmeden önce bir ek süre bekleyecektir. Aşağıda bu ek süre içinde beklenecek saniye miktarını ayarlayabilirsiniz. - + saniye - + Bağlantı Kesildi İzin ve Süresi - + ÖNEMLİ: Bu değeri değiştirirseniz, HASS.Agent tüm sensörlerinizi, komutlarınızı yayından kaldıracak ve yeni cihaz adı altında yeniden yayınlanabilmeleri için kendini yeniden başlatmaya zorlayacaktır. Otomasyonlarınız ve komut dosyalarınız çalışmaya devam edecek. - + Cihaz adı, Home Assistant'ta makinenizi tanımlamak için kullanılır. Ayrıca komut/sensör adlarınız için bir önek olarak kullanılır (bu, varlık başına değiştirilebilir). Fuzzy - + Bu sayfa genel yapılandırma ayarlarını içerir, daha fazla ayar için soldaki sekmelere göz atabilirsiniz. - + Cihaz adı - + İpucu: Göz atmak için bu alana çift tıklayın - + Müşteri ve Sertifika - + &Otomatik istemci sertifikası seçimini kullan - + &Test bağlantısı - + Hangi varlıkları yapılandırdığınızı öğrenmek ve hızlı eylemler göndermek için HASS.Agent, Home Assistant'ın API'sini kullanır. @@ -197,991 +197,991 @@ ve 'TOKEN OLUŞTUR' düğmesini görene kadar sayfanın en altına giderek bir t İşlem yapılabilir bildirim işlevi için yönetici hesabı belirteci sağlamanız gerektiğini lütfen unutmayın. - + &API Simgesi - + Sunucu &URI - + &Temizlemek - + Hızlı işlemlerinizi almanın kolay bir yolu, genel bir global hotkey kullanmaktır. Bu şekilde, makinenizde ne yapıyorsanız yapın, Home Assistant ile her zaman etkileşimde bulunabilirsiniz. - + &Hızlı İşlemler Kısayol Tuşunu Etkinleştir - + &Kısayol Kombinasyonu - + Görüntü Önbelleğini Temizle - + Açık dosya - + Görüntü Önbelleği Konumu - + günler - + Bildirimlerde gösterilen resimler gibi bazı öğelerin geçici olarak yerel olarak depolanması gerekir. HASS.Agent bunları silmeden önce tutulması gereken gün miktarını yapılandırabilirsiniz. Bunları kalıcı olarak tutmak için '0' girin. - + Genişletilmiş günlük kaydı, varsayılan günlük kaydının yeterli olmaması durumunda daha ayrıntılı ve derinlemesine günlük kaydı sağlar. Lütfen bunun etkinleştirilmesinin günlük dosyalarının büyümesine neden olabileceğini ve yalnızca HASS.Agent'ın kendisinde bir sorun olduğundan şüphelendiğinizde veya geliştiriciler tarafından istendiğinde kullanılması gerektiğini unutmayın. - + &Genişletilmiş Günlüğe Kaydetmeyi Etkinleştir - + &Günlükler Klasörünü Aç - + İpucu: Göz atmak için bu alanları çift tıklayın - + Müşteri Sertifikası - + Kök Sertifikası - + Bayrağı &Kaldır Kullan - + &Güvenilmeyen Sertifikalara İzin Ver - + &Yapılandırmayı Temizle - + (emin değilseniz varsayılanı bırakın) - + Komutlar ve sensörler, yeni entegrasyonu kullanırken bildirimler ve medya oynatıcı işlevlerinin yanı sıra MQTT'yi kullanır. Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini kullanıyorsanız, muhtemelen önceden ayarlanmış adresi kullanabilirsiniz. Not: Bu ayarlar (Client ID hariç) uydu hizmetine de uygulanacaktır. - + Keşif Öneki - + Şifre - + Kullanıcı adı - + Liman - + Aracı IP Adresi veya Ana Bilgisayar Adı - + (otomatik oluşturmak için boş bırakın) - + Müşteri Kimliği - + Bir şey çalışmıyorsa, aşağıdaki adımları denediğinizden emin olun: - HASS.Agent entegrasyonunu kurun - Home Assistant'ı yeniden başlatın - MQTT etkinken HASS.Agent'ın etkin olduğundan emin olun! - Cihazınız otomatik olarak bir varlık olarak algılanmalı ve eklenmelidir - İsteğe bağlı olarak: yerel API'yi kullanarak manuel olarak ekleyin - + HASS.Agent metin, resim ve eylemleri kullanarak Home Assistant'tan bildirimler alabilir. MQTT'yi etkinleştirdiyseniz, cihazınız otomatik olarak eklenir. Aksi takdirde, yerel API'yi kullanmak için entegrasyonu manuel olarak yapılandırın. - + Bildirimler ve Belgeler - + Liman - + &Bildirimleri Kabul Et - + Test Bildirimini Göster - + Liman Rezervasyonunu Yürüt - + &Görüntüler için sertifika hatalarını yoksay - + Uydu hizmeti, hiçbir kullanıcı oturum açmadığında bile sensörleri ve komutları çalıştırmanıza izin verir. Yönetmek için ana penceredeki 'uydu hizmeti' düğmesini kullanın. - + Servis durumu: - + Hizmeti&başlat - + &Hizmeti Devre Dışı Bırak - + &Hizmeti durdur - + &Hizmeti Etkinleştir - + &Hizmeti Yeniden Yükle - + Hizmeti yapılandırmazsanız, hiçbir şey yapmaz. Ancak, yine de devre dışı bırakmaya karar verebilirsiniz. Yükleyici, devre dışı bırakılan hizmeti kendi başına bırakacaktır (hizmeti kaldırırsanız, yükleyici yeniden yükleyecektir). - + Düzgün çalışmıyorsa hizmeti yeniden yüklemeyi deneyebilirsiniz. Yapılandırmanız ve varlıklarınız kaldırılmayacak. - + Hizmet ve Günlükler Klasörünü Aç - + Yeniden yüklemeden sonra hizmet hala başarısız olursa, lütfen bir bilet açın ve en son günlüğün içeriğini gönderin. - + HASS.Agent, kullanıcı profilinizin kayıt defterinde bir giriş oluşturarak oturum açtığınızda başlayabilir. HASS.Agent kullanıcı tabanlı olduğundan, başka bir kullanıcı için başlatmak istiyorsanız, HASS.Agent'ı orada kurun ve yapılandırın. - + &Oturum Açıldığında Başlatmayı Etkinleştir - + Girişte Başlama Durumu: - + Beni &beta sürümlerinden haberdar et - + Yeni bir güncelleme mevcut olduğunda, HASS.Agent yükleyiciyi indirebilir ve sizin için başlatabilir. İndirilen dosyanın sertifikası çalıştırmadan önce kontrol edilecek, yine de sürüm notlarını gözden geçirecek ve güncellemeyi manuel olarak onaylayacaksınız. - + Gelecekteki güncellemeleri otomatik olarak &indir - + HASS.Agent, etkinleştirilirse arka planda güncellemeleri kontrol eder. Yeni bir güncelleme keşfedilirse, yeni bir sürümün yüklenmeye hazır olduğunu bildiren bir anında iletme bildirimi gönderilecektir. - + Yeni bir &sürüm çıktığında bana haber ver - + HASS.Agent'a hoş geldiniz! Aracıyı ilk kez başlatıyorsunuz gibi görünüyor. İlk kurulumda size yardımcı olmak için aşağıdaki yapılandırma adımlarını uygulayın veya alternatif olarak 'Kapat'ı tıklayın. - + Cihaz adı, Home Assistant'ta makinenizi tanımlamak için kullanılır, ayrıca komutlarınız ve sensörleriniz için önerilen bir önek olarak kullanılır. - + Cihaz adı - + Evet, Sistem Girişinde HASS.Agent'ı &başlatın - + HASS.Agent, sisteminizle başlayabilir, bu, oturum açar açmaz cihazınız ve Home Assistant arasındaki tüm sensörlerin ve veri aktarımının başlamasına olanak tanır. Bu ayar, daha sonra HASS.Agent yapılandırma penceresinde herhangi bir zamanda değiştirilebilir. - + Mevcut durum getiriliyor, lütfen bekleyin.. - + Not: 5115 varsayılan bağlantı noktasıdır, yalnızca Home Assistant'ta değiştirdiyseniz değiştirin. - + Evet, bağlantı noktasındaki bildirimleri kabul et - + HASS.Agent, metin ve/veya resimler kullanarak Home Assistant'tan bildirimler alabilir. Bu işlevi etkinleştirmek istiyor musunuz? - + HASS.Agent-Notifier GitHub Sayfası - + Şu adımları uyguladığınızdan emin olun: - HASS.Agent-Notifier entegrasyonunu kurun - Home Assistant'ı yeniden başlatın - Bir bildirim varlığı yapılandırın - Home Assistant'ı yeniden başlatın - + Bildirimleri kullanmak için Home Assistant'ta HASS.Agent-notifier entegrasyonunu kurmanız ve yapılandırmanız gerekir. Bu, HACS'yi kullanmak çok kolaydır, ancak manuel olarak da kurulabilir, daha fazla bilgi için aşağıdaki bağlantıyı ziyaret edin. - + API & Jeton - + Sunucu &URI (böyle olması gerekir) - + Hangi varlıkları yapılandırdığınızı öğrenmek ve hızlı eylemler göndermek için HASS.Agent, Home Assistant'ın API'sini kullanır. Lütfen uzun ömürlü bir erişim belirteci ve Home Assistant örneğinizin adresini sağlayın. Home Assistant'ta sol alttaki profil resminize tıklayarak ve 'TOKEN OLUŞTUR' düğmesini görene kadar sayfanın en altına giderek bir jeton alabilirsiniz. - + Test bağlantısı - + İpucu: Özel ayarlar, Yapılandırma Penceresinde bulunabilir. - + Şifre - + Kullanıcı adı - + Liman - + IP Adresi veya Ana Bilgisayar Adı - + Komutlar ve sensörler MQTT aracılığıyla gönderilir. Bildirimler ve medya oynatıcı entegrasyonu da bunlardan yararlanır. İpucu: HA eklentisini kullanıyorsanız, muhtemelen önceden ayarlanmış adresi kullanabilirsiniz - sadece kimlik bilgilerini sağlayın. - + Keşif Öneki - + (emin değilseniz varsayılanı bırakın) - + İpucu: Özel ayarlar, Yapılandırma Penceresinde bulunabilir. - + &Kısayol Kombinasyonu - + Hızlı işlemlerinizi almanın kolay bir yolu, genel bir kısayol tuşu kullanmaktır. Bu şekilde, makinenizde ne yapıyorsanız yapın, Home Assistant ile her zaman etkileşimde bulunabilirsiniz. - + &Temizlemek - + HASS.Agent, etkinleştirilirse arka planda güncellemeleri kontrol eder. Yeni bir güncelleme keşfedilirse, yeni bir sürümün yüklenmeye hazır olduğunu bildiren bir anında iletme bildirimi gönderilecektir. Bu otomatik güncelleme kontrollerini etkinleştirmek istiyor musunuz? - + Evet, beni yeni &güncellemelerden haberdar et - + Evet, benim için yükleyiciyi indirip başlatın - + Yeni bir güncelleme mevcut olduğunda, HASS.Agent yükleyiciyi indirebilir ve sizin için başlatabilir. İndirilen dosyanın sertifikası çalıştırmadan önce kontrol edilecek, yine de sürüm notlarını gözden geçirecek ve güncellemeyi manuel olarak onaylayacaksınız. - + HASS.Agent GitHub sayfası - + Kurcalanacak daha çok şey var, bu yüzden Yapılandırma Penceresine bir göz attığınızdan emin olun! HASS.Agent'ı kullandığınız için teşekkür ederiz, umarım işinize yarar :-) - + HASS.Agent şimdi yapılandırma değişikliklerinizi uygulamak için yeniden başlatılacak. - + Tamam, bitti! - + Düşük Bütünlük - + İsim - + Tip - + &Kaldırmak - + &Değiştir - + &Yeni ekle - + &Gönder && Komutları Etkinleştir - + saklanan komutlar! - + &Uygulamak - + Yetki ve Kimlik - + kimlik doğrulama - + Hizmetle bağlantı kurun - + Uydu servisi bağlanıyor, lütfen bekleyin.. - + Yapılandırmayı Getir - + Bu sayfa, MQTT ayarları, komutları ve sensörler için genel yapılandırma ayarlarını içerir, yukarıdaki farklı sekmelere göz atın. - + Yetki ve Kimlik - + Cihaz adı - + İpucu: Göz atmak için bu alanları çift tıklayın - + Özel Yürütücü ve İkili - + Özel ve Yürütücü Adı - + saniye - + Bağlantı Kesildi İzin ve Süresi - + Uygulamak - + Sürüm - + İpucu: Rastgele oluşturmak için çift tıklayın - + Saklanmış! - + (otomatik oluşturmak için boş bırakın) - + Müşteri Kimliği - + İpucu: Göz atmak için bu alanları çift tıklayın - + Müşteri Sertifikası - + Kök Sertifikası - + Bayrağı &Kaldır Kullan - + &Güvenilmeyen Sertifikalara İzin Ver - + &Yapılandırmayı Temizle - + (emin değilseniz varsayılanı bırakın) - + Komutlar ve sensörler MQTT aracılığıyla gönderilir. Lütfen sunucunuz için kimlik bilgilerini sağlayın. HA eklentisini kullanıyorsanız, muhtemelen önceden ayarlanmış adresi kullanabilirsiniz. - + Keşif Öneki - + Şifre - + Kullanıcı adı - + Liman - + Aracı IP Adresi veya Ana Bilgisayar Adı - + &Gönder && Yapılandırmayı Etkinleştir - + &HASS.Agent'tan kopyala - + Yapılandırma kaydedildi! - + Durum - + sorgulanıyor.. - + İsim - + Tip - + Yenile - + &Kaldırmak - + &Yeni ekle - + &Değiştir - + &Gönder && Sensörleri Etkinleştir - + Sensörler kaydedildi! - + Lütfen görev yapılırken biraz bekleyin .. - + API Bağlantı Noktası Bağlama Oluştur - + Güvenlik Duvarı Kuralı Belirle - + HASS.Agent Port Rezervasyonu - + Bazı güncelleme sonrası görevler yapılırken lütfen biraz bekleyin.. - + Uydu Hizmetini Yapılandırma - + API Bağlantı Noktası Bağlama Oluştur - + HASS.Agent Gönderi Güncellemesi - + HASS.Agent yeniden başlatılırken lütfen bekleyin.. - + Önceki örneğin kapanması bekleniyor.. - + HASS.Agent'ı Yeniden Başlatın - + HASS.Agent Yeniden Başlatıcı - + Uydu servisi yeniden kurulurken lütfen bekleyiniz.. - + Uydu Hizmetini Kaldır - + Uydu Hizmetini Yükle - + HASS.Agent Uydu Hizmetini Yeniden Yükleme - + Uydu hizmeti yapılandırılırken lütfen bekleyin.. - + Uydu Hizmetini Etkinleştir - + HASS.Agent Uydu Hizmetini Yapılandırma - + &Kapat - + Bu, eylem komutlarını yayınlayabileceğiniz MQTT konusudur: - + Panoya kopyala - + yardım ve örnekler - + MQTT Eylem Konusu - + &Kaldırmak - + &Değiştir - + &Yeni ekle - + &Komutları Sakla ve Etkinleştir - + İsim - + Tip - + Düşük Bütünlük - + Eylem - + Komut Yapılandırması - + &Mağaza Komutu - + &Yapılandırma - + &İsim - + Tanım - + &'Düşük Bütünlük' olarak çalıştır - + Bu nedir? - + Tip - + Seçilen Tip - + Hizmet - + ajan - + Yalnızca HASS.Agent! - + &Varlık Türü - + MQTT Eylem Konusunu Göster - + Eylem - + Emretmek - + Varlıklar alınıyor, lütfen bekleyin.. - + Hızlı Eylemler - + Hızlı İşlemleri &Depola - + &Yeni ekle - + &Değiştir - + &Kaldırmak - + &Ön izleme - + Alan adı - + varlık - + Eylem - + Kısayol tuşu - + Tanım - + Etkinleştirilmiş Fuzzy - + Hızlı Eylemler Yapılandırması - + &Mağaza Hızlı İşlemi - + Alan adı - + &Varlık - + İstenen ve Eylem - + &Tanım - + Varlıklar alınıyor, lütfen bekleyin.. - + kısayol tuşunu etkinleştir - + &kısayol tuşu kombinasyonu - + (isteğe bağlı, varlık adı yerine kullanılacaktır) - + Hızlı Eylem - + &Kaldırmak - + &Değiştir - + &Yeni ekle - + &Depola && Sensörleri Etkinleştir - + İsim - + Tip - + Yenile - + Sensör Yapılandırması - + &Mağaza Sensörü - + ayar 1 - + Seçilen Tip - + &İsim - + &Her güncelleme - + saniye - + Tanım - + Ayar 2 - + Ayar 3 - + Tip - + çok değerli Fuzzy - + Ajan - + Hizmet - + Yalnızca HASS.Agent! - + sensör - + Genel - + MQTT - + Komutlar - + Sensörler - + Uydu Servis Yapılandırması - + &Kapat - + Home Assistant platformu için Windows tabanlı bir istemci. - + Bu uygulama açık kaynak kodlu ve tamamen ücretsizdir, lütfen kullanılan bileşenlerin proje sayfalarını bireysel lisansları için kontrol edin: - + Sıkı çalışmalarını biz fanilerle paylaşma nezaketini gösteren bu projelerin geliştiricilerine büyük bir 'teşekkür ederim'. - + Ve tabi ki; Paulus Shoutsen ve Home Assistant :-) yaratan ve bakımını yapan tüm geliştirici ekibine teşekkürler - + Daha da fazla sevgiyle yaratılan orijinal - + HASS.Agent Ekibi şu anda bağış kabul etmemektedir. Ancak bu aracı beğendiyseniz orijinal (LAB02 Araştırması) geliştiricilere bir fincan kahve satın alarak destekleyin: - + Hakkında - + Genel - + Harici Araçlar - + Ev Yardımcısı API'sı - + Kısayol tuşu - + Yerel depolama - + Kerestecilik - + MQTT - + Bildirimler - + Uydu Hizmeti - + Başlatmak - + Güncellemeler - + &Hakkında - + &Yardım && İletişim - + &Yapılandırmayı Kaydet - + Kapat &Kaydetmeden - + Yapılandırma - + Ne yapmak istersin? - + &Tekrar başlat - + &Saklamak - + &Çıkış - + İletişim Kutusundan Çık - + &Kapat - + HASS.Agent ile sorun yaşıyorsanız ve herhangi bir sensör, komut veya genel destek ve geri bildirim için desteğe ihtiyacınız varsa, bize ulaşmanın birkaç yolu vardır: - + Hakkında - + Ana Sayfa Asistan Forumu - + GitHub Sorunları - + Diğer HA kullanıcılarının da size yardımcı olabileceğine ek olarak her şeyden biraz! - + Hataları bildirin, özellik istekleri gönderin, en son değişiklikleri görün vb. - + HASS.Agent'ı kurma ve kullanma konusunda yardım alın, hataları bildirin veya genel sohbete katılın! - + HASS.Agent belgelerine ve kullanım örneklerine göz atın. - + Yardım - + HASS.Agent'ı göster - + Hızlı İşlemleri Göster - + Yapılandırma - + Hızlı Eylemleri Yönet - + Yerel Sensörleri Yönetin - + Komutları Yönet - + Güncellemeleri kontrol et - + Bağış yapmak - + Yardım && İletişim - + Hakkında - + HASS.Agent'tan çıkın - + &Saklamak - + Kontroller - + U&uydu Hizmeti - + &yapılandırma - + &Hızlı İşlemler - + Yükleniyor.. - + Yükleniyor.. - + Sistem durumu - + Uydu Hizmeti: - + Komutlar: - + Sensörler: - + Hızlı İşlemler: - + Ev Asistanı API'sı: - + bildirim API'si: - + &Sonraki - + &Kapat - + &Öncesi - + HASS.Agent Katılımı - + Bilgi alınıyor, lütfen bekleyin.. - + Yeni bir sürüm mevcut: - + Sürüm notları - + &Güncellemeyi Yoksay - + Sürüm Sayfası - + HASS.Agent Güncellemesi - + Özel bir komut yürütün. Bu komutlar özel yükseltme olmadan çalışır. Yükseltilmiş olarak çalıştırmak için bir Zamanlanmış Görev oluşturun ve görevinizi yürütmek için komut olarak 'schtasks /Run /TN "TaskName"'i kullanın. Veya daha sıkı yürütme için 'düşük bütünlük olarak çalıştır'ı etkinleştirin. - + Komutu, yapılandırılmış özel yürütücü aracılığıyla yürütür (Yapılandırma -> Dış Araçlar'da). Komutunuz 'olduğu gibi' bir argüman olarak sağlanır, bu nedenle gerekirse kendi alıntılarınızı vb. sağlamanız gerekir. - + Makineyi hazırda bekletme moduna geçirir. - + Tek bir tuşa basmayı simüle eder. 'Keycode' metin kutusuna tıklayın ve simüle edilmesini istediğiniz tuşa basın. İlgili anahtar kodu sizin için girilecektir. @@ -1189,1714 +1189,1714 @@ TAB tuşu için lütfen LCTRL+TAB kullanın. Daha fazla tuşa ve/veya CTRL gibi değiştiricilere ihtiyacınız varsa, MultipleKeys komutunu kullanın. - + Varsayılan tarayıcınızda varsayılan olarak sağlanan URL'yi başlatır. 'Gizli' kullanmak için Yapılandırma -> Harici Araçlar'da belirli bir tarayıcı sağlayın. Yalnızca belirli bir URL'ye sahip bir pencere istiyorsanız (tam bir tarayıcı değil), bir 'WebView' komutu kullanın. - + Geçerli oturumu kilitler. - + Geçerli oturumun oturumunu kapatır. - + 'Sessiz' tuşunu simüle eder. - + 'Sonraki Medya' tuşunu simüle eder. - + 'Medya Duraklat/Oynat' tuşunu simüle eder. - + 'Önceki Medya' tuşunu simüle eder. - + 'Sesi Kısma' tuşunu simüle eder. - + 'Sesi Aç' tuşunu simüle eder. - + Birden fazla tuşa basmayı simüle eder. Her tuşun arasına [ ] koymanız gerekir, aksi takdirde HASS.Agent onları ayırt edemez. Diyelim ki X TAB Y SHIFT-Z'ye basmak istiyorsunuz, bu [X] [{TAB}] [Y] [+Z] olur. Kullanabileceğiniz birkaç numara vardır: - Bir parantezin basılmasını istiyorsanız, ondan kaçının, bu nedenle [ [\[] ve ] [\]] olur - Özel tuşlar { } arasında gidip gelir, örneğin {TAB} veya {UP} - SHIFT, CTRL için ^ ve ALT için % eklemek için bir tuşun önüne + koyun. Yani +C, SHIFT-C'dir. Veya +(CD), SHIFT-C ve SHIFT-D'dir, +CD ise SHIFT-C ve D'dir - Birden fazla basış için {z 15} kullanın, bu, Z'ye 15 kez basılacağı anlamına gelir. Daha fazla bilgi: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.sendkeys - + Bir Powershell komutu veya betiği yürütün. Bir komut dosyasının (*.ps1) konumunu veya tek satırlı bir komutu sağlayabilirsiniz. Bu, özel yükseklik olmadan çalışacaktır. - + Tüm sensör kontrollerini sıfırlar, tüm sensörleri değerlerini işlemeye ve göndermeye zorlar. Örneğin, bir HA yeniden başlatma sonrasında HASS.Agent'ı tüm sensörlerinizi güncellemeye zorlamak istiyorsanız kullanışlıdır. - + Bir dakika sonra makineyi yeniden başlatır. İpucu: Yanlışlıkla mı tetiklendi? Kapatmayı iptal etmek için 'shutdown /a' komutunu çalıştırın. - + Bir dakika sonra makineyi kapatır. İpucu: Yanlışlıkla mı tetiklendi? Kapatmayı iptal etmek için 'shutdown /a' komutunu çalıştırın. - + Makineyi uyku moduna geçirir. Not: Windows'taki bir sınırlama nedeniyle, bu yalnızca hazırda bekletme modu devre dışı bırakıldığında çalışır, aksi takdirde yalnızca hazırda bekletme moduna geçer. Bunu atlatmak için NirCmd (http://www.nirsoft.net/utils/nircmd.html) gibi bir şey kullanabilirsiniz. - + Lütfen tarayıcınızın ikili dosyasının konumunu girin! (.exe dosyası) - + Sağlanan tarayıcı ikili dosyası bulunamadı, lütfen yolun doğru olduğundan emin olun ve tekrar deneyin. - + Gizli bağımsız değişken sağlanmadı, bu nedenle tarayıcı muhtemelen normal şekilde başlatılacaktır. Devam etmek istiyor musun? - + Tarayıcınızı gizli modda başlatırken bir şeyler ters gitti! Daha fazla bilgi için lütfen günlükleri kontrol edin. - + Lütfen geçerli bir API anahtarı girin! - + Lütfen Ev Asistanınızın URI'si için bir değer girin. - + Bağlanılamadı, aşağıdaki hata döndürüldü: {0} - + Bağlantı Tamam! Ev Asistanı sürümü: {0} - + Görüntü önbelleği temizlendi! - + Temizlik.. - + Bildirimler şu anda devre dışı, lütfen bunları etkinleştirin ve HASS.Agent'ı yeniden başlatın, ardından tekrar deneyin. - + Test bildiriminin görünmesi gerekirdi, almadıysanız lütfen günlükleri kontrol edin veya sorun giderme ipuçları için belgelere bakın. Not: Bu, yalnızca yerel olarak bildirimlerin gösterilip gösterilmeyeceğini test eder! - + Bu bir test bildirimidir! - + Yürütülüyor, lütfen bekleyin.. - + Liman rezerve edilirken bir şeyler ters gitti! El ile yürütme gerekli ve panonuza bir komut kopyalandı, lütfen yükseltilmiş bir terminal açın ve komutu yapıştırın. Ayrıca Güvenlik Duvarı Kuralları bağlantı noktanızı değiştirmeyi unutmayın! - + Yüklü değil - + Engelli - + Koşma - + durduruldu - + Arızalı - + Hizmeti durdururken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmet 'devre dışı' olarak ayarlanmıştır, bu nedenle başlatılamaz. Lütfen önce hizmeti etkinleştirin ve tekrar deneyin. - + Hizmeti başlatırken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmeti devre dışı bırakırken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmeti etkinleştirirken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmeti yeniden yüklerken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Girişte Başlat devre dışı bırakılırken bir şeyler ters gitti, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Girişte Başlat devre dışı bırakılırken bir şeyler ters gitti, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Etkinleştirilmiş - + Girişte Başlamayı Devre Dışı Bırak - + Engelli - + Oturum Açıldığında Başlatmayı Etkinleştir - + Girişte Başlat etkinleştirildi! - + Girişte Başlat'ı şimdi etkinleştirmek istiyor musunuz? - + Girişte Başlat zaten etkinleştirildi, her şey hazır! - + Oturum Açılırken Başlat etkinleştiriliyor.. - + Bir şeyler yanlış gitti. Tekrar deneyebilir veya sonraki sayfaya atlayıp HASS.Agent'ın yeniden başlatılmasından sonra yeniden deneyebilirsiniz. - + Oturum Açıldığında Başlatmayı Etkinleştir - + Lütfen geçerli bir API anahtarı sağlayın. - + Lütfen Ev Asistanınızın URI'sini girin. - + Bağlanılamadı, aşağıdaki hata döndürüldü: {0} - + Bağlantı Tamam! Ev Asistanı sürümü: {0} - + Test yapmak.. - + Komutlarınız kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Uydu servisine bağlanılıyor, lütfen bekleyin.. - + Hizmete bağlanma başarısız oldu! - + Servis bulunamadı! Yapılandırma panelinden kurabilir ve yönetebilirsiniz. Çalışmaya başladığında, komutları ve sensörleri yapılandırmak için buraya geri gelin. - + Hizmetle iletişim başarısız oldu! - + Hizmetle iletişim kurulamıyor. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Yetkisiz - + Servisle iletişime geçme yetkiniz yok. Doğru auth ID'niz varsa, şimdi ayarlayabilir ve tekrar deneyebilirsiniz. - + Ayarlar getirilemedi! - + Hizmet, ayarlarını isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + MQTT ayarları getirilemedi! - + Hizmet, MQTT ayarlarını isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Yapılandırılan komutlar getirilemedi! - + Hizmet, yapılandırılmış komutlarını isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Yapılandırılmış sensörler getirilemedi! - + Hizmet, yapılandırılmış sensörlerini isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Boş bir kimlik doğrulama kimliğinin saklanması, tüm HASS.Agent'ların hizmete erişmesine izin verecektir. Bunu istediğinden emin misin? - + Kaydederken bir hata oluştu, daha fazla bilgi için günlükleri kontrol edin. - + Lütfen bir cihaz adı girin! - + Lütfen önce bir yürütücü seçin. (İpucu: Gözatmak için çift tıklayın) - + Seçilen yürütücü bulunamadı, lütfen sağlanan yolun doğru olduğundan emin olun ve tekrar deneyin. - + Bu bilgisayardaki her HASS.Agent örneğinin uydu hizmetine bağlanmasını istemiyorsanız bir kimlik doğrulama kimliği ayarlayın. Yalnızca doğru kimliğe sahip örnekler bağlanabilir. Herkesin bağlanmasına izin vermek için boş bırakın. - + Bu, uydu hizmetinin kendisini Home Assistant'a kaydettiği addır. Varsayılan olarak, bilgisayarınızın adı artı '-uydu'dur. - + Uydu hizmetinin, MQTT aracısına bağlantının koptuğunu bildirmeden önce bekleyeceği süre. - + Durum alınırken hata oluştu, lütfen bilgi için günlükleri kontrol edin. - + Yapılandırma kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Sensörler kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Tüm adımlar başarıyla tamamlanmadı. Daha fazla bilgi için lütfen günlüklere bakın. - + Tüm adımlar başarıyla tamamlanmadı. Daha fazla bilgi için lütfen günlüklere bakın. - + HASS.Agent, {0} saniye sonra hala etkin. Lütfen tüm örnekleri kapatın ve manuel olarak yeniden başlatın. Daha fazla bilgi için günlükleri kontrol edin ve isteğe bağlı olarak geliştiricileri bilgilendirin. - + Tüm adımlar başarıyla tamamlanmadı, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Uydu Hizmetini Etkinleştir - + Uydu Hizmetini Devre Dışı Bırak - + Uydu Hizmetini Başlat - + Uydu Hizmetini Durdur - + İstenen hizmet durumu işlenirken bir şeyler ters gitti. Daha fazla bilgi için lütfen günlüklere bakın. - + Konu panoya kopyalandı! - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Komutlar kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Yeni Komut - + Mod Komutu - + Lütfen bir komut türü seçin! - + Lütfen geçerli bir komut türü seçin! - + Önce geçerli bir varlık türü seçin. - + Lütfen bir isim verin! - + Bu ada sahip bir komut zaten var, devam etmek istediğinizden emin misiniz? - + Bir komut sağlanmazsa, bu varlığı Home Assistant aracılığıyla yalnızca bir 'eylem' değeriyle kullanabilirsiniz, olduğu gibi çalıştırdığınızda herhangi bir işlem yapılmaz. Devam etmek istediğinizden emin misiniz? - + Bir komut veya komut dosyası girmezseniz, bu varlığı yalnızca Home Assistant aracılığıyla bir 'eylem' değeriyle kullanabilirsiniz. Olduğu gibi çalıştırmak hiçbir şey yapmaz. Bunu istediğinden emin misin? - + Lütfen bir anahtar kodu girin! - + Anahtarlar kontrol edilemedi: {0} - + Bir URL sağlanmazsa, bu varlığı Home Assistant aracılığıyla yalnızca bir 'eylem' değeriyle kullanabilirsiniz, olduğu gibi çalıştırdığınızda herhangi bir işlem yapılmaz. Devam etmek istediğinizden emin misiniz? - + Emretmek - + Komut veya Komut Dosyası - + Anahtar kod - + Anahtar kodları - + Gizli Modda Başlat - + Tarayıcı: Varsayılan Gizli modu etkinleştirmek için lütfen özel bir tarayıcı yapılandırın. - + URL - + Tarayıcı: {0} - + Yürütücü: Yok Lütfen bir yürütücü yapılandırın, aksi takdirde komutunuz çalışmayacaktır. - + Yürütücü: {0} - + Düşük bütünlük, komutunuzun kısıtlı ayrıcalıklarla yürütüleceği anlamına gelir. - + Bu, yalnızca belirli konumlardaki dosyaları kaydedip değiştirebileceği anlamına gelir, - + '%USERPROFILE%\AppData\LocalLow' klasörü gibi veya - + 'HKEY_CURRENT_USER\Software\AppDataLow' kayıt defteri anahtarı. - + Bundan etkilenmediğinden emin olmak için komutunuzu test etmelisiniz! - + yalnızca {0}! - + MQTT yöneticisi doğru şekilde yapılandırılmamış veya başlatma işlemini henüz tamamlamamıştır. - + Eksik yapılandırma nedeniyle varlıklarınız getirilemiyor, lütfen yapılandırma ekranında gerekli değerleri girin. - + Varlıklarınız getirilmeye çalışılırken bir hata oluştu! - + Yeni Hızlı Eylem - + Mod Hızlı Eylem - + Eksik yapılandırma nedeniyle varlıklarınız getirilemiyor, lütfen yapılandırma ekranında gerekli değerleri girin. - + Varlıklarınız getirilmeye çalışılırken bir hata oluştu. - + Lütfen bir varlık seçin! - + Lütfen bir alan seçin! - + Bilinmeyen işlem, lütfen geçerli bir işlem seçin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Sensörler kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Yeni Sensör - + Mod Sensörü - + Pencere Adı - + WMI Sorgusu - + WMI Kapsamı (isteğe bağlı) - + Kategori - + Tezgah - + Örnek (isteğe bağlı) - + İşlem - + Hizmet - + Lütfen bir sensör tipi seçin! - + Lütfen geçerli bir sensör tipi seçin! - + Lütfen bir isim verin! - + Bu ada sahip tek değerli bir sensör zaten var, devam etmek istediğinizden emin misiniz? - + Bu ada sahip çok değerli bir sensör zaten var, devam etmek istediğinizden emin misiniz? - + Lütfen 1 ile 43200 (12 saat) arasında bir aralık sağlayın! - + Lütfen bir pencere adı girin! - + Lütfen bir sorgu girin! - + Lütfen bir kategori ve örnek girin! - + Lütfen bir işlemin adını girin! - + Lütfen bir hizmetin adını girin! - + yalnızca {0}! - + Cihazınızın adını değiştirdiniz. Tüm sensörleriniz ve komutlarınız artık yayından kaldırılacak ve HASS.Agent daha sonra bunları yeniden yayınlamak için yeniden başlatılacaktır. Endişelenmeyin, mevcut adlarını koruyacaklar, böylece otomasyonlarınız veya komut dosyalarınız çalışmaya devam edecek. Not: ad 'temizlenecek', bu da harfler, rakamlar ve boşluklar dışındaki her şeyin bir alt çizgi ile değiştirileceği anlamına gelir. Bu, HA tarafından gereklidir. - + Yerel API'nin bağlantı noktasını değiştirdiniz. Bu yeni limanın rezerve edilmesi gerekiyor. Bunu yapmak için bir UAC isteği alacaksınız, lütfen onaylayın. - + Bir şeyler yanlış gitti! Lütfen gerekli komutu manuel olarak yürütün. Panonuza kopyalandı, sadece yükseltilmiş bir komut istemine yapıştırmanız gerekiyor. Güvenlik duvarı kuralınızın bağlantı noktasını da değiştirmeyi unutmayın. - + Liman başarıyla rezerve edildi! HASS.Agent şimdi yeni konfigürasyonu etkinleştirmek için yeniden başlatılacaktır. - + Yeniden başlatmaya hazırlanırken bir şeyler ters gitti. Lütfen manuel olarak yeniden başlatın. - + Yapılandırmanız kaydedildi. Çoğu değişiklik, HASS.Agent'ın yürürlüğe girmeden önce yeniden başlatılmasını gerektirir. Şimdi yeniden başlatmak istiyor musunuz? - + Ayarlarınız yüklenirken bir şeyler ters gitti. 'config' alt klasöründeki appsettings.json dosyasını kontrol edin veya yeni bir başlangıç yapmak için silin. - + HASS.Agent başlatılırken bir hata oluştu. Lütfen günlükleri kontrol edin ve GitHub'da bir hata raporu oluşturun. - + Yerel ve Sensörler Fuzzy - + &Komutlar - + Kontrol etme.. - + En son sürümü çalıştırıyorsunuz: {0}{1} - + Yeni bir BETA sürümü mevcut: - + HASS.Agent BETA Güncellemesi - + Yükleyiciyi &indirmek ve başlatmak istiyor musunuz? - + Sürüm sayfasına &getmek istiyor musunuz? - + Güncellemeyi yükle - + Beta Sürümünü Yükle - + Yayın Sayfasını Aç - + Beta Sürüm Sayfasını Aç - + İstek işleniyor, lütfen bekleyin.. - + İşleme.. - + HASS.Agent İlk Katılımı: [{0}/{1}] öğesini başlatın - + HASS.Agent İlk Katılım: Başlangıç [{0}/{1}] - + HASS.Agent İlk Katılım: Bildirimler [{0}/{1}] - + HASS.Agent İlk Katılım: Entegrasyon [{0}/{1}] - + HASS.Agent İlk Katılım: API [{0}/{1}] - + HASS.Agent İlk Katılımı: MQTT [{0}/{1}] - + HASS.Agent İlk Katılım: Kısayol Tuşu [{0}/{1}] - + HASS.Agent İlk Katılım: Güncellemeler [{0}/{1}] - + HASS.Agent İlk Katılımı: Tamamlandı [{0}/{1}] - + İlk katılım sürecini iptal etmek istediğinizden emin misiniz? İlerlemeniz kaydedilmeyecek ve bir sonraki başlatmada tekrar gösterilmeyecektir. - + Bilgi alınırken hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Güncellemeyi indirmeye hazırlanamıyor, daha fazla bilgi için günlükleri kontrol edin. Bunun yerine şimdi sürüm sayfası açılacaktır. - + Güncelleme indirilemiyor, daha fazla bilgi için günlükleri kontrol edin. Bunun yerine şimdi sürüm sayfası açılacaktır. - + İndirilen dosya sertifika kontrolünde BAŞARISIZ OLDU. Bu teknik bir hata olabileceği gibi tahrif edilmiş bir dosya da olabilir! Lütfen günlükleri kontrol edin ve bulguları içeren bir bilet gönderin. - + Yükleyici başlatılamıyor (UAC istemini onayladınız mı?), daha fazla bilgi için günlükleri kontrol edin. Bunun yerine şimdi sürüm sayfası açılacaktır. - + HASS API: Bağlantı kurulumu başarısız oldu. - + HASS API: İlk bağlantı başarısız oldu. - + HASS API: Bağlantı başarısız. - + İstemci sertifika dosyası bulunamadı. - + Bağlanamıyor, URI'yi kontrol edin. - + Yapılandırma getirilemiyor, lütfen API anahtarını kontrol edin. - + Bağlanamıyor, lütfen URI'yi ve yapılandırmayı kontrol edin. - + hızlı eylem: eylem başarısız oldu, bilgi için günlükleri kontrol edin - + hızlı eylem: eylem başarısız oldu, varlık bulunamadı - + MQTT: Bağlanırken hata oluştu - + MQTT: Bağlanılamadı - + MQTT: Bağlantı kesildi - + API'yi {0} bağlantı noktasına bağlamaya çalışırken hata oluştu. HASS.Agent'ın başka hiçbir örneğinin çalışmadığından ve bağlantı noktasının kullanılabilir ve kayıtlı olduğundan emin olun. - + Geçerli etkin pencerenin başlığını sağlar. - + Cihazınızın sesinin çeşitli yönleri hakkında bilgi sağlar: Mevcut en yüksek ses seviyesi (basit bir 'bir şey çalıyor' değeri olarak kullanılabilir). Varsayılan ses aygıtı: ad, durum ve ses düzeyi. Sesli oturumlarınızın özeti: uygulama adı, sessiz durumu, ses düzeyi ve mevcut en yüksek ses düzeyi. - + Mevcut şarj durumunu, tam şarjda tahmini dakika miktarını, yüzde olarak kalan şarjı, dakika cinsinden kalan şarjı ve elektrik hattı durumunu gösteren bir sensör sağlar. - + İlk CPU'nun mevcut yükünü yüzde olarak sağlar. - + İlk CPU'nun mevcut saat hızını sağlar. - + Geçerli ses seviyesini yüzde olarak sağlar. Şu anda varsayılan cihazınızın hacmini alıyor. - + Ekran miktarını, birincil ekranın adını ve ekran başına adını, çözünürlüğünü ve piksel başına bit sayısını gösteren bir sensör sağlar. - + Test amaçlı kukla sensör, 0 ile 100 arasında rastgele bir tamsayı değeri gönderir. - + Yüzde olarak ilk GPU'nun mevcut yükünü sağlar. - + İlk GPU'nun mevcut sıcaklığını sağlar. - + Kullanıcının en son ne zaman giriş yaptığını içeren bir tarih saat değeri sağlar. İsteğe bağlı olarak, sistem yapılandırılmış zaman penceresinde uyku/hazırda bekletme durumundan uyandırıldığında ve hiçbir kullanıcı etkinliği gerçekleştirilmediğinde sensörü geçerli tarihle günceller. - + Sistemin (yeniden) başlatıldığı son anı içeren bir tarih saat değeri sağlar. Önemli: Windows'un FastBoot seçeneği bu değeri atabilir, çünkü bu bir hazırda bekletme modudur. Güç Seçenekleri -> 'Güç düğmelerinin ne yapacağını seçin' -> 'Hızlı başlatmayı aç' seçeneğinin işaretini kaldırarak devre dışı bırakabilirsiniz. SSD'li modern makineler için pek bir fark yaratmaz, ancak devre dışı bırakmak, yeniden başlattıktan sonra temiz bir durum almanızı sağlar. - + Son sistem durumu değişikliğini sağlar: ApplicationStarted, Logoff, SystemShutdown, Resume, Suspend, ConsoleConnect, ConsoleDisconnect, RemoteConnect, RemoteDisconnect, SessionLock, SessionLogoff, SessionLogon, SessionRemoteControl ve SessionUnlock. - + Şu anda oturum açmış kullanıcının adını döndürür. Fuzzy - + Şu anda oturum açmış kullanıcıların json biçimli bir listesini döndürür. Fuzzy - + Yüzde olarak kullanılan bellek miktarını sağlar. - + Mikrofonun şu anda kullanılıp kullanılmadığına bağlı olarak bir bool değeri sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - + Pencerenin o anda açık olup olmamasına bağlı olarak bir AÇIK/KAPALI değeri sağlar (etkin olması gerekmez). - + Seçilen ağ kartının/kartlarının kart bilgilerini, yapılandırmasını, aktarım ve paket istatistiklerini ve adreslerini (ip, mac, dhcp, dns) sağlar. Bu çok değerli bir sensördür. - + Bir performans sayacının değerlerini sağlar. Örneğin, yerleşik CPU yük sensörü şu değerleri kullanır: Kategori: İşlemci Sayacı: % İşlemci Zaman Örneği: _Toplam Sayaçları Windows' 'perfmon.exe' aracıyla keşfedebilirsiniz. - + İşlemin etkin örneklerinin sayısını sağlar. Fuzzy - + Sağlanan hizmetin durumunu döndürür: Bulunamadı, Durduruldu, StartPending, StopPending, Running, ContinuePending, PausePending veya Paused. 'Görünen ad' değil, 'Hizmet adı' sağladığınızdan emin olun. - + Geçerli oturum durumunu sağlar: Kilitli, Kilitli Değil veya Bilinmiyor. Oturum durumu değişikliklerini izlemek için bir LastSystemStateChangeSensor kullanın. - + Mevcut tüm çıkarılabilir olmayan disklerin etiketlerini, toplam boyutunu (MB), kullanılabilir alanı (MB), kullanılan alanı (MB) ve dosya sistemini sağlar. - + Geçerli kullanıcı durumunu sağlar: NotPresent, Busy, RunningDirect3dFullScreen, PresentationMode, Kabul Ediyor, QuietTime veya RunningWindowsStoreApp. Örneğin, bildirimlerin mi yoksa TTS mesajlarının mı gönderileceğini belirlemek için kullanılabilir. - + Web kamerasının şu anda kullanılıp kullanılmadığına bağlı olarak bir bool değeri sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - + Bekleyen sürücü güncellemelerinin miktarını gösteren bir sensör, bekleyen yazılım güncellemelerinin miktarını gösteren bir sensör, bekleyen tüm sürücü güncelleme bilgilerini (başlık, kb makale kimlikleri, gizli, tür ve kategoriler) içeren bir sensör ve bekleyenler için aynısını içeren bir sensör sağlar. yazılım güncellemeleri. Bu maliyetli bir istektir, bu nedenle önerilen aralık 15 dakikadır (900 saniye). Ancak 10 dakika ile sınırlandırılmıştır, daha düşük bir değer verirseniz, bilinen son listeyi alırsınız. - + WMI sorgusunun sonucunu sağlar. - + Ayarlar yüklenirken hata oluştu: {0} - + Başlangıç ayarları kaydedilirken hata oluştu: {0} - + Ayarlar kaydedilirken hata oluştu: {0} - + Komutlar yüklenirken hata oluştu: {0} - + Komutları depolarken hata oluştu: {0} - + Hızlı işlemler yüklenirken hata oluştu: {0} - + Hızlı işlemler kaydedilirken hata oluştu: {0} - + Sensör yüklenirken hata oluştu: {0} - + Sensörler kaydedilirken hata oluştu: {0} - + MQTT: - + Wiki - + Meşgul, lütfen bekleyin.. - + Arayüz ve Dil - + veya - + Bitiş - + Arayüz ve Dil - + Yapılandırma eksik - + bağlı - + Bağlanıyor.. - + Bağlantı kesildi - + Hata - + Gelenek - + ÖzelYürütücü - + hazırda bekletme - + Anahtar - + LaunchUrl - + Kilit - + Oturumu Kapat - + MedyaSesi Kapat - + MedyaSonraki - + MedyaOynatDuraklat - + MedyaÖnceki - + MedyaSesi Azalt - + MedyaSesi Açma - + Çoklu Tuşlar - + Güç kalkanı - + Tüm Sensörleri Yayınla - + Tekrar başlat - + Kapat - + Uyumak - + Kabul EdiyorBildirimler - + Meşgul - + Mevcut değil - + Sunum modu - + Sessiz zaman - + KoşuDirect3dFullScreen - + ÇalışanWindowsStoreApp - + KonsolBağlantısı - + Konsol Bağlantısını Kes - + HassAgentUydu HizmetiBaşladı - + HassAgentBaşladı - + Oturumu Kapat - + Uzak Bağlantı - + UzakBağlantıyı Kes - + Sürdürmek - + Oturum Kilidi - + OturumOturumu Kapatma - + OturumOturumu - + OturumUzaktan Kontrol - + Oturum Kilidini Aç - + Askıya almak - + Sistem Kapatma - + ActiveWindow - + Ses - + pil - + İşlemci Yükü - + GeçerliSaatHızı - + Geçerli Hacim - + Görüntülemek - + kukla - + GpuYük - + GpuSıcaklığı - + SonAktif - + Son Önyükleme - + LastSystemStateChange - + Kayıtlı Kullanıcı - + Kayıtlı Kullanıcılar - + Hafıza kullanımı - + MikrofonAktif - + Adlandırılmış Pencere - + - + PerformansSayacı - + SüreçAktif - + Hizmet Durumu - + Oturum Durumu - + Depolamak - + Kullanıcı Bildirimi - + Web kamerasıAktif - + Windows Güncellemeleri - + WmiQuery - + Otomasyon - + İklim - + Örtmek - + GirişBoole - + Işık - + Medya oynatıcı - + Faliyet alani, sahne - + Senaryo - + Değiştirmek - + Kapat - + Kapalı - + Açık - + Açık - + Duraklat - + Oyna - + Durmak - + Aç/Kapat - + Buton - + Işık - + Kilit - + Siren - + Değiştirmek - + Bağlanıyor.. - + Engelli - + Arızalı - + Yükleniyor.. - + Koşma - + durduruldu - + Kilitli - + Bilinmeyen - + kilidi açıldı - + Coğrafi Konum - + Herşey - + &Ölçek - + Test Performansı Sayacı - + WMI Sorgusunu Test Et - + Ağ kartı - + Önce bir kategori ve sayaç girin. - + Test başarıyla yürütüldü, sonuç değeri: {0} - + Test yürütülemedi: {0} Günlükler klasörünü açmak istiyor musunuz? - + Önce bir WMI sorgusu girin. - + Sorgu başarıyla yürütüldü, sonuç değeri: {0} - + Sorgu yürütülemedi: {0} Günlükler klasörünü açmak istiyor musunuz? - + Kapsamınız hatalı biçimlendirilmiş gibi görünüyor, muhtemelen şöyle başlamalıdır: \\.\ROOT\ Girdiğiniz kapsam: {0} İpucu: kapsamı ve sorgu alanlarını değiştirmediğinizden emin olun. Hâlâ mevcut değerleri kullanmak istiyor musunuz? - + Uygulama Başladı - + Uydu hizmetini, oturum açmak zorunda kalmadan sensörleri ve komutları çalıştırmak için kullanabilirsiniz. Tüm türler mevcut değildir, örneğin 'LaunchUrl' komutu yalnızca normal bir komut olarak eklenebilir. - + Bilinen Son Değer - + API'yi {0} bağlantı noktasına bağlamaya çalışırken hata oluştu. HASS.Agent'ın başka hiçbir örneğinin çalışmadığından ve bağlantı noktasının kullanılabilir ve kayıtlı olduğundan emin olun. - + SendWindowToFront - + Web Görünümü - + Sağlanan URL ile bir pencere gösterir. Bu, 'LaunchUrl' komutundan farklıdır, çünkü tam teşekküllü bir tarayıcı yüklemez, yalnızca kendi penceresinde sağlanan URL'yi yükler. Bunu, örneğin Home Assistant'ın kontrol panelini hızlı bir şekilde göstermek için kullanabilirsiniz. Varsayılan olarak, çerezleri süresiz olarak saklar, bu nedenle yalnızca bir kez oturum açmanız gerekir. - + HASS.Ajan Komutları - + Belirtilen işlemi arar ve ana penceresini öne göndermeye çalışır. Uygulama simge durumuna küçültülürse geri yüklenir. Örnek: VLC'yi ön plana göndermek istiyorsanız, 'vlc' kullanın. - + Komutu yapılandırmazsanız, bu varlığı yalnızca Home Assistant aracılığıyla bir 'eylem' değeriyle kullanabilirsiniz ve varsayılan ayarlar kullanılarak görünür, olduğu gibi çalıştırıldığında herhangi bir işlem yapılmaz. Bunu yapmak istediğinden emin misin? - + Ses Önbelleğini Temizle - + Temizlik.. - + Ses önbelleği temizlendi! - + Web Görünümü Önbelleğini Temizle - + Temizlik.. - + WebView önbelleği temizlendi! - + Görünüşe göre alternatif bir ölçeklendirme kullanıyorsunuz. Bu, HASS.Agent'ın bazı bölümlerinin amaçlandığı gibi görünmemesine neden olabilir. Lütfen kullanılamayan yönleri GitHub'da bildirin. Teşekkürler! Not: Bu mesaj yalnızca bir kez gösterilir. - + Depolanan komut ayarları yüklenemiyor, varsayılana sıfırlanıyor. - + Komut ve Parametreleri Yapılandır - + Bağlantı Noktası ve Rezervasyonu Yürüt - + &Yerel API'yi Etkinleştir - + HASS.Agent'ın kendi yerel API'si vardır, bu nedenle Home Assistant istek gönderebilir (örneğin bir bildirim göndermek için). Buradan global olarak yapılandırabilir ve daha sonra bağımlı bölümleri (şu anda bildirimler ve mediaplayer) yapılandırabilirsiniz. Not: Yeni entegrasyonun çalışması için bu gerekli değildir. Yalnızca MQTT kullanmıyorsanız etkinleştirin ve kullanın. - + İstekleri dinleyebilmek için HASS.Agent'ın portunun ayrılmış ve güvenlik duvarınızda açılmış olması gerekir. Bunu sizin için yaptırmak için bu düğmeyi kullanabilirsiniz. - + &Liman - + Ses Önbelleği Konumu - + günler - + Görüntü Önbelleği Konumu - + Sesi şunun için sakla: - + Resimleri şunun için sakla: - + Her seferinde önbelleği temizle - + Web Görünümü Önbellek Konumu - + Medya Oynatıcı ve Belgeler - + &Medya Oynatıcı İşlevselliğini Etkinleştir - + HASS.Agent, Home Assistant için bir medya oynatıcı görevi görebilir, böylece çalmakta olan herhangi bir medyayı görebilir ve kontrol edebilir ve metinden konuşmaya gönderebilirsiniz. MQTT'yi etkinleştirdiyseniz, cihazınız otomatik olarak eklenir. Aksi takdirde, yerel API'yi kullanmak için entegrasyonu manuel olarak yapılandırın. - + Bir şey çalışmıyorsa, aşağıdaki adımları denediğinizden emin olun: - HASS.Agent entegrasyonunu kurun - Home Assistant'ı yeniden başlatın - MQTT etkinken HASS.Agent'ın etkin olduğundan emin olun! - Cihazınız otomatik olarak bir varlık olarak algılanmalı ve eklenmelidir - İsteğe bağlı olarak: yerel API'yi kullanarak manuel olarak ekleyin - + Yerel API devre dışıdır, ancak medya oynatıcının çalışması için buna ihtiyacı vardır. - + &TLS - + Yerel API devre dışıdır, ancak medya oynatıcının çalışması için buna ihtiyacı vardır. - + Önizlemeyi Göster - + &Varsayılan Menüyü Göster - + &Web Görünümünü Göster - + &Sayfayı arka planda yüklü tut - + Sağ tıklandığında tepsi simgesinin davranışını kontrol edin. - + (Bu, ekstra kaynaklar kullanır, ancak yükleme süresini azaltır.) - + Boyut (px) - + &WebView URL'si (Örneğin, Home Assistant Dashboard URL'niz) - + Yerel API - + Medya oynatıcı - + Tepsi ikonu - + Giriş dilinizin '{0}' varsayılan CTRL-ALT-Q kısayol tuşuyla çakıştığı biliniyor. Lütfen kendinizinkini ayarlayın. - + Giriş diliniz '{0}' bilinmiyor ve varsayılan CTRL-ALT-Q kısayol tuşuyla çakışabilir. Lütfen emin olmak için kontrol edin. Varsa, listeye eklenebilmesi için GitHub'da bir bilet açmayı düşünün. - + Anahtar bulunamadı - + parantezler eksik, tüm tuşları [ ] ile başlatın ve kapatın - + Anahtarlar ayrıştırılırken hata oluştu, daha fazla bilgi için lütfen günlükleri kontrol edin. - + Açık parantezlerin sayısı ('['), kapalı parantezlerin sayısına karşılık gelmez. (']')! ({0} - {1}) - + belgeler - + Belgeler ve Kullanım Örnekleri - + Güncellemeleri kontrol et - + Yerel API: - + Uydu Hizmetini Yönet - + Bildirimleri kullanmak için Home Assistant'ta HASS.Agent-notifier entegrasyonunu kurmanız ve yapılandırmanız gerekir. Bu, HACS'ı kullanmak çok kolaydır, ancak manuel olarak da kurabilirsiniz. Daha fazla bilgi için aşağıdaki bağlantıyı ziyaret edin. - + Aşağıdaki adımları uyguladığınızdan emin olun: - HASS.Agent-Notifier ve/veya HASS.Agent-MediaPlayer entegrasyonunu kurun - Home Assistant'ı yeniden başlatın -Bir bildirim ve/veya media_player varlığı yapılandırın -Home Assistant'ı yeniden başlatın - + Aynı şey medya oynatıcı için de geçerlidir; bu entegrasyon, cihazınızı bir media_player varlığı olarak kontrol etmenize, neyin oynatıldığını görmenize ve metinden konuşmaya göndermenize olanak tanır. - + HASS.Agent-MediaPlayer GitHub Sayfası - + HASS.Agent-Entegrasyon GitHub Sayfası - + Evet, bağlantı noktasında yerel API'yi &etkinleştirin - + &Medya Oynatıcıyı ve metinden konuşmaya (TTS) etkinleştirin - + &Bildirimleri Etkinleştir - + HASS.Agent'ın kendi dahili API'si vardır, bu nedenle Home Assistant istek gönderebilir (bildirimler veya metinden konuşmaya gibi). Etkinleştirmek istiyor musunuz? - + Hangi modülleri etkinleştirmek istediğinizi seçebilirsiniz. HA entegrasyonları gerektirirler, ancak merak etmeyin, sonraki sayfa bunları nasıl kuracağınız konusunda size daha fazla bilgi verecektir. - + Not: 5115 varsayılan bağlantı noktasıdır, yalnızca Home Assistant'ta değiştirdiyseniz değiştirin. - + &TLS - + Cihaz adınız, HA tarafından izin verilmeyen bazı karakterler içeriyordu (yalnızca harfler, rakamlar ve boşluklar). Son ad: {0} Bu sürümü kullanmak istiyor musunuz? - + HASS.Agent Katılımı - + saklanmış! - + &TLS - + &Kaydetmek - + &Her zaman ekranda ortalanmış olarak göster - + Pencerenin &başlık çubuğunu göster - + Pencereyi 'Her zaman &Üstte' olarak ayarla - + Web görünümü komutunuzun boyutunu ve konumunu ayarlamak için bu pencereyi sürükleyip yeniden boyutlandırın. - + Konum - + Boyut - + İpucu: Bir Web Görünümünü kapatmak için ESCAPE'e basın. - + &URL - + Web Görünümü Yapılandırması - + Web Görünümü - + Sağladığınız anahtar kodu geçerli bir numara değil! Lütfen anahtar kodu alanının odakta olduğundan emin olun ve simüle etmek istediğiniz tuşa basın, ardından anahtar kodu sizin için oluşturulmalıdır. - + Cihaz Adını ve Sanitasyonunu Etkinleştir - + Durum Bildirimlerini Etkinleştir - + HASS.Agent, HA'nın kabul edeceğinden emin olmak için cihaz adınızı temizleyecektir, adınızın olduğu gibi kabul edileceğinden eminseniz aşağıdaki bu kuralı geçersiz kılabilirsiniz. - + HASS.Agent, bir modülün durumu değiştiğinde bildirim gönderir, bu bildirimleri almak isteyip istemediğinizi aşağıdan ayarlayabilirsiniz. - + Cihazınızın adını değiştirdiniz. Tüm sensörleriniz ve komutlarınız artık yayından kaldırılacak ve HASS.Agent yeniden başlatıldıktan sonra yeniden yayınlanacaktır. Merak etme! Otomasyonlarınız ve komut dosyalarınızın çalışmaya devam etmesi için mevcut adlarını koruyacaklar. Not: Temizliği devre dışı bıraktınız, bu nedenle cihaz adınızın Home Assistant tarafından kabul edildiğinden emin olun. - + Yazıcılar - + MonitörUyku - + MonitörUyandırma - + Açık - + Kapat - + karartılmış - + Bilinmeyen - + MonitörGüç Durumu - + PowershellSensor - + Sesi Ayarla - + Maksimize - + Küçültülmüş - + Normal - + Bilinmeyen - + Gizlenmiş - + Pencere Durumu - + Web kamerasıSüreci - + MikrofonSüreci - + Tüm monitörleri uyku (düşük güç) moduna geçirir. - + 'Yukarı ok' tuş basımını simüle ederek tüm monitörleri uyandırmaya çalışır. - + Geçerli varsayılan ses cihazının sesini belirtilen düzeye ayarlar. - + Lütfen istenen ses seviyesi olarak 0-100 arasında bir değer giriniz! - + Emretmek - + Bir hacim değeri girmezseniz, bu varlığı yalnızca Home Assistant aracılığıyla bir 'eylem' değeriyle kullanabilirsiniz. Olduğu gibi çalıştırmak hiçbir şey yapmaz. Bunu istediğinden emin misin? - + Sağladığınız ad, desteklenmeyen karakterler içeriyor ve çalışmayacak. Önerilen sürüm: {0} Bu sürümü kullanmak istiyor musunuz? - + Sağladığınız API jetonu geçerli görünmüyor, lütfen jetonun tamamını seçtiğinizden emin olun (CTRL + A kullanmayın veya çift tıklamayın). Geçerli bir API anahtarı, iki noktayla ayrılmış üç bölüm içerir. Yine de bu anahtarı kullanmak istediğinizden emin misiniz? - + Sağladığınız URI geçerli görünmüyor, geçerli bir URI aşağıdakilerden biri gibi görünebilir: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Kullanmak istediğinizden emin misiniz? yine de bu URI? - + Test yapmak.. - + hem yerel API hem de MQTT devre dışı bırakıldı, ancak entegrasyonun çalışması için en az birine ihtiyacı var - + MQTT'yi etkinleştir - + MQTT etkinleştirilmezse komutlar ve sensörler çalışmayacaktır! - + hem yerel API hem de MQTT devre dışı bırakıldı, ancak entegrasyonun çalışması için en az birine ihtiyacı var - + &Hizmeti Yönet - + Hizmet şu anda durduruldu ve yapılandırılamıyor. Lütfen yapılandırmak için önce hizmeti başlatın. - + Servisi yönetmek istiyorsanız (komut ve sensör ekleyin, ayarları değiştirin) buradan veya ana penceredeki 'uydu servisi' butonunu kullanarak yapabilirsiniz. - + Fare sol tıklamasıyla varsayılan menüyü göster - + Home Assistant API jetonunuz doğru görünmüyor. Tüm belirteci seçtiğinizden emin olun (CTRL+A kullanmayın veya çift tıklamayın). Üç bölüm içermelidir (iki nokta ile ayrılmış). Bu şekilde kullanmak istediğinizden emin misiniz? - + Ev Asistanı URI'niz doğru görünmüyor. 'http://homeassistant.local:8123' veya 'https://192.168.0.1:8123' gibi görünmelidir. Bu şekilde kullanmak istediğinizden emin misiniz? - + MQTT broker URI'niz doğru görünmüyor. 'homeassistant.local' veya '192.168.0.1' gibi görünmelidir. Bu şekilde kullanmak istediğinizden emin misiniz? - + &Kapat - + Zaten bağış yaptım, ana penceredeki düğmeyi gizle. - + HASS.Agent tamamen ücretsizdir ve her zaman kısıtlama olmaksızın bu şekilde kalacaktır! Ancak, bu aracı (ve destek ve dokümanlar gibi onu çevreleyen her şeyi) geliştirmek ve sürdürmek çok zaman alır. Çoğu geliştirici gibi, ben de kafein kullanıyorum - bu yüzden eğer onu ayırabilirseniz, bir fincan kahve her zaman çok değerlidir! - + Bağış yapmak - + URL ayarlanmadı! Lütfen web görünümünü Yapılandırma -> Tepsi Simgesi aracılığıyla yapılandırın. - + Güncellemeleri kontrol et - + Sağladığınız API jetonu geçerli görünmüyor, lütfen jetonun tamamını seçtiğinizden emin olun (CTRL + A kullanmayın veya çift tıklamayın). Geçerli bir API anahtarı, iki noktayla ayrılmış üç bölüm içerir. Yine de bu anahtarı kullanmak istediğinizden emin misiniz? - + Sağladığınız URI geçerli görünmüyor, geçerli bir URI aşağıdakilerden biri gibi görünebilir: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Kullanmak istediğinizden emin misiniz? yine de bu URI? - + Bu aracı (ve onu çevreleyen her şeyi) geliştirmek ve sürdürmek çok zaman alır. Çoğu geliştirici gibi, ben de kafein kullanıyorum - bu yüzden eğer onu ayırabilirseniz, bir fincan kahve her zaman çok değerlidir! - + İpucu: Hakkında Penceresinde başka bağış yöntemleri de mevcuttur. - + &Media Player'ı etkinleştir (metinden sese dahil) - + &Bildirimleri Etkinleştir - + MQTT'yi etkinleştir - + HASS.Agent Gönderi Güncellemesi - + Bulunan bluetooth cihazlarının miktarını gösteren bir sensör sağlar. Cihazlar ve bağlı durumları nitelik olarak eklenir. - + Bulunan bluetooth LE cihazlarının miktarını gösteren bir sensör sağlar. Cihazlar ve bağlı durumları nitelik olarak eklenir. Yalnızca son rapordan bu yana görülen cihazları gösterir, ör. sensör yayınlandığında liste temizlenir. - + Geçerli enlem, boylam ve yüksekliğinizi virgülle ayrılmış bir değer olarak döndürür. Windows'un konum hizmetlerinin etkinleştirildiğinden emin olun! Windows sürümünüze bağlı olarak bu, yeni kontrol panelinde -> 'gizlilik ve güvenlik' -> 'konum'da bulunabilir. - + Son monitör güç durumu değişikliğini sağlar: Soluk, Güç Kapalı, Güç Açık ve Bilinmiyor. - + Sağlanan Powershell komutunun veya betiğinin sonucunu döndürür. Not: Home Assistant'ın 255 karaktere kadar yükü kabul ettiğini lütfen unutmayın. Sonucu metne dönüştürür. - + Yüklü tüm yazıcılar ve sıraları hakkında bilgi sağlar. - + Şu anda web kamerasını kullanan işlemin adını sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - + İşlem penceresinin mevcut durumunu sağlar: Gizli, Büyütülmüş, Küçültülmüş, Normal ve Bilinmeyen. - + powershell komutu veya komut dosyası - + Sağladığınız ad, desteklenmeyen karakterler içeriyor ve çalışmayacak. Önerilen sürüm: {0} Bu sürümü kullanmak istiyor musunuz? - + Test Komutu/Komut Dosyası - + Lütfen bir komut veya komut dosyası girin! - + Test başarıyla yürütüldü, sonuç değeri: {0} - + Test yürütülemedi: {0} Günlükler klasörünü açmak istiyor musunuz? - + BluetoothCihazları - + BluetoothLeCihazlar - + Önemli hata, lütfen bilgi için günlükleri kontrol edin! - + Zaman aşımı süresi doldu - + bilinmeyen sebep - + Hizmet Yöneticisini açamıyor - + hizmeti açamıyor - + Başlangıç modu yapılandırılırken hata oluştu, daha fazla bilgi için lütfen günlükleri kontrol edin. - + Başlangıç modu ayarlanırken hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Microsoft'un WebView2 çalışma zamanı makinenizde bulunamadı. Bu genellikle yükleyici tarafından gerçekleştirilir, ancak manuel olarak yükleyebilirsiniz. Çalışma zamanı yükleyicisini indirmek istiyor musunuz? - + WebView başlatılırken bir şeyler ters gitti! Lütfen günlüklerinizi kontrol edin ve daha fazla yardım için bir GitHub sorunu açın. - + domain - + Lütfen kullanılabilirliğin göz ardı edilmesinin, HASS.Agent çevrimdışı olsa bile Home Assistant'ın belirli bir sensör için her zaman son değeri görüntülemesine neden olacağını unutmayın. - + Kullanılab&ilirliği yoksay - + &Treat URI Action öğeleri, Android Companion App'in yaptığı gibi (açık) - + Bildirim Giriş Metni - + Bildirim Giriş Başlığı - + Evet - + Hayır - + Hiçbiri - + Seçilen radyo cihazını açar/kapatır. Radyo cihazlarının kullanılabilirliği HASS.Agent'ın kurulu olduğu cihaza bağlıdır. - + Lütfen radyo cihazını seçin! - + Radyo cihazı - + Radyo Komutu - + Hata, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Komutlar dönüştürülürken hata oluştu! - + Çoklu değer sensörleri dönüştürülürken hata oluştu! - + Tek değerli sensörler dönüştürülürken hata oluştu! - + Varlık adlarını değiştirme Home Assistant 2023.8 gereksinimlerini karşılamak için - + Sensör adındaki cihaz adı, 2023.8'den itibaren Ev Asistanı sürümlerinde sorunlara neden olabilir. - + Eylemi zorla ne zaman sistem uykudan/hazırda bekletme modundan uyanır - + Sağlanan ses cihazında sağlanan uygulamanın ses düzeyini ve sessiz durumunu belirtilen düzeye ayarlar. Komut/yükün JSON formatında olması gerekir. Tüm olası seçeneklere örnek: { @@ -2918,106 +2918,113 @@ Gelişmiş seçenek: yalnızca uygulamanın belirli bir oturumu için ses seviye "sessionid":"<LONG SESSION ID FROM SENSOR>" } - + Lütfen geçerli bir JSON dizesi girin! - + Dahili cihaz sensöründen veri sağlar. Sensörün kullanılabilirliği cihaza bağlıdır; bazı durumlarda sensör bulunmayabilir. - + Dahili Sensör - + DahiliCihazSensörü - + Komut adındaki cihaz adı, 2023.8'den itibaren Home Assistant sürümlerinde sorunlara neden olabilir. - + Şu anda etkin olan sanal masaüstünün kimliğini sağlar. - + Sağlanan Sanal Masaüstünü etkinleştirir. Masaüstü Kimliği "ActiveDesktop" sensöründen alınabilir. - + Makinenizde Sanal Masaüstü yönetimi mevcut değil. Bunun nedeni genellikle sanal masaüstü yönetim kitaplığının Windows sürümünüzü destekleyecek şekilde güncellenmemesidir. - + HASS.Agent yapılandırmasının taşınması orijinal versiyondan - + Tarafından sevgiyle güncellendi ve sürdürüldü - + HASS.Agent'ın orijinal versiyonunu geliştirdikleri için LAB02 Research ve Sam'e daha da büyük bir 'teşekkür ederim' - bu olmasaydı bu olmazdı! - + Uydu servisiyle iletişimde hata! - + Uydu hizmeti başlatılırken hata oluştu! - + Sistem için varsayılan ses çıkışını ayarlar. Yük olarak ses cihazı adını gerektirir. - + Ses Cihazı Adı - + JSON Komut Yükü - + Hacim (0 ile 100 arasında) - + Bir kamera varlığı biçiminde bir ekran görüntüsü sensörü sağlarsınız. Ekran numarası sistem konfigürasyonuna bağlıdır - 0'dan başlar. - + Ekran numarası - + Sistem için varsayılan ses girişini ayarlar (varsayılan iletişim cihazı dahil). Yük olarak ses cihazı adına ihtiyacınız vardır. - + Hazırda bekletme modundan uyandıktan sonra ek süreyi göz ardı et - + Modern tepsi simgesini kullan - + Halihazırda mikrofonu kullanan işlemlerin sayısını sağlar; ayrıca sensörün özniteliklerinde süreçlerin adını da sağlar. Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılamaz. - + NFC - + Gelişmiş Ayarlar - + Ge&lişmiş Ayarlar - + Uyarı, aşağıdaki ayarların (geçersiz kılmaların) eklenmesi sensörü bozabilir, dikkatli kullanın! - + Tepsi Simgesi Web Görünümünü gösterir/gizler. "Yapılandırma -> Tepsi Simgesi"nde yapılandırılması gerekir - + Tetiklemek - + Buton - + Basmak + + + Mevcut enlem, boylam ve yüksekliğinizi konum/cihaz izleyici olarak döndürür, doğrudan bir harita üzerinde görüntülemek ve izlemek için. + +Windows'un konum servislerinin etkinleştirildiğinden emin olun! + +Windows sürümünüze bağlı olarak, bu, 'gizlilik ve güvenlik' > 'konum' > yeni kontrol panelinde bulunabilir. \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs index 1d8f2848..9cc9996a 100644 --- a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs @@ -466,6 +466,13 @@ internal static void LoadSensorInfo() Languages.SensorsManager_GeoLocationSensorDescription, 30, false, true, false); + SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); + // ================================= + + sensorInfoCard = new SensorInfoCard(SensorType.DeviceTrackerSensor, + Languages.SensorsManager_DeviceTrackerSensorDescription, + 30, false, true, false); + SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); // ================================= diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs index 57c52ed9..fc0a007c 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs @@ -175,6 +175,9 @@ internal static AbstractSingleValueSensor ConvertConfiguredToAbstractSingleValue case SensorType.GeoLocationSensor: abstractSensor = new GeoLocationSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; + case SensorType.DeviceTrackerSensor: + abstractSensor = new DeviceTrackerSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); + break; case SensorType.MonitorPowerStateSensor: abstractSensor = new MonitorPowerStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break;