From 2a7ad4f017d4d7b32164de0393a97c971dfb7664 Mon Sep 17 00:00:00 2001 From: Bluscream Date: Sat, 21 Mar 2026 22:45:27 +0100 Subject: [PATCH 1/2] fix(satellite): check for null after sensor/command conversion. This avoids a NullReferenceException when the Satellite Service encounters unsupported entity types (e.g. WebViewCommand, Bluetooth sensors) that aren't available in the headless service context. --- .../Settings/StoredCommands.cs | 7 +++++-- .../Settings/StoredSensors.cs | 14 +++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs index 0673e775..de838048 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs @@ -1,4 +1,4 @@ -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Models.Config; using HASS.Agent.Shared.HomeAssistant.Commands; using HASS.Agent.Shared.HomeAssistant.Commands.CustomCommands; @@ -56,7 +56,10 @@ internal static async Task LoadAsync() // convert to abstract commands await Task.Run(delegate { - foreach (var abstractCommand in configuredCommands.Select(ConvertConfiguredToAbstract)) Variables.Commands.Add(abstractCommand!); + foreach (var abstractCommand in configuredCommands.Select(ConvertConfiguredToAbstract)) + { + if (abstractCommand != null) Variables.Commands.Add(abstractCommand); + } }); // all good 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 ea81e57a..556c8086 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs @@ -1,4 +1,4 @@ -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Models.Config; using HASS.Agent.Satellite.Service.Extensions; using HASS.Agent.Shared.HomeAssistant.Sensors; @@ -61,8 +61,16 @@ await Task.Run(delegate { foreach (var sensor in configuredSensors) { - if (sensor.IsSingleValue()) Variables.SingleValueSensors.Add(ConvertConfiguredToAbstractSingleValue(sensor)!); - else Variables.MultiValueSensors.Add(ConvertConfiguredToAbstractMultiValue(sensor)!); + if (sensor.IsSingleValue()) + { + var abstractSensor = ConvertConfiguredToAbstractSingleValue(sensor); + if (abstractSensor != null) Variables.SingleValueSensors.Add(abstractSensor); + } + else + { + var abstractSensor = ConvertConfiguredToAbstractMultiValue(sensor); + if (abstractSensor != null) Variables.MultiValueSensors.Add(abstractSensor); + } } }); From 2be30a397fc859e5a3cd0c4f4c874bc09aa867d0 Mon Sep 17 00:00:00 2001 From: Bluscream Date: Sat, 21 Mar 2026 22:56:20 +0100 Subject: [PATCH 2/2] feat(satellite): prioritize service-specific config files. The Satellite Service will now try to load 'servicecommands.json' and 'servicesensors.json' before falling back to the standard 'commands.json' and 'sensors.json' files. This allows for service-specific configurations without affecting the global HASS.Agent UI-managed configs. --- .../Settings/StoredCommands.cs | 10 +++++++--- .../Settings/StoredSensors.cs | 10 +++++++--- .../HASS.Agent.Satellite.Service/Variables.cs | 6 +++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs index de838048..078794dc 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs @@ -28,7 +28,11 @@ internal static async Task LoadAsync() Variables.Commands = new List(); // check for existing file - if (!File.Exists(Variables.CommandsFile)) + Variables.LoadedCommandsFile = File.Exists(Variables.ServiceCommandsFile) + ? Variables.ServiceCommandsFile + : Variables.CommandsFile; + + if (!File.Exists(Variables.LoadedCommandsFile)) { // none yet Log.Information("[SETTINGS_COMMANDS] Config not found, no entities loaded"); @@ -36,7 +40,7 @@ internal static async Task LoadAsync() } // read the content - var commandsRaw = await File.ReadAllTextAsync(Variables.CommandsFile); + var commandsRaw = await File.ReadAllTextAsync(Variables.LoadedCommandsFile); if (string.IsNullOrWhiteSpace(commandsRaw)) { Log.Information("[SETTINGS_COMMANDS] Config is empty, no entities loaded"); @@ -265,7 +269,7 @@ internal static bool Store() // serialize to file var commands = JsonConvert.SerializeObject(configuredCommands, Formatting.Indented); - File.WriteAllText(Variables.CommandsFile, commands); + File.WriteAllText(Variables.LoadedCommandsFile ?? Variables.CommandsFile, commands); // done Log.Information("[SETTINGS_COMMANDS] Stored {count} entities", Variables.Commands.Count); 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 556c8086..013575cd 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs @@ -31,7 +31,11 @@ internal static async Task LoadAsync() Variables.MultiValueSensors = new List(); // check for existing file - if (!File.Exists(Variables.SensorsFile)) + Variables.LoadedSensorsFile = File.Exists(Variables.ServiceSensorsFile) + ? Variables.ServiceSensorsFile + : Variables.SensorsFile; + + if (!File.Exists(Variables.LoadedSensorsFile)) { // none yet Log.Information("[SETTINGS_SENSORS] Config not found, no entities loaded"); @@ -39,7 +43,7 @@ internal static async Task LoadAsync() } // read the content - var sensorsRaw = await File.ReadAllTextAsync(Variables.SensorsFile); + var sensorsRaw = await File.ReadAllTextAsync(Variables.LoadedSensorsFile); if (string.IsNullOrWhiteSpace(sensorsRaw)) { Log.Information("[SETTINGS_SENSORS] Config is empty, no entities loaded"); @@ -504,7 +508,7 @@ internal static bool Store() // serialize to file var sensors = JsonConvert.SerializeObject(configuredSensors, Formatting.Indented); - File.WriteAllText(Variables.SensorsFile, sensors); + File.WriteAllText(Variables.LoadedSensorsFile ?? Variables.SensorsFile, sensors); // done Log.Information("[SETTINGS_SENSORS] Stored {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Variables.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Variables.cs index 645f4100..8822352b 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Variables.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Variables.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using GrpcDotNetNamedPipes; using HASS.Agent.Shared.Models.Config.Service; using HASS.Agent.Shared.Models.HomeAssistant; @@ -45,7 +45,9 @@ internal static class Variables internal static string ServiceSettingsFile { get; } = Path.Combine(ConfigPath, "servicesettings.json"); internal static string ServiceMqttSettingsFile { get; } = Path.Combine(ConfigPath, "servicemqttsettings.json"); internal static string CommandsFile { get; } = Path.Combine(ConfigPath, "commands.json"); + internal static string ServiceCommandsFile { get; } = Path.Combine(ConfigPath, "servicecommands.json"); internal static string SensorsFile { get; } = Path.Combine(ConfigPath, "sensors.json"); + internal static string ServiceSensorsFile { get; } = Path.Combine(ConfigPath, "servicesensors.json"); /// /// Internal state @@ -67,7 +69,9 @@ internal static class Variables internal static ServiceSettings? ServiceSettings { get; set; } = new(); internal static ServiceMqttSettings? ServiceMqttSettings { get; set; } = new(); internal static List Commands { get; set; } = new(); + internal static string LoadedCommandsFile { get; set; } = string.Empty; internal static List SingleValueSensors { get; set; } = new(); internal static List MultiValueSensors { get; set; } = new(); + internal static string LoadedSensorsFile { get; set; } = string.Empty; } }