From 389b85c27d80434f3dd38cc6fa4971430d981936 Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 19 Dec 2025 11:23:13 +0100 Subject: [PATCH 01/18] Initial work on light automation pipeline. --- CodeCasa.sln | 6 + ...CodeCasa.AutomationPipelines.Lights.csproj | 15 ++ .../CompositeAsyncDisposable.cs | 43 +++ .../CompositeHelper.cs | 69 +++++ .../Context/ILightPipelineContext.cs | 9 + .../Context/LightPipelineContext.cs | 15 ++ .../Context/LightPipelineContextProvider.cs | 22 ++ ...mpositeLightTransitionCycleConfigurator.cs | 120 +++++++++ .../ILightTransitionCycleConfigurator.cs | 140 ++++++++++ .../Cycle/LightTransitionCycleConfigurator.cs | 91 +++++++ .../DimmerOptions.cs | 17 ++ .../ExcludedLightBehaviours.cs | 14 + .../Extensions/DimmerOptionsExtensions.cs | 45 ++++ .../Extensions/EnumerableExtensions.cs | 13 + .../LightTransitionNodeExtensions.cs | 35 +++ .../Extensions/ObservableExtensions.cs | 75 ++++++ .../Extensions/SchedulerExtensions.cs | 65 +++++ .../Extensions/ServiceCollectionExtensions.cs | 19 ++ .../Extensions/ServiceProviderExtensions.cs | 26 ++ .../Nodes/CompositeDimmer.cs | 29 ++ .../Nodes/DimHelper.cs | 119 ++++++++ .../Nodes/FactoryNode.cs | 10 + .../Nodes/LightTransitionNode.cs | 151 +++++++++++ .../Nodes/PassThroughNode.cs | 9 + .../Nodes/ResettableTimeoutNode.cs | 35 +++ .../Nodes/ScopedNode.cs | 43 +++ .../Nodes/StaticLightTransitionNode.cs | 12 + .../Nodes/TurnOffThenPassThroughNode.cs | 17 ++ ...tionPipelineConfigurator.Reactive.Cycle.cs | 34 +++ ...ionPipelineConfigurator.Reactive.Toggle.cs | 50 ++++ ...htTransitionPipelineConfigurator.Switch.cs | 140 ++++++++++ ...ightTransitionPipelineConfigurator.When.cs | 152 +++++++++++ ...siteLightTransitionPipelineConfigurator.cs | 99 +++++++ ...tionPipelineConfigurator.Reactive.Cycle.cs | 62 +++++ ...ionPipelineConfigurator.Reactive.Toggle.cs | 85 ++++++ ...htTransitionPipelineConfigurator.Switch.cs | 254 ++++++++++++++++++ ...ightTransitionPipelineConfigurator.When.cs | 234 ++++++++++++++++ .../ILightTransitionPipelineConfigurator.cs | 91 +++++++ .../Pipeline/LightPipelineFactory.cs | 61 +++++ ...tionPipelineConfigurator.Reactive.Cycle.cs | 34 +++ ...ionPipelineConfigurator.Reactive.Toggle.cs | 50 ++++ ...htTransitionPipelineConfigurator.Switch.cs | 128 +++++++++ ...ightTransitionPipelineConfigurator.When.cs | 135 ++++++++++ .../LightTransitionPipelineConfigurator.cs | 92 +++++++ ...ransitionReactiveNodeConfigurator.Cycle.cs | 58 ++++ ...htTransitionReactiveNodeConfigurator.On.cs | 71 +++++ ...ansitionReactiveNodeConfigurator.Toggle.cs | 79 ++++++ ...LightTransitionReactiveNodeConfigurator.cs | 101 +++++++ ...ransitionReactiveNodeConfigurator.Cycle.cs | 67 +++++ ...htTransitionReactiveNodeConfigurator.On.cs | 114 ++++++++ ...ansitionReactiveNodeConfigurator.Toggle.cs | 85 ++++++ ...LightTransitionReactiveNodeConfigurator.cs | 95 +++++++ ...ransitionReactiveNodeConfigurator.Cycle.cs | 57 ++++ ...htTransitionReactiveNodeConfigurator.On.cs | 59 ++++ ...ansitionReactiveNodeConfigurator.Toggle.cs | 77 ++++++ ...LightTransitionReactiveNodeConfigurator.cs | 106 ++++++++ .../ReactiveNode/ReactiveDimmerNode.cs | 194 +++++++++++++ .../ReactiveNode/ReactiveNode.cs | 113 ++++++++ .../ReactiveNode/ReactiveNodeFactory.cs | 226 ++++++++++++++++ ...positeLightTransitionToggleConfigurator.cs | 138 ++++++++++ .../ILightTransitionToggleConfigurator.cs | 145 ++++++++++ .../LightTransitionToggleConfigurator.cs | 113 ++++++++ 62 files changed, 4863 insertions(+) create mode 100644 src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj create mode 100644 src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/SchedulerExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Cycle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Toggle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Cycle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Toggle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs diff --git a/CodeCasa.sln b/CodeCasa.sln index 571957a..4836c87 100644 --- a/CodeCasa.sln +++ b/CodeCasa.sln @@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipeline EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights.NetDaemon", "src\CodeCasa.Lights.NetDaemon\CodeCasa.Lights.NetDaemon.csproj", "{FA32051C-0DFF-4FDE-9D9D-60F4ABA4D1A8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines.Lights", "src\CodeCasa.AutomationPipelines.Lights\CodeCasa.AutomationPipelines.Lights.csproj", "{A66370F1-3944-4EAA-82EF-6A321BF273D1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {FA32051C-0DFF-4FDE-9D9D-60F4ABA4D1A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA32051C-0DFF-4FDE-9D9D-60F4ABA4D1A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA32051C-0DFF-4FDE-9D9D-60F4ABA4D1A8}.Release|Any CPU.Build.0 = Release|Any CPU + {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj b/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj new file mode 100644 index 0000000..157c5bb --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + enable + enable + + + + + + + + + diff --git a/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs b/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs new file mode 100644 index 0000000..26ae2a7 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs @@ -0,0 +1,43 @@ + +namespace CodeCasa.AutomationPipelines.Lights +{ + public sealed class CompositeAsyncDisposable : IAsyncDisposable + { + private readonly List _asyncDisposables = new(); + private readonly List _disposables = new(); + private bool _disposed; + + public void Add(IAsyncDisposable asyncDisposable) + { + _asyncDisposables.Add(asyncDisposable); + } + + public void Add(IDisposable disposable) + { + _disposables.Add(disposable); + } + + public void AddRange(IEnumerable disposables) + { + _disposables.AddRange(disposables); + } + + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + _disposed = true; + + foreach (var d in _asyncDisposables) + { + await d.DisposeAsync(); + } + foreach (var d in _disposables) + { + d.Dispose(); + } + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs b/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs new file mode 100644 index 0000000..cbea9f4 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs @@ -0,0 +1,69 @@ +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; + +namespace CodeCasa.AutomationPipelines.Lights +{ + internal static class CompositeHelper + { + // todo: check and filter without errors. + public static string[] ResolveAndValidateLightEntities(IEnumerable lightEntityIds, IEnumerable supportedLightIds) + { + var supportedLightIdsArray = supportedLightIds.ToArray(); + var lightLeafs = lightEntityIds.SelectMany(le => le.Flatten()).DistinctBy(l => l.Id).ToArray(); + if (!lightLeafs.Any()) + { + throw new ArgumentException("At least one entity id should be provided.", nameof(lightLeafs)); + } + + var missingLights = lightLeafs + .Where(id => !supportedLightIdsArray.Contains(id.Id)) + .ToArray(); + + if (missingLights.Any()) + { + throw new InvalidOperationException( + $"The following light entities are not supported: {string.Join(", ", missingLights)}."); + } + + return lightLeafs.Select(l => l.Id).ToArray(); + } + + public static void ValidateLightEntities(IEnumerable lights, string supportedLightId) + { + var lightLeafs = lights.SelectMany(le => le.Flatten()).DistinctBy(l => l.Id).ToArray(); + if (!lightLeafs.Any()) + { + throw new ArgumentException("At least one entity id should be provided.", nameof(lightLeafs)); + } + + var missingLights = lightLeafs + .Where(l => l.Id != supportedLightId) + .ToArray(); + + if (missingLights.Any()) + { + throw new InvalidOperationException( + $"The following light entities are not supported: {string.Join(", ", missingLights)}."); + } + } + + public static void ValidateLightEntities(IEnumerable lights, string supportedLightId) + { + var lightsArray = lights.ToArray(); + if (!lightsArray.Any()) + { + throw new ArgumentException("At least one entity id should be provided.", nameof(lightsArray)); + } + + var missingLights = lightsArray + .Where(id => id != supportedLightId) + .ToArray(); + + if (missingLights.Any()) + { + throw new InvalidOperationException( + $"The following light entities are not supported: {string.Join(", ", missingLights)}."); + } + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs new file mode 100644 index 0000000..a58e30e --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs @@ -0,0 +1,9 @@ +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Context; + +public interface ILightPipelineContext +{ + IServiceProvider ServiceProvider { get; } + ILight LightEntity { get; } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs new file mode 100644 index 0000000..2f90efa --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs @@ -0,0 +1,15 @@ +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Context; + +public class LightPipelineContext : ILightPipelineContext +{ + internal LightPipelineContext(IServiceProvider serviceProvider, ILight lightEntity) + { + ServiceProvider = serviceProvider; + LightEntity = lightEntity; + } + + public IServiceProvider ServiceProvider { get; } + public ILight LightEntity { get; } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs new file mode 100644 index 0000000..5e0d95e --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs @@ -0,0 +1,22 @@ +namespace CodeCasa.AutomationPipelines.Lights.Context +{ + internal class LightPipelineContextProvider + { + private ILightPipelineContext? _lightPipelineContext; + + public ILightPipelineContext GetLightPipelineContext() + { + return _lightPipelineContext ?? throw new InvalidOperationException("Current context not set."); + } + + public void SetLightPipelineContext(ILightPipelineContext context) + { + _lightPipelineContext = context; + } + + public void ResetLightEntity() + { + _lightPipelineContext = null; + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs new file mode 100644 index 0000000..59758f7 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs @@ -0,0 +1,120 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Cycle; + +internal class CompositeLightTransitionCycleConfigurator( + Dictionary activeConfigurators, + Dictionary inactiveConfigurators) + : ILightTransitionCycleConfigurator +{ + public ILightTransitionCycleConfigurator AddOff() + { + var matchesNodeState = () => activeConfigurators.Values.All(c => c.LightEntity.IsOff()); + activeConfigurators.Values.ForEach(c => c.Add(_ => matchesNodeState())); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough(_ => matchesNodeState())); + return this; + } + + public ILightTransitionCycleConfigurator AddOn() + { + return Add(LightTransition.On()); + } + + public ILightTransitionCycleConfigurator Add(LightParameters lightParameters) + { + return Add(lightParameters.AsTransition()); + } + + public ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState) + { + return Add(c => lightParametersFactory(c)?.AsTransition(), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState) + { + return Add((c, t) => lightParametersFactory(c, t)?.AsTransition(), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(LightTransition lightTransition) + { + return Add(_ => lightTransition, _ => activeConfigurators.Values.All(c => c.LightEntity.SceneEquals(lightTransition.LightParameters))); + } + + public ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState) + { + return Add(c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService()), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState) + { + return Add(c => new FactoryNode(t => lightTransitionFactory(c, t)), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func matchesNodeState) where TNode : IPipelineNode + { + activeConfigurators.Values.ForEach(c => c.Add(matchesNodeState)); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough(matchesNodeState)); + return this; + } + + public ILightTransitionCycleConfigurator Add(Func> nodeFactory, Func matchesNodeState) + { + activeConfigurators.Values.ForEach(c => c.Add(nodeFactory, matchesNodeState)); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough(matchesNodeState)); + return this; + } + + public ILightTransitionCycleConfigurator AddPassThrough(Func matchesNodeState) + { + activeConfigurators.Values.ForEach(c => c.AddPassThrough(matchesNodeState)); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough(matchesNodeState)); + return this; + } + + public ILightTransitionCycleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + + public ILightTransitionCycleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntityIds, + Action configure, + ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + var lightEntityIdsArray = + CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, activeConfigurators.Keys); + + if (lightEntityIdsArray.Length == activeConfigurators.Count) + { + configure(this); + return this; + } + + if (excludedLightBehaviour == ExcludedLightBehaviours.None) + { + if (lightEntityIdsArray.Length == 1) + { + configure(activeConfigurators[lightEntityIdsArray.First()]); + return this; + } + + configure(new CompositeLightTransitionCycleConfigurator( + activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), [])); + return this; + } + + configure(new CompositeLightTransitionCycleConfigurator( + activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + activeConfigurators.Where(kvp => !lightEntityIdsArray.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); + return this; + } + + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.Id), configure, excludedLightBehaviour); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs new file mode 100644 index 0000000..b135e08 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs @@ -0,0 +1,140 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Cycle +{ + /// + /// Configurator for state-based cycle behavior. Cycles advance based on the current light state: + /// if the light matches a state in the cycle, it advances to the next state. + /// If the current state is not recognized, the cycle starts from the beginning. + /// + public interface ILightTransitionCycleConfigurator + { + /// + /// Adds an "off" state to the cycle. + /// + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator AddOff(); + + /// + /// Adds an "on" state to the cycle. + /// + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator AddOn(); + + /// + /// Adds light parameters to the cycle. The cycle will advance to these parameters when the current state matches the previous entry in the cycle. + /// + /// The light parameters to add to the cycle. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(LightParameters lightParameters); + + /// + /// Adds light parameters created by a factory to the cycle, with a custom state matching function. + /// The function determines if the current light state matches this cycle entry. + /// + /// A factory function that creates light parameters based on the pipeline context. + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState); + + /// + /// Adds light parameters created by a factory to the cycle, with a custom state matching function. + /// The factory receives both the pipeline context and the current light transition. + /// The function determines if the current light state matches this cycle entry. + /// + /// A factory function that creates light parameters based on the pipeline context and current transition. + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState); + + /// + /// Adds a light transition to the cycle. The cycle will advance to this transition when the current state matches the previous entry in the cycle. + /// + /// The light transition to add to the cycle. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(LightTransition lightTransition); + + /// + /// Adds a light transition created by a factory to the cycle, with a custom state matching function. + /// The function determines if the current light state matches this cycle entry. + /// + /// A factory function that creates a light transition based on the pipeline context. + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState); + + /// + /// Adds a light transition created by a factory to the cycle, with a custom state matching function. + /// The factory receives both the pipeline context and the current light transition. + /// The function determines if the current light state matches this cycle entry. + /// + /// A factory function that creates a light transition based on the pipeline context and current transition. + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState); + + /// + /// Adds a pipeline node of type to the cycle, with a custom state matching function. + /// The node is resolved from the service provider. + /// The function determines if the current light state matches this cycle entry. + /// + /// The type of the pipeline node to add to the cycle. + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(Func matchesNodeState) where TNode : IPipelineNode; + + /// + /// Adds a pipeline node created by a factory to the cycle, with a custom state matching function. + /// The function determines if the current light state matches this cycle entry. + /// + /// A factory function that creates a pipeline node based on the pipeline context. + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator Add(Func> nodeFactory, Func matchesNodeState); + + /// + /// Adds a pass-through state to the cycle that maintains the current light state. + /// The function determines if the current light state matches this cycle entry. + /// + /// A function that determines if the current state matches this cycle entry. + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator AddPassThrough(Func matchesNodeState); + + /// + /// Creates a scoped cycle configuration for a specific light entity identified by its entity ID. + /// + /// The entity ID of the light to configure. + /// An action to configure the cycle for this specific light. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + + /// + /// Creates a scoped cycle configuration for a specific light entity. + /// + /// The light entity to configure. + /// An action to configure the cycle for this specific light. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + + /// + /// Creates a scoped cycle configuration for multiple light entities identified by their entity IDs. + /// + /// The entity IDs of the lights to configure. + /// An action to configure the cycle for these lights. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + + /// + /// Creates a scoped cycle configuration for multiple light entities. + /// + /// The light entities to configure. + /// An action to configure the cycle for these lights. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs new file mode 100644 index 0000000..e8055e2 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs @@ -0,0 +1,91 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; + +namespace CodeCasa.AutomationPipelines.Lights.Cycle; + +internal class LightTransitionCycleConfigurator(ILight lightEntity, IScheduler scheduler) : ILightTransitionCycleConfigurator +{ + public ILight LightEntity { get; } = lightEntity; + + internal List<(Func> nodeFactory, Func matchesNodeState)> CycleNodeFactories + { + get; + } = []; + + public ILightTransitionCycleConfigurator AddOff() + { + return Add(_ => LightEntity.IsOff()); + } + + public ILightTransitionCycleConfigurator AddOn() + { + return Add(LightTransition.On()); + } + + public ILightTransitionCycleConfigurator Add(LightParameters lightParameters) + { + return Add(lightParameters.AsTransition()); + } + + public ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState) + { + return Add(c => lightParametersFactory(c)?.AsTransition(), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState) + { + return Add((c, t) => lightParametersFactory(c, t)?.AsTransition(), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(LightTransition lightTransition) + { + return Add(new StaticLightTransitionNode(lightTransition, scheduler), _ => LightEntity.SceneEquals(lightTransition.LightParameters)); + } + + public ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState) + { + return Add(c => new StaticLightTransitionNode(lightTransitionFactory(c), scheduler), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState) + { + return Add(c => new FactoryNode(t => lightTransitionFactory(c, t)), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func matchesNodeState) where TNode : IPipelineNode + { + return Add(c => c.ServiceProvider.CreateInstanceWithinContext(c), matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(IPipelineNode node, Func matchesNodeState) + { + return Add(_ => node, matchesNodeState); + } + + public ILightTransitionCycleConfigurator Add(Func> nodeFactory, Func matchesNodeState) + { + CycleNodeFactories.Add((nodeFactory, matchesNodeState)); + return this; + } + + public ILightTransitionCycleConfigurator AddPassThrough(Func matchesNodeState) + { + return Add(new PassThroughNode(), matchesNodeState); + } + + public ILightTransitionCycleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + + public ILightTransitionCycleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + CompositeHelper.ValidateLightEntities(lightEntityIds, LightEntity.Id); + return this; + } + + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.EntityId), configure, excludedLightBehaviour); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs b/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs new file mode 100644 index 0000000..9ecec2c --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs @@ -0,0 +1,17 @@ +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights; + +public record DimmerOptions +{ + public int MinBrightness { get; set; } = 2; + public int BrightnessStep { get; set; } = 51; + public TimeSpan TimeBetweenSteps { get; set; } = TimeSpan.FromMilliseconds(500); + public IEnumerable? DimOrderLightEntities { get; set; } + + public void SetLightOrder(IEnumerable lightEntities) => + DimOrderLightEntities = lightEntities.Select(l => l.Id).ToArray(); + + public void SetLightOrder(params ILight[] lightEntities) => + DimOrderLightEntities = lightEntities.Select(l => l.Id).ToArray(); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs b/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs new file mode 100644 index 0000000..8a7cd89 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs @@ -0,0 +1,14 @@ +namespace CodeCasa.AutomationPipelines.Lights; + +public enum ExcludedLightBehaviours +{ + /// + /// No behavior will be defined for excluded lights. + /// Either add behavior specifically, or they will be out of sync when toggling or cycling through behaviors. + /// + None, + /// + /// A simple pass-through node is added for excluded lights to ensure they stay in sync when toggling or cycling through behaviors. + /// + PassThrough +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs new file mode 100644 index 0000000..55deb5e --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs @@ -0,0 +1,45 @@ +namespace CodeCasa.AutomationPipelines.Lights.Extensions +{ + internal static class DimmerOptionsExtensions + { + public static void ValidateSingleLightEntity(this DimmerOptions dimmerOptions, string lightEntityId) + { + var dimOrderLightEntitiesArray = dimmerOptions.DimOrderLightEntities?.ToArray(); + if (dimOrderLightEntitiesArray != null && dimOrderLightEntitiesArray.Any()) + { + var extraEntities = dimOrderLightEntitiesArray.Where(l => l != lightEntityId).ToArray(); + if (extraEntities.Any()) + { + throw new InvalidOperationException( + $"Builder only supports entity {lightEntityId}. Please remove extra entities {string.Join(", ", extraEntities)}."); + } + } + } + + public static OrderedDictionary ValidateAndOrderMultipleLightEntityTypes(this DimmerOptions dimmerOptions, Dictionary typesByLightEntityIds) + { + var dimOrderLightEntitiesArray = dimmerOptions.DimOrderLightEntities?.ToArray(); + if (dimOrderLightEntitiesArray != null && dimOrderLightEntitiesArray.Any()) + { + var missingEntities = typesByLightEntityIds.Keys.Except(dimOrderLightEntitiesArray).ToArray(); + if (missingEntities.Any()) + { + throw new InvalidOperationException( + $"When providing dim order, all entities should be provided. The following entities are missing: {string.Join(", ", missingEntities)}. Make sure to provide low level entities."); + } + + var extraEntities = dimOrderLightEntitiesArray.Except(typesByLightEntityIds.Keys).ToArray(); + if (extraEntities.Any()) + { + throw new InvalidOperationException( + $"Pipeline does not contain the following entities: {string.Join(", ", extraEntities)}. Make sure to provide low level entities."); + } + + return new OrderedDictionary(dimOrderLightEntitiesArray + .Select(e => new KeyValuePair(e, typesByLightEntityIds[e]))); + } + + return new OrderedDictionary(typesByLightEntityIds); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..694a6ec --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs @@ -0,0 +1,13 @@ +namespace CodeCasa.AutomationPipelines.Lights.Extensions +{ + public static class EnumerableExtensions + { + public static void ForEach(this IEnumerable source, Action action) + { + foreach (var item in source) + { + action(item); + } + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs new file mode 100644 index 0000000..6917073 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs @@ -0,0 +1,35 @@ +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Extensions +{ + public static class LightTransitionNodeExtensions + { + public static IPipelineNode LightSceneThatTurnsOffAfter(this ILightPipelineContext context, + LightSceneTemplate lightSceneTemplate, + TimeSpan timeSpan, IObservable resetTimerObservable) + { + // todo: move to diferent project + var scheduler = context.ServiceProvider.GetRequiredService(); + var innerNode = new StaticLightTransitionNode(lightSceneTemplate(context.LightEntity).AsTransition(), scheduler); + return innerNode.TurnOffAfter(timeSpan, resetTimerObservable, scheduler); + } + + public static IPipelineNode TurnOffAfter(this IPipelineNode node, + TimeSpan timeSpan, IScheduler scheduler) + { + return new ResettableTimeoutNode(node, timeSpan, Observable.Empty(), scheduler); + } + + public static IPipelineNode TurnOffAfter(this IPipelineNode node, + TimeSpan timeSpan, IObservable resetTimerObservable, IScheduler scheduler) + { + return new ResettableTimeoutNode(node, timeSpan, resetTimerObservable, scheduler); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs new file mode 100644 index 0000000..71d9081 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs @@ -0,0 +1,75 @@ +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +namespace CodeCasa.AutomationPipelines.Lights.Extensions +{ + public static class ObservableExtensions + { + public static IObservable ToPulsesWhenTrue(this IObservable source, TimeSpan timeBetweenPulses, IScheduler scheduler) + { + return source + .Select(b => + b + ? Observable.Timer(TimeSpan.Zero, timeBetweenPulses, scheduler).Select(_ => Unit.Default) + : Observable.Empty()) + .Switch(); + } + + public static IObservable ToCycleObservable( + this IObservable triggerObservable, + IEnumerable<(Func valueFactory, Func valueIsActiveFunc)> cycleValues) + { + var cycleValuesList = cycleValues.ToList(); + return triggerObservable.Select(_ => + { + var index = cycleValuesList.FindIndex(n => n.valueIsActiveFunc()) + 1; + if (index >= cycleValuesList.Count) + { + index = 0; + } + + return cycleValuesList[index].valueFactory(); + }); + } + + public static IObservable ToToggleObservable( + this IObservable triggerObservable, + Func offCondition, + Func offValueFactory, + IEnumerable> valueFactories, + TimeSpan timeout, + bool? includeOff) + { + var valueFactoryArray = valueFactories.ToArray(); + var includeOffBool = includeOff ?? valueFactoryArray.Length <= 1; + if (!includeOffBool && valueFactoryArray.Length <= 1) + { + throw new InvalidOperationException("When only supplying one factory, off should be included."); + } + DateTime? previousLastChanged = null; + var index = 0; + var maxIndexValue = includeOffBool ? valueFactoryArray.Length : valueFactoryArray.Length - 1; + return triggerObservable + .Select(_ => + { + var utcNow = DateTime.UtcNow; + var consecutive = previousLastChanged != null && utcNow - previousLastChanged < timeout; + previousLastChanged = utcNow; + + if (!consecutive) + { + index = 0; + if (offCondition()) + { + return offValueFactory(); + } + } + + var value = index >= valueFactoryArray.Length ? offValueFactory() : valueFactoryArray[index](); + index = index < maxIndexValue ? index + 1 : 0; + return value; + }); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/SchedulerExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/SchedulerExtensions.cs new file mode 100644 index 0000000..2659045 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/SchedulerExtensions.cs @@ -0,0 +1,65 @@ + +using System.Reactive.Concurrency; +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; + +namespace CodeCasa.AutomationPipelines.Lights.Extensions +{ + internal static class SchedulerExtensions + { + /// + /// Schedules a smooth light transition between two states, + /// optionally interpolating progress if the transition is already in progress. + /// + public static IDisposable? ScheduleInterpolatedLightTransition( + this IScheduler scheduler, + LightParameters? sourceLightParameters, + LightParameters? destinationLightParameters, + DateTime? startOfTransition, + DateTime? endOfTransition, + Action transitionAction, + int defaultTransitionTimeMs = 500) + { + if (destinationLightParameters == null) + { + transitionAction(null); + return null; + } + + if (endOfTransition == null) + { + // If there is no end of transition specified, we simply go to the destination state immediately. + transitionAction(destinationLightParameters.AsTransition()); + return null; + } + + var utcNow = DateTime.UtcNow; + // Note: this can be negative. + var timeToEndOfInputTransition = endOfTransition.Value - utcNow; + // For any transition under half a second we simply don't provide a transition. Lights will just smoothly go to the corresponding state. + if (timeToEndOfInputTransition <= TimeSpan.FromMilliseconds(defaultTransitionTimeMs)) + { + transitionAction(destinationLightParameters.AsTransition()); + return null; + } + + if (sourceLightParameters == null || startOfTransition == null) + { + // We don't know the original parameters or the start of this transition, so we simply transition from where we are towards the new. + transitionAction(destinationLightParameters.AsTransition(timeToEndOfInputTransition)); + return null; + } + + var total = (endOfTransition.Value - startOfTransition.Value).TotalSeconds; + var elapsed = (utcNow - startOfTransition.Value).TotalSeconds; + var progress = elapsed / total; + + var initialState = sourceLightParameters.Interpolate(destinationLightParameters, progress); + // First (re)set the lights to where they would have been. + transitionAction(initialState.AsTransition()); + // After the lights have been reset (default transition takes half a second), continue the rest of the transition. + return scheduler.Schedule(TimeSpan.FromMilliseconds(defaultTransitionTimeMs), + () => transitionAction(destinationLightParameters.AsTransition(timeToEndOfInputTransition - TimeSpan.FromMilliseconds(defaultTransitionTimeMs)))); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..13bc25e --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddLightPipelines(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddTransient() + .AddTransient() + .AddSingleton() + .AddTransient(serviceProvider => + serviceProvider.GetRequiredService().GetLightPipelineContext()); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs new file mode 100644 index 0000000..011d9c7 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs @@ -0,0 +1,26 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Extensions; + +internal static class ServiceProviderExtensions +{ + public static T + CreateInstanceWithinContext(this IServiceProvider serviceProvider, ILight lightEntity) => + serviceProvider.CreateInstanceWithinContext(new LightPipelineContext(serviceProvider, lightEntity)); + + public static T + CreateInstanceWithinContext(this IServiceProvider serviceProvider, ILightPipelineContext context) => + (T)serviceProvider.CreateInstanceWithinContext(typeof(T), context); + + public static object CreateInstanceWithinContext(this IServiceProvider serviceProvider, Type instanceType, + ILightPipelineContext context) + { + var contextProvider = serviceProvider.GetRequiredService(); + contextProvider.SetLightPipelineContext(context); + var instance = ActivatorUtilities.CreateInstance(serviceProvider, instanceType); + contextProvider.ResetLightEntity(); + return instance; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs new file mode 100644 index 0000000..62dc598 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs @@ -0,0 +1,29 @@ +using System.Reactive.Linq; + + +namespace CodeCasa.AutomationPipelines.Lights.Nodes +{ + public class CompositeDimmer : IDimmer + { + public CompositeDimmer(IEnumerable dimmers) + { + dimmers = dimmers.ToArray(); + if (dimmers == null || !dimmers.Any()) + throw new ArgumentException("At least one dimmer must be provided.", nameof(dimmers)); + + Dimming = dimmers + .Select(d => d.Dimming) + .CombineLatest(x => x.Any()) + .DistinctUntilChanged(); + + Brightening = dimmers + .Select(d => d.Brightening) + .CombineLatest(x => x.Any()) + .DistinctUntilChanged(); + } + + public IObservable Dimming { get; } + + public IObservable Brightening { get; } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs new file mode 100644 index 0000000..55bfd83 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs @@ -0,0 +1,119 @@ +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Nodes; + +public class DimHelper( + ILight subject, + IEnumerable lightsInDimOrder, + int minBrightness, + int brightnessStep) +{ + private readonly ILight[] _lightsInDimOrder = lightsInDimOrder.ToArray(); + + public LightTransition? DimStep() + { + var lightToTurnOff = ShouldTurnOffToDim(); + if (lightToTurnOff?.Id == subject.Id) + { + return LightTransition.Off(); + } + if (lightToTurnOff != null) + { + // Another node should act. We shouldn't do anything. + return null; + } + + var parameters = subject.GetParameters(); + var oldBrightness = parameters.Brightness ?? 0; + if (oldBrightness == 0) + { + return null; + } + var newBrightness = Math.Max(minBrightness, oldBrightness - brightnessStep); + if (newBrightness == oldBrightness) + { + return null; + } + + return (parameters with { Brightness = newBrightness }).AsTransition(); + } + + public LightTransition? BrightenStep() + { + var lightToTurnOn = ShouldTurnOnToBrighten(); + if (lightToTurnOn?.Id == subject.Id) + { + return (subject.GetParameters() with { Brightness = minBrightness }).AsTransition(); + } + if (lightToTurnOn != null) + { + // Another node should act. We shouldn't do anything. + return null; + } + + var parameters = subject.GetParameters(); + var oldBrightness = parameters.Brightness ?? 0; + if (oldBrightness == 0) + { + return null; + } + var newBrightness = Math.Min(255, oldBrightness + brightnessStep); + if (newBrightness == oldBrightness) + { + return null; + } + + return (parameters with { Brightness = newBrightness }).AsTransition(); + } + + private ILight? ShouldTurnOffToDim() + { + // todo: filter on availability + ILight? lightToTurnOff = null; + foreach (var light in _lightsInDimOrder) + { + var brightness = light.GetParameters().Brightness ?? 0; + if (brightness == 0) + { + continue; + } + if (brightness > minBrightness) + { + // If any light is brighter than MinBrightness, we don't turn anything off, we need to dim. + return null; + } + + lightToTurnOff ??= light; + } + + return lightToTurnOff; + } + + private ILight? ShouldTurnOnToBrighten() + { + Console.WriteLine($"What node for {subject.Id} sees:"); + foreach (var light in _lightsInDimOrder.Reverse()) + { + Console.WriteLine($"{light.Id}: {light.GetParameters()}"); + } + + // This method is a bit more specific: it will only return true if lights are turned on in the correct order. If not, we want to keep the lights that are off, off. + ILight? lightToTurnOn = null; + foreach (var light in _lightsInDimOrder.Reverse()) + { + var brightness = light.GetParameters().Brightness ?? 0; + if (brightness >= minBrightness) // On + { + if (lightToTurnOn != null || brightness > minBrightness) + { + return null; + } + continue; + } + + lightToTurnOn ??= light; + } + + return lightToTurnOn; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs new file mode 100644 index 0000000..99627df --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs @@ -0,0 +1,10 @@ +namespace CodeCasa.AutomationPipelines.Lights.Nodes; + +public class FactoryNode(Func lightTransitionFactory) + : PipelineNode +{ + protected override void InputReceived(TState? input) + { + Output = lightTransitionFactory(input); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs new file mode 100644 index 0000000..6dd5ef4 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs @@ -0,0 +1,151 @@ +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Nodes +{ + public abstract class LightTransitionNode(IScheduler scheduler) : IPipelineNode + { + private readonly Subject _newOutputSubject = new(); + private LightTransition? _input; + private LightParameters? _inputLightDestinationParameters; + private DateTime? _inputStartOfTransition; + private DateTime? _inputEndOfTransition; + private LightTransition? _output; + private bool _passThroughNextInput; + private bool _passThrough; + private IDisposable? _scheduledAction; + + protected LightParameters? InputLightSourceParameters { get; private set; } + + /// + public IObservable OnNewOutput => _newOutputSubject.AsObservable(); + + /// + public LightTransition? Input + { + get => _input; + set + { + _scheduledAction?.Dispose(); // Always cancel scheduled actions when the input changes. + // We save additional information on the light transition that we can later use to continue the transition if it would be interrupted. + InputLightSourceParameters = _inputLightDestinationParameters; + _input = value; + _inputLightDestinationParameters = value?.LightParameters; + var transitionTime = value?.TransitionTime; + _inputStartOfTransition = DateTime.UtcNow; + _inputEndOfTransition = transitionTime == null ? null : _inputStartOfTransition + transitionTime; + + if (_passThroughNextInput) + { + PassThrough = true; + return; + } + if (PassThrough) + { + SetOutputInternal(_input); + return; + } + InputReceived(_input); + } + } + + /// + /// Called when the input is received. + /// + protected virtual void InputReceived(LightTransition? input) + { + // Ignore input by default. + } + + /// + /// Turns on pass-through mode for the node, meaning it will pass the input directly to the output without processing it. + /// + protected void PassInputThrough() + { + PassThrough = true; + } + + /// + /// Sets the output state of the node. This will trigger the processing of the input. + /// If the node is disabled, it will be enabled when setting an output value. + /// + public LightTransition? Output + { + get => _output; + protected set + { + _scheduledAction?.Dispose(); // Always cancel scheduled actions when the output is changed directly. + PassThrough = false; + + SetOutputInternal(value); + } + } + + protected void ScheduleInterpolatedLightTransitionUsingInputTransitionTime(LightParameters? sourceLightParameters, LightParameters? desiredLightParameters) + { + PassThrough = false; + _scheduledAction = scheduler.ScheduleInterpolatedLightTransition(sourceLightParameters, + desiredLightParameters, _inputStartOfTransition, _inputEndOfTransition, SetOutputInternal); + } + + public bool PassThrough + { + get => _passThrough; + set + { + // Always reset _passThroughNextInput when PassThrough is explicitly called. + _passThroughNextInput = false; + + if (_passThrough == value) + { + return; + } + + _scheduledAction?.Dispose(); // Always cancel scheduled actions when the pass through value changes. + + _passThrough = value; + if (_passThrough) + { + _scheduledAction = scheduler.ScheduleInterpolatedLightTransition(InputLightSourceParameters, + _inputLightDestinationParameters, _inputStartOfTransition, _inputEndOfTransition, SetOutputInternal); + } + } + } + + /// + /// Changes the output state of the node and enables pass-through mode after the next input. + /// This can be useful for nodes that should influence pipeline behavior once. For example a light switch or a motion sensor detection. + /// + protected void ChangeOutputAndTurnOnPassThroughOnNextInput(LightTransition? output) + { + Output = output; + TurnOnPassThroughOnNextInput(); + } + + /// + /// Keeps the current output but enables pass-through mode after receiving the next input. + /// This can be useful for nodes that should influence pipeline behavior once. For example a light switch or a motion sensor detection. + /// + protected void TurnOnPassThroughOnNextInput() + { + if (PassThrough) + { + return; + } + + _passThroughNextInput = true; + } + + private void SetOutputInternal(LightTransition? output) + { + _output = output; + _newOutputSubject.OnNext(output); + } + + /// + public override string ToString() => GetType().Name; + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs new file mode 100644 index 0000000..88f064c --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs @@ -0,0 +1,9 @@ +namespace CodeCasa.AutomationPipelines.Lights.Nodes; + +public class PassThroughNode : PipelineNode +{ + public PassThroughNode() + { + PassThrough = true; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs new file mode 100644 index 0000000..9663fe8 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs @@ -0,0 +1,35 @@ +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Nodes +{ + public class ResettableTimeoutNode : LightTransitionNode + { + public ResettableTimeoutNode(IPipelineNode childNode, TimeSpan turnOffTime, IObservable refreshObservable, IScheduler scheduler) : base(scheduler) + { + var serializedChild = childNode.OnNewOutput.Prepend(childNode.Output).ObserveOn(scheduler); + + var serializedTurnOff = + refreshObservable.Select(_ => Unit.Default) + .Prepend(Unit.Default) + .Throttle(turnOffTime, scheduler) + .Take(1) + .ObserveOn(scheduler); + + serializedChild + .TakeUntil(serializedTurnOff) + .Subscribe(output => + { + Output = output; + }); + + serializedTurnOff + .Subscribe(_ => + { + ChangeOutputAndTurnOnPassThroughOnNextInput(LightTransition.Off()); + }); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs new file mode 100644 index 0000000..158fbc8 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Nodes +{ + internal class ScopedNode(IServiceScope serviceScope, IPipelineNode innerNode) + : IPipelineNode, IAsyncDisposable + { + public async ValueTask DisposeAsync() + { + // todo: make this a helper method + switch (serviceScope) + { + case IAsyncDisposable asyncDisposable: + await asyncDisposable.DisposeAsync(); + break; + case IDisposable disposable: + disposable.Dispose(); + break; + } + + switch (innerNode) + { + case IAsyncDisposable asyncDisposable: + await asyncDisposable.DisposeAsync(); + break; + case IDisposable disposable: + disposable.Dispose(); + break; + } + } + + public TState? Input + { + get => innerNode.Input; + set => innerNode.Input = value; + } + + public TState? Output => innerNode.Output; + public IObservable OnNewOutput => innerNode.OnNewOutput; + + public override string? ToString() => $"{innerNode} (scoped)"; + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs new file mode 100644 index 0000000..9e5d1c8 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs @@ -0,0 +1,12 @@ +using System.Reactive.Concurrency; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Nodes; + +public class StaticLightTransitionNode : LightTransitionNode +{ + public StaticLightTransitionNode(LightTransition? output, IScheduler scheduler) : base(scheduler) + { + Output = output; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs new file mode 100644 index 0000000..fec52cd --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs @@ -0,0 +1,17 @@ +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Nodes; + +public class TurnOffThenPassThroughNode : PipelineNode +{ + public TurnOffThenPassThroughNode() + { + // Note: we cannot simply call ChangeOutputAndTurnOnPassThroughOnNextInput here, as the input will immediately be set when this node is added to the timeline. + Output = LightTransition.Off(); + } + + protected override void InputReceived(LightTransition? input) + { + TurnOnPassThroughOnNextInput(); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs new file mode 100644 index 0000000..53420f8 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs @@ -0,0 +1,34 @@ +using CodeCasa.AutomationPipelines.Lights.Cycle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class CompositeLightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightTransitions) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, Action configure) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, configure)); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs new file mode 100644 index 0000000..205bff2 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -0,0 +1,50 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Toggle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class CompositeLightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightParameters) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightTransitions) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable>> nodeFactories) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params Func>[] nodeFactories) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + Action configure) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, configure)); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs new file mode 100644 index 0000000..68e37bd --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs @@ -0,0 +1,140 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using Microsoft.Extensions.DependencyInjection; + + +using System.Reactive.Linq; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class CompositeLightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, + LightParameters falseLightParameters) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.Switch(trueLightParameters, falseLightParameters)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightParameters trueLightParameters, + LightParameters falseLightParameters) + { + NodeContainers.Values.ForEach(b => b.Switch(observable, trueLightParameters, falseLightParameters)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(Func trueLightParametersFactory, + Func falseLightParametersFactory) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.Switch(trueLightParametersFactory, falseLightParametersFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightParametersFactory, + Func falseLightParametersFactory) + { + NodeContainers.Values.ForEach(b => b.Switch(observable, trueLightParametersFactory, falseLightParametersFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(LightTransition trueLightTransition, + LightTransition falseLightTransition) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.Switch(trueLightTransition, falseLightTransition)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightTransition trueLightTransition, + LightTransition falseLightTransition) + { + NodeContainers.Values.ForEach(b => b.Switch(observable, trueLightTransition, falseLightTransition)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(Func trueLightTransitionFactory, + Func falseLightTransitionFactory) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.Switch(trueLightTransitionFactory, falseLightTransitionFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightTransitionFactory, + Func falseLightTransitionFactory) + { + NodeContainers.Values.ForEach(b => b.Switch(observable, trueLightTransitionFactory, falseLightTransitionFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(Func> trueNodeFactory, Func> falseNodeFactory) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.Switch(trueNodeFactory, falseNodeFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func> trueNodeFactory, + Func> falseNodeFactory) + { + NodeContainers.Values.ForEach(b => b.Switch(observable, trueNodeFactory, falseNodeFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator Switch() where TObservable : IObservable where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode + { + NodeContainers.Values.ForEach(b => b.Switch()); + return this; + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable) where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode + { + NodeContainers.Values.ForEach(b => b.Switch(observable)); + return this; + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action trueConfigure, + Action falseConfigure) where TObservable : IObservable + { + /* + * For this implementation we can either instantiate the TObservable for each container and pass configure to them individual, breaking composite dimming behavior. + * Or we can create a single TObservable without light context. + * I decided to go with the latter to preserve composite dimming behavior. + */ + var observable = ActivatorUtilities.CreateInstance(serviceProvider); + return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action trueConfigure, + Action falseConfigure) + { + // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + return AddReactiveNode(c => c + .On(observable.Where(x => x), trueConfigure) + .On(observable.Where(x => !x), falseConfigure)); + } + + public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable + { + var observable = ActivatorUtilities.CreateInstance(serviceProvider); + return AddPipelineSwitch(observable, trueConfigure, falseConfigure); + } + + public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action trueConfigure, + Action falseConfigure) + { + // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + return AddReactiveNode(c => c + .On(observable.Where(x => x), trueConfigure) + .On(observable.Where(x => x), falseConfigure)); + } + + public ILightTransitionPipelineConfigurator TurnOnOff() where TObservable : IObservable + { + return Switch(LightTransition.On(), LightTransition.Off()); + } + + public ILightTransitionPipelineConfigurator TurnOnOff(IObservable observable) + { + return Switch(observable, LightTransition.On(), LightTransition.Off()); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs new file mode 100644 index 0000000..5ec8750 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs @@ -0,0 +1,152 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using Microsoft.Extensions.DependencyInjection; + + +using System.Reactive.Linq; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class CompositeLightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator When(LightParameters lightParameters) + where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.When(lightParameters)); + return this; + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + LightParameters lightParameters) + { + NodeContainers.Values.ForEach(b => b.When(observable, lightParameters)); + return this; + } + + public ILightTransitionPipelineConfigurator When( + Func lightParametersFactory) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.When(lightParametersFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + Func lightParametersFactory) + { + NodeContainers.Values.ForEach(b => b.When(observable, lightParametersFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator When(LightTransition lightTransition) + where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.When(lightTransition)); + return this; + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + LightTransition lightTransition) + { + NodeContainers.Values.ForEach(b => b.When(observable, lightTransition)); + return this; + } + + public ILightTransitionPipelineConfigurator When( + Func lightTransitionFactory) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.When(lightTransitionFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + Func lightTransitionFactory) + { + NodeContainers.Values.ForEach(b => b.When(observable, lightTransitionFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator When( + Func> nodeFactory) where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.When(nodeFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + Func> nodeFactory) + { + NodeContainers.Values.ForEach(b => b.When(observable, nodeFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator When() + where TObservable : IObservable where TNode : IPipelineNode + { + NodeContainers.Values.ForEach(b => b.When()); + return this; + } + + public ILightTransitionPipelineConfigurator When(IObservable observable) + where TNode : IPipelineNode + { + NodeContainers.Values.ForEach(b => b.When(observable)); + return this; + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action configure) where TObservable : IObservable + { + /* + * For this implementation we can either instantiate the TObservable for each container and pass configure to them individual, breaking composite dimming behavior. + * Or we can create a single TObservable without light context. + * I decided to go with the latter to preserve composite dimming behavior. + */ + var observable = ActivatorUtilities.CreateInstance(serviceProvider); + return AddReactiveNodeWhen(observable, configure); + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action configure) + { + // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + return AddReactiveNode(c => c + .On(observable.Where(x => x), configure) + .PassThroughOn(observable.Where(x => !x))); + } + + public ILightTransitionPipelineConfigurator AddPipelineWhen(Action configure) where TObservable : IObservable + { + var observable = ActivatorUtilities.CreateInstance(serviceProvider); + return AddPipelineWhen(observable, configure); + } + + public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action configure) + { + // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + return AddReactiveNode(c => c + .On(observable.Where(x => x), configure) + .PassThroughOn(observable.Where(x => !x))); + } + + public ILightTransitionPipelineConfigurator TurnOffWhen() where TObservable : IObservable + { + NodeContainers.Values.ForEach(b => b.TurnOffWhen()); + return this; + } + + public ILightTransitionPipelineConfigurator TurnOffWhen(IObservable observable) + { + NodeContainers.Values.ForEach(b => b.TurnOffWhen(observable)); + return this; + } + + public ILightTransitionPipelineConfigurator TurnOnWhen() where TObservable : IObservable + { + return When(LightTransition.On()); + } + + public ILightTransitionPipelineConfigurator TurnOnWhen(IObservable observable) + { + return When(observable, LightTransition.On()); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs new file mode 100644 index 0000000..6c98077 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs @@ -0,0 +1,99 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline +{ + public partial class CompositeLightTransitionPipelineConfigurator( + IServiceProvider serviceProvider, + LightPipelineFactory lightPipelineFactory, + ReactiveNodeFactory reactiveNodeFactory, + Dictionary nodeContainers, + IScheduler scheduler) + : ILightTransitionPipelineConfigurator + { + public Dictionary NodeContainers { get; } = nodeContainers; + + public ILightTransitionPipelineConfigurator SetName(string name) + { + NodeContainers.Values.ForEach(b => b.SetName(name)); + return this; + } + + public ILightTransitionPipelineConfigurator AddNode() where TNode : IPipelineNode + { + NodeContainers.Values.ForEach(b => b.AddNode()); + return this; + } + + public ILightTransitionPipelineConfigurator AddNode(Func> nodeFactory) + { + NodeContainers.Values.ForEach(b => b.AddNode(nodeFactory)); + return this; + } + + public ILightTransitionPipelineConfigurator AddReactiveNode( + Action configure) + { + var nodes = reactiveNodeFactory.CreateReactiveNodes(NodeContainers.Select(nc => nc.Value.LightEntity), + configure); + NodeContainers.ForEach(kvp => kvp.Value.AddNode(nodes[kvp.Key])); + return this; + } + + public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) + { + var pipelines = lightPipelineFactory.CreateLightPipelines(NodeContainers.Select(c => c.Value.LightEntity), + pipelineNodeOptions); + NodeContainers.ForEach(kvp => kvp.Value.AddNode(pipelines[kvp.Key])); + return this; + } + + public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer) + { + return AddDimmer(dimmer, _ => { }); + } + + public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action dimOptions) + { + return AddReactiveNode(c => + { + c.AddUncoupledDimmer(dimmer, dimOptions); + }); + } + + public ILightTransitionPipelineConfigurator ForLight(string lightEntityId, + Action compositeNodeBuilder) => ForLights([lightEntityId], compositeNodeBuilder); + + public ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, + Action compositeNodeBuilder) => ForLights([lightEntity], compositeNodeBuilder); + + public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, + Action compositeNodeBuilder) + { + var lightEntityIdsArray = + CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, NodeContainers.Keys); + + if (lightEntityIdsArray.Length == NodeContainers.Count) + { + compositeNodeBuilder(this); + return this; + } + if (lightEntityIdsArray.Length == 1) + { + compositeNodeBuilder(NodeContainers[lightEntityIdsArray.First()]); + return this; + } + + compositeNodeBuilder(new CompositeLightTransitionPipelineConfigurator(serviceProvider, lightPipelineFactory, reactiveNodeFactory, NodeContainers + .Where(kvp => lightEntityIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + scheduler)); + return this; + } + + public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, + Action compositeNodeBuilder) => ForLights(lightEntities.Select(e => e.Id), compositeNodeBuilder); + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Cycle.cs new file mode 100644 index 0000000..cb13ff1 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Cycle.cs @@ -0,0 +1,62 @@ +using CodeCasa.AutomationPipelines.Lights.Cycle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial interface ILightTransitionPipelineConfigurator +{ + /// + /// Adds a cycle node that rotates through the specified light parameters when triggered by . + /// Each trigger cycles to the next set of parameters in the collection. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next parameters. + /// The collection of light parameters to cycle through. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + IEnumerable lightParameters); + + /// + /// Adds a cycle node that rotates through the specified light parameters when triggered by . + /// Each trigger cycles to the next set of parameters in the array. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next parameters. + /// The array of light parameters to cycle through. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + params LightParameters[] lightParameters); + + /// + /// Adds a cycle node that rotates through the specified light transitions when triggered by . + /// Each trigger cycles to the next transition in the collection. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next transition. + /// The collection of light transitions to cycle through. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + IEnumerable lightTransitions); + + /// + /// Adds a cycle node that rotates through the specified light transitions when triggered by . + /// Each trigger cycles to the next transition in the array. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next transition. + /// The array of light transitions to cycle through. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + params LightTransition[] lightTransitions); + + /// + /// Adds a cycle node configured by the specified action when triggered by . + /// Each trigger cycles to the next configured state. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next state. + /// An action to configure the cycle behavior. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + Action configure); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Toggle.cs new file mode 100644 index 0000000..18b1399 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -0,0 +1,85 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Toggle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial interface ILightTransitionPipelineConfigurator +{ + /// + /// Adds a toggle node that switches between the specified light parameters when triggered by . + /// Each trigger toggles to the next set of parameters in the collection, wrapping back to the first after the last. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next parameters. + /// The collection of light parameters to toggle between. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightParameters); + + /// + /// Adds a toggle node that switches between the specified light parameters when triggered by . + /// Each trigger toggles to the next set of parameters in the array, wrapping back to the first after the last. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next parameters. + /// The array of light parameters to toggle between. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params LightParameters[] lightParameters); + + /// + /// Adds a toggle node that switches between the specified light transitions when triggered by . + /// Each trigger toggles to the next transition in the collection, wrapping back to the first after the last. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next transition. + /// The collection of light transitions to toggle between. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightTransitions); + + /// + /// Adds a toggle node that switches between the specified light transitions when triggered by . + /// Each trigger toggles to the next transition in the array, wrapping back to the first after the last. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next transition. + /// The array of light transitions to toggle between. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params LightTransition[] lightTransitions); + + /// + /// Adds a toggle node that switches between nodes created by the specified factory functions when triggered by . + /// Each trigger toggles to the next node factory in the collection, wrapping back to the first after the last. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next node. + /// The collection of factory functions that create pipeline nodes. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable>> nodeFactories); + + /// + /// Adds a toggle node that switches between nodes created by the specified factory functions when triggered by . + /// Each trigger toggles to the next node factory in the array, wrapping back to the first after the last. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next node. + /// The array of factory functions that create pipeline nodes. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params Func>[] nodeFactories); + + /// + /// Adds a toggle node configured by the specified action when triggered by . + /// Each trigger toggles to the next configured state. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next state. + /// An action to configure the toggle behavior. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + Action configure); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs new file mode 100644 index 0000000..f2f302b --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs @@ -0,0 +1,254 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial interface ILightTransitionPipelineConfigurator +{ + /// + /// Registers a node that switches between two sets of light parameters based on a boolean observable. + /// When the observable of type emits , + /// the are applied; when it emits , + /// the are applied. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The light parameters to apply when the observable emits true. + /// The light parameters to apply when the observable emits false. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, + LightParameters falseLightParameters) + where TObservable : IObservable; + + /// + /// Registers a node that switches between two sets of light parameters based on the . + /// When the observable emits , the are applied; + /// when it emits , the are applied. + /// + /// The observable that determines which parameters to apply. + /// The light parameters to apply when the observable emits true. + /// The light parameters to apply when the observable emits false. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(IObservable observable, + LightParameters trueLightParameters, LightParameters falseLightParameters); + + /// + /// Registers a node that switches between two sets of light parameters created by factory functions based on a boolean observable. + /// When the observable of type emits , + /// the parameters from are applied; when it emits , + /// the parameters from are applied. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// A factory function that creates light parameters for true values. + /// A factory function that creates light parameters for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch( + Func trueLightParametersFactory, + Func falseLightParametersFactory) + where TObservable : IObservable; + + /// + /// Registers a node that switches between two sets of light parameters created by factory functions based on the . + /// When the observable emits , the parameters from are applied; + /// when it emits , the parameters from are applied. + /// + /// The observable that determines which parameters to apply. + /// A factory function that creates light parameters for true values. + /// A factory function that creates light parameters for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(IObservable observable, + Func trueLightParametersFactory, + Func falseLightParametersFactory); + + /// + /// Registers a node that switches between two light transitions based on a boolean observable. + /// When the observable of type emits , + /// the is applied; when it emits , + /// the is applied. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The light transition to apply when the observable emits true. + /// The light transition to apply when the observable emits false. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(LightTransition trueLightTransition, + LightTransition falseLightTransition) + where TObservable : IObservable; + + /// + /// Registers a node that switches between two light transitions based on the . + /// When the observable emits , the is applied; + /// when it emits , the is applied. + /// + /// The observable that determines which transition to apply. + /// The light transition to apply when the observable emits true. + /// The light transition to apply when the observable emits false. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(IObservable observable, + LightTransition trueLightTransition, LightTransition falseLightTransition); + + /// + /// Registers a node that switches between two light transitions created by factory functions based on a boolean observable. + /// When the observable of type emits , + /// the transition from is applied; when it emits , + /// the transition from is applied. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// A factory function that creates a light transition for true values. + /// A factory function that creates a light transition for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch( + Func trueLightTransitionFactory, + Func falseLightTransitionFactory) + where TObservable : IObservable; + + /// + /// Registers a node that switches between two light transitions created by factory functions based on the . + /// When the observable emits , the transition from is applied; + /// when it emits , the transition from is applied. + /// + /// The observable that determines which transition to apply. + /// A factory function that creates a light transition for true values. + /// A factory function that creates a light transition for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(IObservable observable, + Func trueLightTransitionFactory, + Func falseLightTransitionFactory); + + /// + /// Registers a node that switches between two pipeline nodes created by factory functions based on a boolean observable. + /// When the observable of type emits , + /// the node from is applied; when it emits , + /// the node from is applied. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// A factory function that creates a pipeline node for true values. + /// A factory function that creates a pipeline node for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch( + Func> trueNodeFactory, + Func> falseNodeFactory) + where TObservable : IObservable; + + /// + /// Registers a node that switches between two pipeline nodes created by factory functions based on the . + /// When the observable emits , the node from is applied; + /// when it emits , the node from is applied. + /// + /// The observable that determines which node to apply. + /// A factory function that creates a pipeline node for true values. + /// A factory function that creates a pipeline node for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(IObservable observable, + Func> trueNodeFactory, + Func> falseNodeFactory); + + /// + /// Registers a node that switches between two pipeline node types based on a boolean observable. + /// When the observable of type emits , + /// a node of type is applied; when it emits , + /// a node of type is applied. + /// The observable and both node types are resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The type of the pipeline node to apply for true values. + /// The type of the pipeline node to apply for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch() + where TObservable : IObservable + where TTrueNode : IPipelineNode + where TFalseNode : IPipelineNode; + + /// + /// Registers a node that switches between two pipeline node types based on the . + /// When the observable emits , a node of type is applied; + /// when it emits , a node of type is applied. + /// Both node types are resolved from the service provider. + /// + /// The type of the pipeline node to apply for true values. + /// The type of the pipeline node to apply for false values. + /// The observable that determines which node to apply. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator Switch(IObservable observable) + where TTrueNode : IPipelineNode + where TFalseNode : IPipelineNode; + + /// + /// Registers a reactive node that switches between two configurations based on a boolean observable. + /// When the observable of type emits , + /// the node is configured by ; when it emits , + /// the node is configured by . + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// An action to configure the reactive node for true values. + /// An action to configure the reactive node for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddReactiveNodeSwitch( + Action trueConfigure, + Action falseConfigure) + where TObservable : IObservable; + + /// + /// Registers a reactive node that switches between two configurations based on the . + /// When the observable emits , the node is configured by ; + /// when it emits , the node is configured by . + /// + /// The observable that determines which configuration to apply. + /// An action to configure the reactive node for true values. + /// An action to configure the reactive node for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, + Action trueConfigure, + Action falseConfigure); + + /// + /// Registers a pipeline that switches between two configurations based on a boolean observable. + /// When the observable of type emits , + /// the pipeline is configured by ; when it emits , + /// the pipeline is configured by . + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// An action to configure the pipeline for true values. + /// An action to configure the pipeline for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddPipelineSwitch( + Action trueConfigure, + Action falseConfigure) + where TObservable : IObservable; + + /// + /// Registers a pipeline that switches between two configurations based on the . + /// When the observable emits , the pipeline is configured by ; + /// when it emits , the pipeline is configured by . + /// + /// The observable that determines which configuration to apply. + /// An action to configure the pipeline for true values. + /// An action to configure the pipeline for false values. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, + Action trueConfigure, + Action falseConfigure); + + /// + /// Registers a node that turns the light on when the observable of type + /// emits , and turns it off when it emits . + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator TurnOnOff() where TObservable : IObservable; + + /// + /// Registers a node that turns the light on when the emits , + /// and turns it off when it emits . + /// + /// The observable that determines whether to turn the light on or off. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator TurnOnOff(IObservable observable); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs new file mode 100644 index 0000000..16ecfe1 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs @@ -0,0 +1,234 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial interface ILightTransitionPipelineConfigurator +{ + /// + /// Registers a node that applies the given when the observable + /// of type emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The light parameters to apply when the observable emits true. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(LightParameters lightParameters) + where TObservable : IObservable; + + /// + /// Registers a node that applies the given when the + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// + /// The observable that determines when to apply the light parameters. + /// The light parameters to apply when the observable emits true. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(IObservable observable, LightParameters lightParameters); + + /// + /// Registers a node that applies light parameters created by + /// when the observable of type emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// A factory function that creates light parameters based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When( + Func lightParametersFactory) + where TObservable : IObservable; + + /// + /// Registers a node that applies light parameters created by + /// when the emits . + /// When the observable emits , inputs are passed through unchanged. + /// + /// The observable that determines when to apply the light parameters. + /// A factory function that creates light parameters based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(IObservable observable, + Func lightParametersFactory); + + /// + /// Registers a node that applies the given when the observable + /// of type emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The light transition to apply when the observable emits true. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(LightTransition lightTransition) + where TObservable : IObservable; + + /// + /// Registers a node that applies the given when the + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// + /// The observable that determines when to apply the light transition. + /// The light transition to apply when the observable emits true. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(IObservable observable, LightTransition lightTransition); + + /// + /// Registers a node that applies the created by + /// when the observable of type + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// A factory function that creates a light transition based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When( + Func lightTransitionFactory) + where TObservable : IObservable; + + /// + /// Registers a node that applies the created by + /// when the + /// emits . When the observable emits , + /// inputs are passed through unchanged. + /// + /// The observable that determines when to apply the light transition. + /// A factory function that creates a light transition based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(IObservable observable, + Func lightTransitionFactory); + + /// + /// Registers a node created by when the observable of type + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// A factory function that creates a pipeline node based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When( + Func> nodeFactory) + where TObservable : IObservable; + + /// + /// Registers a node created by when the + /// emits . When the observable emits , + /// inputs are passed through unchanged. + /// + /// The observable that determines when to apply the node. + /// A factory function that creates a pipeline node based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(IObservable observable, + Func> nodeFactory); + + /// + /// Registers a node of type when the observable of type + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// Both the observable and the node are resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The type of the pipeline node to resolve from the service provider. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When() + where TObservable : IObservable + where TNode : IPipelineNode; + + /// + /// Registers a node of type when the + /// emits . When the observable emits , + /// inputs are passed through unchanged. The node is resolved from the service provider. + /// + /// The type of the pipeline node to resolve from the service provider. + /// The observable that determines when to apply the node. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator When(IObservable observable) + where TNode : IPipelineNode; + + /// + /// Registers a reactive node configured by when the observable of type + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// An action to configure the reactive node. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddReactiveNodeWhen( + Action configure) + where TObservable : IObservable; + + /// + /// Registers a reactive node configured by when the + /// emits . When the observable emits , + /// inputs are passed through unchanged. + /// + /// The observable that determines when to apply the reactive node. + /// An action to configure the reactive node. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, + Action configure); + + /// + /// Registers a pipeline configured by when the observable of type + /// emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// An action to configure the nested pipeline. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddPipelineWhen( + Action configure) + where TObservable : IObservable; + + /// + /// Registers a pipeline configured by when the + /// emits . When the observable emits , + /// inputs are passed through unchanged. + /// + /// The observable that determines when to apply the pipeline. + /// An action to configure the nested pipeline. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, + Action configure); + + /// + /// Registers a node that turns off the light when the observable + /// of type emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator TurnOffWhen() where TObservable : IObservable; + + /// + /// Registers a node that turns off the light when the emits . + /// When the observable emits , inputs are passed through unchanged. + /// + /// The observable that determines when to turn off the light. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator TurnOffWhen(IObservable observable); + + /// + /// Registers a node that turns on the light when the observable + /// of type emits . + /// When the observable emits , inputs are passed through unchanged. + /// The observable is resolved from the service provider. + /// + /// The type of the observable to resolve from the service provider. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator TurnOnWhen() where TObservable : IObservable; + + /// + /// Registers a node that turns on the light when the emits . + /// When the observable emits , inputs are passed through unchanged. + /// + /// The observable that determines when to turn on the light. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator TurnOnWhen(IObservable observable); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs new file mode 100644 index 0000000..6f23a63 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs @@ -0,0 +1,91 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial interface ILightTransitionPipelineConfigurator +{ + /// + /// Sets the name for the current pipeline configuration. + /// + /// The name to assign to the pipeline. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator SetName(string name); + + /// + /// Adds a pipeline node of type to the pipeline. + /// The node is resolved from the service provider. + /// + /// The type of the pipeline node to add. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddNode() where TNode : IPipelineNode; + + /// + /// Adds a pipeline node created by the specified to the pipeline. + /// + /// A factory function that creates a pipeline node based on the light pipeline context. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddNode(Func> nodeFactory); + + /// + /// Adds a reactive node to the pipeline configured by the specified action. + /// + /// An action to configure the reactive node. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddReactiveNode(Action configure); + + /// + /// Adds a nested pipeline to the current pipeline configured by the specified action. + /// + /// An action to configure the nested pipeline. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions); + + /// + /// Adds a dimmer control to the pipeline. + /// + /// The dimmer device to add. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer); + + /// + /// Adds a dimmer control to the pipeline with custom configuration options. + /// + /// The dimmer device to add. + /// An action to configure the dimmer options. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action dimOptions); + + /// + /// Creates a scoped pipeline configuration for a specific light entity identified by its entity ID. + /// + /// The entity ID of the light to configure. + /// An action to configure the pipeline for this specific light. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator ForLight(string lightEntityId, Action compositeNodeBuilder); + + /// + /// Creates a scoped pipeline configuration for a specific light entity. + /// + /// The light entity to configure. + /// An action to configure the pipeline for this specific light. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, Action compositeNodeBuilder); + + /// + /// Creates a scoped pipeline configuration for multiple light entities identified by their entity IDs. + /// + /// The entity IDs of the lights to configure. + /// An action to configure the pipeline for these lights. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, Action compositeNodeBuilder); + + /// + /// Creates a scoped pipeline configuration for multiple light entities. + /// + /// The light entities to configure. + /// An action to configure the pipeline for these lights. + /// The configurator instance for method chaining. + ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, Action compositeNodeBuilder); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs new file mode 100644 index 0000000..3441525 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline +{ + public class LightPipelineFactory( + ILogger> logger, IServiceProvider serviceProvider, ReactiveNodeFactory reactiveNodeFactory, IScheduler scheduler) + { + public IAsyncDisposable SetupLightPipeline(ILight lightEntity, + Action pipelineBuilder) + { + var disposables = new CompositeAsyncDisposable(); + var pipelines = CreateLightPipelines(lightEntity.Flatten(), pipelineBuilder); + foreach (var pipeline in pipelines.Values) + { + disposables.Add(pipeline); + } + return disposables; + } + + internal IPipeline CreateLightPipeline(ILight lightEntity, Action pipelineBuilder) + { + return CreateLightPipelines([lightEntity], pipelineBuilder)[lightEntity.Id]; + } + + internal Dictionary> CreateLightPipelines(IEnumerable lightEntities, Action pipelineBuilder) + { + // todo: is this assumption correct? Make internal? + // Note: we simply assume that these are not groups. + var lightEntityArray = lightEntities.ToArray(); + if (!lightEntityArray.Any()) + { + return new Dictionary>(); + } + + var configurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionPipelineConfigurator(serviceProvider, this, reactiveNodeFactory, l, scheduler)); + ILightTransitionPipelineConfigurator configurator = lightEntityArray.Length == 1 + ? configurators[lightEntityArray[0].Id] + : new CompositeLightTransitionPipelineConfigurator( + serviceProvider, + this, + reactiveNodeFactory, + configurators, scheduler); + pipelineBuilder(configurator); + + return configurators.ToDictionary(kvp => kvp.Key, kvp => + { + var conf = kvp.Value; + return (IPipeline)new Pipeline( + conf.Name ?? conf.LightEntity.Id, + LightTransition.Off(), + conf.Nodes, + conf.LightEntity.ApplyTransition, + logger); + }); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs new file mode 100644 index 0000000..03308f6 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs @@ -0,0 +1,34 @@ +using CodeCasa.AutomationPipelines.Lights.Cycle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class LightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightTransitions) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, Action configure) + { + return AddReactiveNode(c => c.AddCycle(triggerObservable, configure)); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs new file mode 100644 index 0000000..af01003 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -0,0 +1,50 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Toggle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class LightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightParameters) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightTransitions) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + IEnumerable>> nodeFactories) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + params Func>[] nodeFactories) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); + } + + public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, + Action configure) + { + return AddReactiveNode(c => c.AddToggle(triggerObservable, configure)); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs new file mode 100644 index 0000000..fe0d178 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs @@ -0,0 +1,128 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class LightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, + LightParameters falseLightParameters) where TObservable : IObservable + { + return Switch(trueLightParameters.AsTransition(), falseLightParameters.AsTransition()); + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightParameters trueLightParameters, + LightParameters falseLightParameters) + { + return Switch(observable, trueLightParameters.AsTransition(), falseLightParameters.AsTransition()); + } + + public ILightTransitionPipelineConfigurator Switch(Func trueLightParametersFactory, + Func falseLightParametersFactory) where TObservable : IObservable + { + return Switch(c => falseLightParametersFactory(c).AsTransition(), c => trueLightParametersFactory(c).AsTransition()); + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightParametersFactory, + Func falseLightParametersFactory) + { + return Switch(observable, c => trueLightParametersFactory(c).AsTransition(), c => falseLightParametersFactory(c).AsTransition()); + } + + public ILightTransitionPipelineConfigurator Switch(LightTransition trueLightTransition, + LightTransition falseLightTransition) where TObservable : IObservable + { + return Switch(trueLightTransition, falseLightTransition); + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightTransition trueLightTransition, + LightTransition falseLightTransition) + { + return Switch(observable, trueLightTransition, falseLightTransition); + } + + public ILightTransitionPipelineConfigurator Switch(Func trueLightTransitionFactory, + Func falseLightTransitionFactory) where TObservable : IObservable + { + return Switch( + c => new StaticLightTransitionNode(trueLightTransitionFactory(c), c.ServiceProvider.GetRequiredService()), + c => new StaticLightTransitionNode(falseLightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightTransitionFactory, + Func falseLightTransitionFactory) + { + return Switch( + observable, + c => new StaticLightTransitionNode(trueLightTransitionFactory(c), c.ServiceProvider.GetRequiredService()), + c => new StaticLightTransitionNode(falseLightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + } + + public ILightTransitionPipelineConfigurator Switch(Func> trueNodeFactory, Func> falseNodeFactory) where TObservable : IObservable + { + var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + return Switch(observable, trueNodeFactory, falseNodeFactory); + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func> trueNodeFactory, + Func> falseNodeFactory) + { + return AddReactiveNode(c => c + .On(observable.Where(x => x), trueNodeFactory) + .On(observable.Where(x => !x), falseNodeFactory)); + } + + public ILightTransitionPipelineConfigurator Switch() where TObservable : IObservable where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode + { + var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + return Switch(observable); + } + + public ILightTransitionPipelineConfigurator Switch(IObservable observable) where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode + { + return AddReactiveNode(c => c + .On(observable.Where(x => x)) + .On(observable.Where(x => !x))); + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable + { + var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action trueConfigure, + Action falseConfigure) + { + return AddReactiveNode(c => c + .On(observable.Where(x => x), trueConfigure) + .On(observable.Where(x => !x), falseConfigure)); + } + + public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable + { + return Switch(c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, falseConfigure)); + } + + public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action trueConfigure, + Action falseConfigure) + { + return Switch(observable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, falseConfigure)); + } + + public ILightTransitionPipelineConfigurator TurnOnOff() where TObservable : IObservable + { + return Switch(LightTransition.On(), LightTransition.Off()); + } + + public ILightTransitionPipelineConfigurator TurnOnOff(IObservable observable) + { + return Switch(observable, LightTransition.On(), LightTransition.Off()); + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs new file mode 100644 index 0000000..f471b34 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs @@ -0,0 +1,135 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline; + +public partial class LightTransitionPipelineConfigurator +{ + public ILightTransitionPipelineConfigurator When(LightParameters lightParameters) + where TObservable : IObservable + { + return When(lightParameters.AsTransition()); + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + LightParameters lightParameters) + { + return When(observable, lightParameters.AsTransition()); + } + + public ILightTransitionPipelineConfigurator When( + Func lightParametersFactory) where TObservable : IObservable + { + return When(c => lightParametersFactory(c).AsTransition()); + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + Func lightParametersFactory) + { + return When(observable, c => lightParametersFactory(c).AsTransition()); + } + + public ILightTransitionPipelineConfigurator When(LightTransition lightTransition) + where TObservable : IObservable + { + return When(_ => lightTransition); + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + LightTransition lightTransition) + { + return When(observable, _ => lightTransition); + } + + public ILightTransitionPipelineConfigurator When( + Func lightTransitionFactory) where TObservable : IObservable + { + return When(c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + Func lightTransitionFactory) + { + return When(observable, c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + } + + public ILightTransitionPipelineConfigurator When( + Func> nodeFactory) where TObservable : IObservable + { + var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + return When(observable, nodeFactory); + } + + public ILightTransitionPipelineConfigurator When(IObservable observable, + Func> nodeFactory) + { + return AddReactiveNode(c => c + .On(observable.Where(x => x), nodeFactory) + .PassThroughOn(observable.Where(x => !x))); + } + + public ILightTransitionPipelineConfigurator When() + where TObservable : IObservable + where TNode : IPipelineNode + { + var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + return When(observable); + } + + public ILightTransitionPipelineConfigurator When(IObservable observable) + where TNode : IPipelineNode + { + return AddReactiveNode(c => c + .On(observable.Where(x => x)) + .PassThroughOn(observable.Where(x => !x))); + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action configure) where TObservable : IObservable + { + var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + return AddReactiveNodeWhen(observable, configure); + } + + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action configure) + { + return AddReactiveNode(c => c + .On(observable.Where(x => x), configure) + .PassThroughOn(observable.Where(x => !x))); + } + + public ILightTransitionPipelineConfigurator AddPipelineWhen(Action pipelineConfigurator) where TObservable : IObservable + { + return When(c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + } + + public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action pipelineConfigurator) + { + return When(observable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + } + + public ILightTransitionPipelineConfigurator TurnOffWhen() where TObservable : IObservable + { + return When(LightTransition.Off()); + } + + public ILightTransitionPipelineConfigurator TurnOffWhen(IObservable observable) + { + return When(observable, LightTransition.Off()); + } + + public ILightTransitionPipelineConfigurator TurnOnWhen() where TObservable : IObservable + { + return When(LightTransition.On()); + } + + public ILightTransitionPipelineConfigurator TurnOnWhen(IObservable observable) + { + return When(observable, LightTransition.On()); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs new file mode 100644 index 0000000..d3ca556 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs @@ -0,0 +1,92 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using System.Reactive.Concurrency; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Pipeline +{ + public partial class LightTransitionPipelineConfigurator( + IServiceProvider serviceProvider, + LightPipelineFactory lightPipelineFactory, + ReactiveNodeFactory reactiveNodeFactory, + ILight lightEntity, + IScheduler scheduler) + : ILightTransitionPipelineConfigurator + { + private readonly List> _nodes = new(); + + internal ILight LightEntity { get; } = lightEntity; + internal string? Name { get; private set; } + + public IReadOnlyCollection> Nodes => _nodes.AsReadOnly(); + + public ILightTransitionPipelineConfigurator + AddConditional(IObservable observable, Action trueConfigure, Action falseConfigure) + { + throw new NotImplementedException(); + } + + public ILightTransitionPipelineConfigurator SetName(string name) + { + Name = name; + return this; + } + + public ILightTransitionPipelineConfigurator AddNode() where TNode : IPipelineNode + { + _nodes.Add(serviceProvider.CreateInstanceWithinContext(LightEntity)); + return this; + } + + public ILightTransitionPipelineConfigurator AddNode(IPipelineNode node) + { + _nodes.Add(node); + return this; + } + + public ILightTransitionPipelineConfigurator AddNode(Func> nodeFactory) + { + _nodes.Add(nodeFactory(new LightPipelineContext(serviceProvider, LightEntity))); + return this; + } + + public ILightTransitionPipelineConfigurator AddReactiveNode( + Action configure) + { + return AddNode(reactiveNodeFactory.CreateReactiveNode(LightEntity, configure)); + } + + public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer) + { + return AddDimmer(dimmer, _ => { }); + } + + public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action dimOptions) + { + return AddReactiveNode(c => + { + c.AddUncoupledDimmer(dimmer, dimOptions); + }); + } + + public ILightTransitionPipelineConfigurator ForLight(string lightEntityId, + Action compositeNodeBuilder) => + ForLights([lightEntityId], compositeNodeBuilder); + + public ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, + Action compositeNodeBuilder) => ForLights([lightEntity], compositeNodeBuilder); + + public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, + Action compositeNodeBuilder) + { + CompositeHelper.ValidateLightEntities(LightEntity.HaContext, lightEntityIds, LightEntity.EntityId); + return this; + } + + public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, + Action compositeNodeBuilder) => ForLights(lightEntities.Select(e => e.EntityId), compositeNodeBuilder); + + public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) => AddNode(lightPipelineFactory.CreateLightPipeline(LightEntity, pipelineNodeOptions)); + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs new file mode 100644 index 0000000..07deddd --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs @@ -0,0 +1,58 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Cycle; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class CompositeLightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) + => AddCycle(triggerObservable, lightParameters.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddCycle(triggerObservable, configure => + { + foreach (var lp in lightParameters) + { + configure.Add(lp); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightTransitions) + => AddCycle(triggerObservable, lightTransitions.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddCycle(triggerObservable, configure => + { + foreach (var lt in lightTransitions) + { + configure.Add(lt); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, Action configure) + { + var cycleConfigurators = configurators.ToDictionary(kvp => kvp.Key, + kvp => new LightTransitionCycleConfigurator(kvp.Value.LightEntity, scheduler)); + var compositeCycleConfigurator = new CompositeLightTransitionCycleConfigurator(cycleConfigurators, []); + configure(compositeCycleConfigurator); + configurators.ForEach(kvp => kvp.Value.AddNodeSource(triggerObservable.ToCycleObservable(cycleConfigurators[kvp.Key].CycleNodeFactories.Select(tuple => + { + var serviceScope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.LightEntity); + var factory = new Func>(() => new ScopedNode(serviceScope, tuple.nodeFactory(context))); + var valueIsActiveFunc = () => tuple.matchesNodeState(context); + return (factory, valueIsActiveFunc); + })))); + return this; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs new file mode 100644 index 0000000..1efd8b3 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -0,0 +1,71 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class CompositeLightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightParameters lightParameters) + => On(triggerObservable, lightParameters.AsTransition()); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func lightParametersFactory) + => On(triggerObservable, c => lightParametersFactory(c).AsTransition()); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightTransition lightTransition) + => On(triggerObservable, c => new StaticLightTransitionNode(lightTransition, c.ServiceProvider.GetRequiredService())); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func lightTransitionFactory) + => On(triggerObservable, c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable) where TNode : IPipelineNode + { + configurators.Values.ForEach(c => c.On(triggerObservable)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func> nodeFactory) + { + configurators.Values.ForEach(c => c.On(triggerObservable, nodeFactory)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) + { + // Note: we create the pipeline in composite context so all configuration is also applied in that context. + var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.LightEntity), + pipelineConfigurator); + configurators.Values.ForEach(c => c.On(triggerObservable, ctx => pipelines[ctx.LightEntity.Id])); + return this; + } + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) + { + // Note: we create the pipeline in composite context so all configuration is also applied in that context. + var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.LightEntity), + configure); + configurators.Values.ForEach(c => c.On(triggerObservable, ctx => nodes[ctx.LightEntity.Id])); + return this; + } + + public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) + { + configurators.Values.ForEach(c => c.PassThroughOn(triggerObservable)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator TurnOffWhen(IObservable triggerObservable) + { + configurators.Values.ForEach(c => c.TurnOffWhen(triggerObservable)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator TurnOnWhen(IObservable triggerObservable) + { + return On(triggerObservable, LightTransition.On()); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs new file mode 100644 index 0000000..616d6d5 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs @@ -0,0 +1,79 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.Toggle; +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class CompositeLightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) + => AddToggle(triggerObservable, lightParameters.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddToggle(triggerObservable, configure => + { + foreach (var lightParameter in lightParameters) + { + configure.Add(lightParameter); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightTransitions) + => AddToggle(triggerObservable, lightTransitions.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddToggle(triggerObservable, configure => + { + foreach (var lightTransition in lightTransitions) + { + configure.Add(lightTransition); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable>> nodeFactories) + => AddToggle(triggerObservable, nodeFactories.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params Func>[] nodeFactories) + { + return AddToggle(triggerObservable, configure => + { + foreach (var fact in nodeFactories) + { + configure.Add(fact); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, Action configure) + { + var toggleConfigurators = configurators.ToDictionary(kvp => kvp.Key, + kvp => new LightTransitionToggleConfigurator(kvp.Value.LightEntity, scheduler)); + var compositeCycleConfigurator = new CompositeLightTransitionToggleConfigurator(toggleConfigurators, []); + configure(compositeCycleConfigurator); + configurators.ForEach(kvp => kvp.Value.AddNodeSource(triggerObservable.ToToggleObservable( + () => configurators.Values.Any(c => c.LightEntity.IsOn()), + () => new TurnOffThenPassThroughNode(), + toggleConfigurators[kvp.Key].NodeFactories.Select(fact => + { + return new Func>(() => + { + var serviceScope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.LightEntity); + return new ScopedNode(serviceScope, fact(context)); + }); + }), + toggleConfigurators[kvp.Key].ToggleTimeout ?? TimeSpan.FromMilliseconds(1000), + toggleConfigurators[kvp.Key].IncludeOffValue))); + return this; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs new file mode 100644 index 0000000..475e9ec --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -0,0 +1,101 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class CompositeLightTransitionReactiveNodeConfigurator( + IServiceProvider serviceProvider, + LightPipelineFactory lightPipelineFactory, + ReactiveNodeFactory reactiveNodeFactory, + Dictionary configurators, + IScheduler scheduler) + : ILightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator SetName(string name) + { + configurators.Values.ForEach(c => c.SetName(name)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator AddReactiveDimmer(IDimmer dimmer) + { + foreach (var configurator in configurators) + { + configurator.Value.AddReactiveDimmer(dimmer); + } + return this; + } + + public ILightTransitionReactiveNodeConfigurator SetReactiveDimmerOptions(DimmerOptions dimmerOptions) + { + foreach (var configurator in configurators) + { + configurator.Value.SetReactiveDimmerOptions(dimmerOptions); + } + return this; + } + + public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer) + { + return AddUncoupledDimmer(dimmer, _ => { }); + } + + public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions) + { + var options = new DimmerOptions(); + dimOptions(options); + + var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); + var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); + + var configuratorsWithLightEntity = options.ValidateAndOrderMultipleLightEntityTypes(configurators).Values.ToArray(); + var lightEntitiesInDimOrder = configuratorsWithLightEntity.Select(t => t.LightEntity).ToArray(); + foreach (var containerAndLight in configuratorsWithLightEntity) + { + // Note: this is not strictly required, but I think it's neater and might prevent issues. + var lightEntitiesInDimOrderWithContainerInstance = lightEntitiesInDimOrder.Select(l => l.EntityId == containerAndLight.lightEntity.EntityId ? containerAndLight.lightEntity : l); + containerAndLight.configurator.AddDimPulses(options, lightEntitiesInDimOrderWithContainerInstance, dimPulses, brightenPulses); + } + return this; + } + + public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) + { + configurators.Values.ForEach(c => c.AddNodeSource(nodeFactorySource)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, Action configure) => ForLights([lightEntityId], configure); + + public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, Action configure) => ForLights([lightEntity], configure); + + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, Action configure) + { + var lightEntityIdsArray = + CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, configurators.Keys); + + if (lightEntityIdsArray.Length == configurators.Count) + { + configure(this); + return this; + } + if (lightEntityIdsArray.Length == 1) + { + configure(configurators[lightEntityIdsArray.First()]); + return this; + } + + configure(new CompositeLightTransitionReactiveNodeConfigurator( + serviceProvider, + lightPipelineFactory, + reactiveNodeFactory, + configurators + .Where(kvp => lightEntityIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), scheduler)); + return this; + } + + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) => ForLights(lightEntities.Select(e => e.EntityId), configure); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Cycle.cs new file mode 100644 index 0000000..7b3a004 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Cycle.cs @@ -0,0 +1,67 @@ +using CodeCasa.AutomationPipelines.Lights.Cycle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial interface ILightTransitionReactiveNodeConfigurator +{ + /// + /// Adds a state-based cycle trigger that rotates through the specified light parameters when triggered by . + /// The cycle advances based on the current light state: if the light matches parameters in the cycle, it advances to the next set. + /// If the current state is not recognized, the cycle starts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next parameters. + /// The collection of light parameters to cycle through. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + IEnumerable lightParameters); + + /// + /// Adds a state-based cycle trigger that rotates through the specified light parameters when triggered by . + /// The cycle advances based on the current light state: if the light matches parameters in the cycle, it advances to the next set. + /// If the current state is not recognized, the cycle starts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next parameters. + /// The array of light parameters to cycle through. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + params LightParameters[] lightParameters); + + /// + /// Adds a state-based cycle trigger that rotates through the specified light transitions when triggered by . + /// The cycle advances based on the current light state: if the light matches a transition in the cycle, it advances to the next transition. + /// If the current state is not recognized, the cycle starts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next transition. + /// The collection of light transitions to cycle through. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + IEnumerable lightTransitions); + + /// + /// Adds a state-based cycle trigger that rotates through the specified light transitions when triggered by . + /// The cycle advances based on the current light state: if the light matches a transition in the cycle, it advances to the next transition. + /// If the current state is not recognized, the cycle starts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next transition. + /// The array of light transitions to cycle through. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + params LightTransition[] lightTransitions); + + /// + /// Adds a state-based cycle trigger configured by the specified action when triggered by . + /// The cycle advances based on the current light state: if the light matches a state in the cycle, it advances to the next state. + /// If the current state is not recognized, the cycle starts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers cycling to the next state. + /// An action to configure the cycle behavior. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + Action configure); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs new file mode 100644 index 0000000..a3e5fa2 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs @@ -0,0 +1,114 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial interface ILightTransitionReactiveNodeConfigurator +{ + /// + /// Registers a trigger that applies the given when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the light parameter application. + /// The light parameters to apply when triggered. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + LightParameters lightParameters); + + /// + /// Registers a trigger that applies light parameters created by when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the light parameter application. + /// A factory function that creates light parameters based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Func lightParametersFactory); + + /// + /// Registers a trigger that applies the given when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the light transition application. + /// The light transition to apply when triggered. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + LightTransition lightTransition); + + /// + /// Registers a trigger that applies a light transition created by when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the light transition application. + /// A factory function that creates a light transition based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Func lightTransitionFactory); + + /// + /// Registers a trigger that activates a pipeline node of type when the emits a value. + /// The node is resolved from the service provider. + /// + /// The type of values emitted by the trigger observable. + /// The type of the pipeline node to resolve and activate. + /// The observable that triggers the node activation. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable) + where TNode : IPipelineNode; + + /// + /// Registers a trigger that activates a pipeline node created by when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the node activation. + /// A factory function that creates a pipeline node based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Func> nodeFactory); + + /// + /// Registers a trigger that activates a nested pipeline configured by when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the pipeline activation. + /// An action to configure the nested pipeline. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Action pipelineConfigurator); + + /// + /// Registers a trigger that activates a nested reactive node configured by when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the reactive node activation. + /// An action to configure the nested reactive node. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Action configure); + + /// + /// Registers a pass-through trigger that allows the current input to pass through unchanged when the emits a value. + /// This is useful for conditional logic where you want to maintain the current state under certain conditions. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers the pass-through behavior. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable); + + /// + /// Registers a trigger that turns off the light when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers turning off the light. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator TurnOffWhen(IObservable triggerObservable); + + /// + /// Registers a trigger that turns on the light when the emits a value. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers turning on the light. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator TurnOnWhen(IObservable triggerObservable); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Toggle.cs new file mode 100644 index 0000000..7d8b7e9 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.Toggle.cs @@ -0,0 +1,85 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Toggle; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial interface ILightTransitionReactiveNodeConfigurator +{ + /// + /// Adds a time-based toggle trigger that switches between the specified light parameters when triggered by . + /// Quick consecutive triggers advance through all parameter sets sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next parameters. + /// The collection of light parameters to toggle between. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightParameters); + + /// + /// Adds a time-based toggle trigger that switches between the specified light parameters when triggered by . + /// Quick consecutive triggers advance through all parameter sets sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next parameters. + /// The array of light parameters to toggle between. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params LightParameters[] lightParameters); + + /// + /// Adds a time-based toggle trigger that switches between the specified light transitions when triggered by . + /// Quick consecutive triggers advance through all transitions sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next transition. + /// The collection of light transitions to toggle between. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + IEnumerable lightTransitions); + + /// + /// Adds a time-based toggle trigger that switches between the specified light transitions when triggered by . + /// Quick consecutive triggers advance through all transitions sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next transition. + /// The array of light transitions to toggle between. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params LightTransition[] lightTransitions); + + /// + /// Adds a time-based toggle trigger that switches between nodes created by the specified factory functions when triggered by . + /// Quick consecutive triggers advance through all node factories sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next node. + /// The collection of factory functions that create pipeline nodes. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + IEnumerable>> nodeFactories); + + /// + /// Adds a time-based toggle trigger that switches between nodes created by the specified factory functions when triggered by . + /// Quick consecutive triggers advance through all node factories sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next node. + /// The array of factory functions that create pipeline nodes. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params Func>[] nodeFactories); + + /// + /// Adds a time-based toggle trigger configured by the specified action when triggered by . + /// Quick consecutive triggers advance through all configured states sequentially. After a timeout period, the next trigger restarts from the beginning. + /// + /// The type of values emitted by the trigger observable. + /// The observable that triggers toggling to the next state. + /// An action to configure the toggle behavior. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + Action configure); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs new file mode 100644 index 0000000..1fee215 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs @@ -0,0 +1,95 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial interface ILightTransitionReactiveNodeConfigurator +{ + /// + /// Sets the name for the current reactive node configuration. + /// + /// The name to assign to the reactive node. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator SetName(string name); + + /// + /// Adds a reactive dimmer control that will be reset when the reactive node activates a new node. + /// Responds to dimmer events and adjusts light parameters accordingly. + /// Multiple reactive dimmers can be added and will behave as a group. + /// + /// The dimmer device to add to the reactive node. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddReactiveDimmer(IDimmer dimmer); + + /// + /// Sets the configuration options for reactive dimmer controls in this node. + /// + /// The dimmer options to configure dimmer behavior. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator SetReactiveDimmerOptions(DimmerOptions dimmerOptions); + + /// + /// Adds an uncoupled dimmer control that operates independently without being affected by the reactive node's behavior. + /// The dimmer will not be reset when the reactive node activates a new node. + /// + /// The dimmer device to add as an uncoupled control. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer); + + /// + /// Adds an uncoupled dimmer control with custom configuration options. + /// The dimmer operates independently without being affected by the reactive node's behavior. + /// The dimmer will not be reset when the reactive node activates a new node. + /// + /// The dimmer device to add as an uncoupled control. + /// An action to configure the dimmer options. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions); + + /// + /// Adds a dynamic node source that activates a new node in the reactive node each time the observable emits a factory. + /// The emitted factory is invoked to create and activate the new pipeline node. + /// + /// An observable that emits factory functions for creating pipeline nodes. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator + AddNodeSource( + IObservable?>> + nodeFactorySource); + + /// + /// Creates a scoped reactive node configuration for a specific light entity identified by its entity ID. + /// + /// The entity ID of the light to configure. + /// An action to configure the reactive node for this specific light. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, + Action configure); + + /// + /// Creates a scoped reactive node configuration for a specific light entity. + /// + /// The light entity to configure. + /// An action to configure the reactive node for this specific light. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, + Action configure); + + /// + /// Creates a scoped reactive node configuration for multiple light entities identified by their entity IDs. + /// + /// The entity IDs of the lights to configure. + /// An action to configure the reactive node for these lights. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, + Action configure); + + /// + /// Creates a scoped reactive node configuration for multiple light entities. + /// + /// The light entities to configure. + /// An action to configure the reactive node for these lights. + /// The configurator instance for method chaining. + ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, + Action configure); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs new file mode 100644 index 0000000..d28cbf4 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs @@ -0,0 +1,57 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Cycle; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class LightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) + => AddCycle(triggerObservable, lightParameters.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddCycle(triggerObservable, configure => + { + foreach (var lp in lightParameters) + { + configure.Add(lp); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightTransitions) + => AddCycle(triggerObservable, lightTransitions.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddCycle(triggerObservable, configure => + { + foreach (var lt in lightTransitions) + { + configure.Add(lt); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, + Action configure) + { + var cycleConfigurator = new LightTransitionCycleConfigurator(LightEntity, scheduler); + configure(cycleConfigurator); + AddNodeSource(triggerObservable.ToCycleObservable(cycleConfigurator.CycleNodeFactories.Select(tuple => + { + var serviceScope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(serviceScope.ServiceProvider, LightEntity); + var factory = new Func>(() => new ScopedNode(serviceScope, tuple.nodeFactory(context))); + var valueIsActiveFunc = () => tuple.matchesNodeState(context); + return (factory, valueIsActiveFunc); + }))); + return this; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs new file mode 100644 index 0000000..aad06ef --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -0,0 +1,59 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class LightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + LightParameters lightParameters) => On(triggerObservable, lightParameters.AsTransition()); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Func lightParametersFactory) + => On(triggerObservable, c => lightParametersFactory(c).AsTransition()); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + LightTransition lightTransition) => + On(triggerObservable, c => new StaticLightTransitionNode(lightTransition, c.ServiceProvider.GetRequiredService())); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Func lightTransitionFactory) + => On(triggerObservable, c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable) + where TNode : IPipelineNode => + AddNodeSource(triggerObservable.Select(_ => + new Func?>(c => + c.ServiceProvider.CreateInstanceWithinContext(c)))); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, + Func> nodeFactory) => + AddNodeSource(triggerObservable.Select(_ => nodeFactory)); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) => On(triggerObservable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) => On(triggerObservable, c => reactiveNodeFactory.CreateReactiveNode(c.LightEntity, configure)); + + public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) + { + AddNodeSource(triggerObservable.Select(_ => new PassThroughNode())); + return this; + } + + public ILightTransitionReactiveNodeConfigurator TurnOffWhen(IObservable triggerObservable) + { + AddNodeSource(triggerObservable.Select(_ => new TurnOffThenPassThroughNode())); + return this; + } + + public ILightTransitionReactiveNodeConfigurator TurnOnWhen(IObservable triggerObservable) + { + return On(triggerObservable, LightTransition.On()); + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs new file mode 100644 index 0000000..6426866 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs @@ -0,0 +1,77 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.Toggle; +using CodeCasa.Lights; +using CodeCasa.Lights.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class LightTransitionReactiveNodeConfigurator +{ + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) + => AddToggle(triggerObservable, lightParameters.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params LightParameters[] lightParameters) + { + return AddToggle(triggerObservable, configure => + { + foreach (var lightParameter in lightParameters) + { + configure.Add(lightParameter); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightTransitions) + => AddToggle(triggerObservable, lightTransitions.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, + params LightTransition[] lightTransitions) + { + return AddToggle(triggerObservable, configure => + { + foreach (var lightTransition in lightTransitions) + { + configure.Add(lightTransition); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable>> nodeFactories) + => AddToggle(triggerObservable, nodeFactories.ToArray()); + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params Func>[] nodeFactories) + { + return AddToggle(triggerObservable, configure => + { + foreach (var fact in nodeFactories) + { + configure.Add(fact); + } + }); + } + + public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, Action configure) + { + var toggleConfigurator = new LightTransitionToggleConfigurator(LightEntity, scheduler); + configure(toggleConfigurator); + AddNodeSource(triggerObservable.ToToggleObservable( + () => LightEntity.IsOn(), + () => new TurnOffThenPassThroughNode(), + toggleConfigurator.NodeFactories.Select(fact => + { + return new Func>(() => + { + var serviceScope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(serviceScope.ServiceProvider, LightEntity); + return new ScopedNode(serviceScope, fact(context)); + }); + }), + toggleConfigurator.ToggleTimeout ?? TimeSpan.FromMilliseconds(1000), + toggleConfigurator.IncludeOffValue)); + return this; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs new file mode 100644 index 0000000..d8e1e21 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs @@ -0,0 +1,106 @@ +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.Abstractions; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public partial class LightTransitionReactiveNodeConfigurator( + IServiceProvider serviceProvider, + LightPipelineFactory lightPipelineFactory, + ReactiveNodeFactory reactiveNodeFactory, + ILight lightEntity, + IScheduler scheduler) : ILightTransitionReactiveNodeConfigurator +{ + public ILight LightEntity { get; } = lightEntity; + + internal string? Name { get; private set; } + internal List?>> NodeObservables { get; } = new(); + internal List Dimmers { get; } = new(); + internal DimmerOptions DimmerOptions { get; private set; } = new (); + + public ILightTransitionReactiveNodeConfigurator SetName(string name) + { + Name = name; + return this; + } + + public ILightTransitionReactiveNodeConfigurator AddReactiveDimmer(IDimmer dimmer) + { + Dimmers.Add(dimmer); + return this; + } + + public ILightTransitionReactiveNodeConfigurator SetReactiveDimmerOptions(DimmerOptions dimmerOptions) + { + DimmerOptions = dimmerOptions; + return this; + } + + public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer) + { + return AddUncoupledDimmer(dimmer, _ => { }); + } + + public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions) + { + var options = new DimmerOptions(); + dimOptions(options); + options.ValidateSingleLightEntity(LightEntity.Id); + + var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); + var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); + + AddDimPulses(options, [LightEntity], dimPulses, brightenPulses); + return this; + } + + internal void AddDimPulses(DimmerOptions options, IEnumerable lightsInDimOrder, IObservable dimPulses, IObservable brightenPulses) + { + var dimHelper = new DimHelper(LightEntity, lightsInDimOrder, options.MinBrightness, options.BrightnessStep); + AddNodeSource(dimPulses + .Select(_ => dimHelper.DimStep()) + .Where(t => t != null) + .Select(t => (IPipelineNode)(t == LightTransition.Off() ? new TurnOffThenPassThroughNode() : new StaticLightTransitionNode(t, scheduler)))); + AddNodeSource(brightenPulses + .Select(_ => dimHelper.BrightenStep()) + .Where(t => t != null) + .Select(t => (IPipelineNode)(t == LightTransition.Off() ? new TurnOffThenPassThroughNode() : new StaticLightTransitionNode(t, scheduler)))); + } + + public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?> nodeSource) + { + NodeObservables.Add(nodeSource); + return this; + } + + public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) + { + return AddNodeSource(nodeFactorySource.Select(f => f(new LightPipelineContext(serviceProvider, LightEntity)))); + } + + public ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, + Action configure) => ForLights([lightEntityId], configure); + + public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, + Action configure) => ForLights([lightEntity], configure); + + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, + Action configure) + { + CompositeHelper.ValidateLightEntities(lightEntityIds, LightEntity.Id); + return this; + } + + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, + Action configure) + { + CompositeHelper.ValidateLightEntities(lightEntities, LightEntity.Id); + return this; + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs new file mode 100644 index 0000000..2fc4e72 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs @@ -0,0 +1,194 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode +{ + // todo: investigate why off is sent twice in a row + public class DimmingContext( + (string entityId, LightParameters? parametersAfterDim)[] dimmerNodeOutputParametersInOrder) + { + public (string entityId, LightParameters? parametersAfterDim)[] DimmerNodeOutputParametersInOrder { get; } = dimmerNodeOutputParametersInOrder; + } + + internal class ReactiveDimmerNode : LightTransitionNode + { + private readonly int _minBrightness; + private readonly int _brightnessStep; + private int _dimSteps; // negative is dimming, positive is brightening. + + public ReactiveDimmerNode( + ReactiveNode reactiveNode, + string lightEntityId, + int minBrightness, + int brightnessStep, + IScheduler scheduler) : base(scheduler) + { + reactiveNode.NodeChanged.Subscribe(_ => Reset()); + PassThrough = true; + + LightEntityId = lightEntityId; + _minBrightness = minBrightness; + _brightnessStep = brightnessStep; + } + + public string LightEntityId { get; } + + public void Reset() + { + PassThrough = true; + _dimSteps = 0; + } + + protected override void InputReceived(LightTransition? input) + { + if (input == null) + { + Output = null; + return; + } + + var newBrightness = CalculateBrightness(input.LightParameters.Brightness ?? 0); + Output = input with { LightParameters = input.LightParameters with { Brightness = newBrightness } }; + } + + public void DimStep(DimmingContext context) + { + if (!ShouldDim(context)) + { + return; + } + _dimSteps--; + if (_dimSteps == 0) + { + PassThrough = true; + return; + } + + ScheduleInterpolatedLightTransition(); + } + + public void BrightenStep(DimmingContext context) + { + if (!ShouldBrighten(context)) + { + return; + } + _dimSteps++; + if (_dimSteps == 0) + { + PassThrough = true; + return; + } + + ScheduleInterpolatedLightTransition(); + } + + private bool ShouldDim(DimmingContext context) + { + var subjectParameters = context.DimmerNodeOutputParametersInOrder.Single(x => x.entityId == LightEntityId).parametersAfterDim; + var subjectBrightness = subjectParameters?.Brightness ?? 0; + if (subjectBrightness > _minBrightness) + { + // If we are brighter than minimum brightness, we have to dim anyway. + return true; + } + if (subjectBrightness <= 0) + { + return false; + } + // At this point we are at min brightness and have to check if any other light is going to dim. If so, we don't have to. + string? lightToTurnOff = null; + foreach (var (entityId, parametersAfterDim) in context.DimmerNodeOutputParametersInOrder) + { + var brightness = parametersAfterDim?.Brightness ?? 0; + if (brightness == 0) + { + continue; + } + if (brightness > _minBrightness) + { + // If any light is brighter than MinBrightness, we let them dim first. + return false; + } + + lightToTurnOff ??= entityId; + } + + return lightToTurnOff == LightEntityId; + } + + private bool ShouldBrighten(DimmingContext context) + { + var subjectParameters = context.DimmerNodeOutputParametersInOrder.Single(x => x.entityId == LightEntityId).parametersAfterDim; + var subjectBrightness = subjectParameters?.Brightness ?? 0; + if (subjectBrightness > _minBrightness) + { + // If we are brighter than minimum brightness, we have to brighten anyway. + return subjectBrightness < byte.MaxValue; + } + // At this point we are either off or at min brightness and have to check if any other light is going to turn on. If so, we don't have to turn on or brighten. + string? lightToTurnOn = null; + foreach (var (entityId, parametersAfterDim) in context.DimmerNodeOutputParametersInOrder.Reverse()) + { + var brightness = parametersAfterDim?.Brightness ?? 0; + if (brightness >= _minBrightness) // On + { + if (lightToTurnOn != null || brightness > _minBrightness) + { + return false; + } + continue; + } + + lightToTurnOn ??= entityId; + } + + return lightToTurnOn == null || lightToTurnOn == LightEntityId; + } + + private void ScheduleInterpolatedLightTransition() + { + if (Input == null) + { + Output = new LightParameters { Brightness = CalculateBrightness(0) }.AsTransition(); + } + else + { + ScheduleInterpolatedLightTransitionUsingInputTransitionTime( + InputLightSourceParameters == null + ? null + : InputLightSourceParameters with + { + Brightness = CalculateBrightness(InputLightSourceParameters.Brightness ?? 0) + }, + Input.LightParameters with + { + Brightness = CalculateBrightness(Input.LightParameters.Brightness ?? 0) + }); + } + } + + private double? CalculateBrightness(double inputBrightness) + { + var calculatedBrightness = inputBrightness + _brightnessStep * _dimSteps; + if (_dimSteps < 0) + { + // Make sure we always show minimum brightness before turning off. + if (calculatedBrightness <= _minBrightness && calculatedBrightness + _brightnessStep > _minBrightness) + { + return _minBrightness; + } + } + if (_dimSteps > 0) + { + // Make sure we always show minimum brightness after turning on. + if (calculatedBrightness > _minBrightness && calculatedBrightness - _brightnessStep <= _minBrightness) + { + return _minBrightness; + } + } + return Math.Min(byte.MaxValue, Math.Max(0, calculatedBrightness)); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs new file mode 100644 index 0000000..4e7417c --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs @@ -0,0 +1,113 @@ +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using CodeCasa.Lights; +using Microsoft.Extensions.Logging; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public class ReactiveNode : PipelineNode +{ + private readonly Lock _lock = new(); + private readonly string? _name; + private readonly ILogger? _logger; + private readonly Subject _nodeChangedSubject = new(); + private IPipelineNode? _activeNode; + private IDisposable? _activeNodeSubscription; + + public ReactiveNode(string? name, IObservable?> nodeObservable, ILogger logger) + { + _name = name; + _logger = logger; + PassThrough = true; + + nodeObservable + .Subscribe(n => + { + lock (_lock) + { + if (n == null) + { + DeactivateActiveNode(); + PassThrough = true; + _logger?.LogTrace($"{LogPrefix}No active node. Passing through data."); + _nodeChangedSubject.OnNext(Unit.Default); + return; + } + + ActivateNode(n); + _nodeChangedSubject.OnNext(Unit.Default); + } + }); + } + + private string LogPrefix => _name == null ? "" : $"{_name}: "; + + public IObservable NodeChanged => _nodeChangedSubject.AsObservable(); + + protected override void InputReceived(LightTransition? input) + { + if (_activeNode == null) + { + return; + } + lock (_lock) + { + if (_activeNode != null) + { + _activeNode.Input = input; + } + } + } + + private void DeactivateActiveNode() + { + _activeNodeSubscription?.Dispose(); + if (_activeNode != null) + { + _activeNode.Input = null; + switch (_activeNode) + { + case IAsyncDisposable asyncDisposable: + asyncDisposable.DisposeAsync().GetAwaiter().GetResult(); + break; + case IDisposable disposable: + disposable.Dispose(); + break; + } + } + + _activeNode = null; + _activeNodeSubscription = null; + } + + private void ActivateNode(IPipelineNode node) + { + DeactivateActiveNode(); + _activeNode = node; + _logger?.LogTrace($"{LogPrefix}Activating {node}."); + // todo: move after input setting? + _activeNodeSubscription = _activeNode.OnNewOutput.Subscribe(output => + { + if (EqualityComparer.Default.Equals(Output, output)) + { + return; + } + lock (_lock) + { + if (!EqualityComparer.Default.Equals(Output, output)) + { + Output = output; + } + } + }); + _activeNode.Input = Input; + if (!EqualityComparer.Default.Equals(Output, _activeNode.Output)) + { + Output = _activeNode.Output; + } + PassThrough = false; + } + + public override string ToString() => _name ?? base.ToString(); +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs new file mode 100644 index 0000000..d02c0ac --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs @@ -0,0 +1,226 @@ +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode +{ + public class ReactiveNodeFactory(IServiceProvider serviceProvider, IScheduler scheduler) + { + public IPipelineNode CreateReactiveNode(ILight lightEntity, Action configure) + { + return CreateReactiveNodes([lightEntity], configure)[lightEntity.Id]; + } + + public Dictionary> CreateReactiveNodes(IEnumerable lightEntities, Action configure) + { + // todo: is this assumption correct? Make internal? + // Note: we simply assume that these are not groups. + var lightEntityArray = lightEntities.ToArray(); + if (!lightEntityArray.Any()) + { + return new Dictionary>(); + } + + var lightPipelineFactory = serviceProvider.GetRequiredService(); // todo + var reactiveConfigurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionReactiveNodeConfigurator(serviceProvider, lightPipelineFactory, + this, l, scheduler)); + ILightTransitionReactiveNodeConfigurator configurator = lightEntityArray.Length == 1 + ? reactiveConfigurators[lightEntityArray[0].Id] + : new CompositeLightTransitionReactiveNodeConfigurator( + serviceProvider, + lightPipelineFactory, + this, + reactiveConfigurators, + scheduler); + configure(configurator); + + /* + * Note: for now this implementation does not support assigning specific dimmers to specific children. + * The nicest way to achieve this would be to create a pulse observable that emits a IDimmer[] for every pulse given, reflecting the dimmers that are currently pushed and providing the pulse. + * This array should then be compared to a dictionary that contains which dimmer node (and entity) relate to which dimmers. + * Then simply build the context and dim/brighten only for those dimmers. + */ + var dimmers = reactiveConfigurators.Values + .SelectMany(rnc => rnc.Dimmers) + .Distinct() + .ToArray(); + + if (!dimmers.Any()) + { + return lightEntityArray.ToDictionary(l => l.Id, l => + { + var reactiveNode = CreateReactiveNode(reactiveConfigurators[l.Id]); + return (IPipelineNode)reactiveNode; + }); + } + + var registrationManager = new RegistrationManager(); + var dimmerNodes = new Dictionary(); + var result = new Dictionary>(); + + foreach (var lightEntity in lightEntityArray) + { + var reactiveNodeConfigurator = reactiveConfigurators[lightEntity.Id]; + var reactiveNode = CreateReactiveNode(reactiveNodeConfigurator); + var lightDimmerOptions = reactiveNodeConfigurator.DimmerOptions; + + var dimmerNode = new ReactiveDimmerNode( + reactiveNode, + lightEntity.Id, + lightDimmerOptions.MinBrightness, + lightDimmerOptions.BrightnessStep, + scheduler); + + dimmerNodes.Add(lightEntity.Id, dimmerNode); + var innerPipeline = new ReactiveDimmerPipeline(reactiveNode, dimmerNode, registrationManager); + + result.Add(lightEntity.Id, innerPipeline); + } + + /* + * Note: for now this implementation does not support assigning specific dimmers to specific children. + * The same is true for the options. We simply pick the first as all options will be set to the same value. + * If this ever changes, time between steps and entity order should be extracted to apply to every dimmer while the other properties can be applied to individual ones. + */ + var dimmerOptions = reactiveConfigurators.First().Value.DimmerOptions; + var dimmer = dimmers.Length > 1 ? new CompositeDimmer(dimmers) : dimmers[0]; + + var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(dimmerOptions.TimeBetweenSteps, scheduler); + var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(dimmerOptions.TimeBetweenSteps, scheduler); + + var orderedDimNodes = dimmerOptions.ValidateAndOrderMultipleLightEntityTypes(dimmerNodes); + + var dimSubscriptionDisposables = new CompositeDisposable(); + SubscribeToPulses(dimPulses, dimmerNodes, orderedDimNodes, dimSubscriptionDisposables, + (context, dn) => dn.DimStep(context)); + SubscribeToPulses(brightenPulses, dimmerNodes, orderedDimNodes, dimSubscriptionDisposables, + (context, dn) => dn.BrightenStep(context)); + + var lastUnregisteredSubscription = registrationManager.LastUnregistered.Subscribe(_ => + { + dimSubscriptionDisposables.Dispose(); + }); + + dimSubscriptionDisposables.Add(lastUnregisteredSubscription); + + return result; + } + + private ReactiveNode CreateReactiveNode(LightTransitionReactiveNodeConfigurator reactiveNodeConfigurator) + { + return new ReactiveNode( + reactiveNodeConfigurator.Name, + reactiveNodeConfigurator.NodeObservables.Merge(), + serviceProvider.GetRequiredService>()); + } + + private void SubscribeToPulses( + IObservable pulses, + Dictionary dimmerNodes, + OrderedDictionary orderedDimNodes, + CompositeDisposable compositeDisposable, + Action dimmerAction) + { + compositeDisposable.Add(pulses.Subscribe(_ => + { + var context = CreateDimmingContext(orderedDimNodes); + dimmerNodes.Values.ForEach(dn => dimmerAction(context, dn)); + })); + } + + private DimmingContext CreateDimmingContext(OrderedDictionary orderedDimNodes) + { + return new DimmingContext(orderedDimNodes + .Select(kvp => (kvp.Key, kvp.Value.Output?.LightParameters)).ToArray()); + } + } + + internal class ReactiveDimmerPipeline : Pipeline + { + private readonly RegisterInterface _registerInterface; + + public ReactiveDimmerPipeline( + ReactiveNode reactiveNode, + ReactiveDimmerNode reactiveDimmerNode, + RegisterInterface registerInterface) + { + _registerInterface = registerInterface; + _registerInterface.Register(this); + RegisterNode(reactiveNode); + RegisterNode(reactiveDimmerNode); + } + + public override ValueTask DisposeAsync() + { + _registerInterface.Unregister(this); + return base.DisposeAsync(); + } + } + + internal interface RegisterInterface + { + void Register(T reference); + void Unregister(T reference); + } + + internal sealed class RegistrationManager : RegisterInterface, IDisposable + { + private readonly HashSet _items = new HashSet(); + private readonly object _lock = new object(); + private readonly Subject _lastUnregistered = new Subject(); + private bool _isDisposed; + + public IObservable LastUnregistered => _lastUnregistered; + + public void Register(T reference) + { + lock (_lock) + { + if (_isDisposed) + throw new ObjectDisposedException(nameof(RegistrationManager)); + + _items.Add(reference); + } + } + + public void Unregister(T reference) + { + bool becameEmpty = false; + + lock (_lock) + { + if (_isDisposed) + return; + + if (_items.Remove(reference) && _items.Count == 0) + becameEmpty = true; + } + + if (becameEmpty) + _lastUnregistered.OnNext(Unit.Default); + } + + public void Dispose() + { + lock (_lock) + { + if (_isDisposed) + return; + + _isDisposed = true; + _items.Clear(); + } + + _lastUnregistered.OnCompleted(); + _lastUnregistered.Dispose(); + } + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs new file mode 100644 index 0000000..6751e5b --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs @@ -0,0 +1,138 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Toggle +{ + internal class CompositeLightTransitionToggleConfigurator( + Dictionary activeConfigurators, + Dictionary inactiveConfigurators) : ILightTransitionToggleConfigurator + { + public ILightTransitionToggleConfigurator SetToggleTimeout(TimeSpan timeout) + { + activeConfigurators.Values.ForEach(c => c.SetToggleTimeout(timeout)); + inactiveConfigurators.Values.ForEach(c => c.SetToggleTimeout(timeout)); + return this; + } + + public ILightTransitionToggleConfigurator IncludeOffInToggleCycle() + { + activeConfigurators.Values.ForEach(c => c.IncludeOffInToggleCycle()); + inactiveConfigurators.Values.ForEach(c => c.IncludeOffInToggleCycle()); + return this; + } + + public ILightTransitionToggleConfigurator ExcludeOffFromToggleCycle() + { + activeConfigurators.Values.ForEach(c => c.ExcludeOffFromToggleCycle()); + inactiveConfigurators.Values.ForEach(c => c.ExcludeOffFromToggleCycle()); + return this; + } + + public ILightTransitionToggleConfigurator AddOff() + { + return Add(); + } + + public ILightTransitionToggleConfigurator AddOn() + { + return Add(LightTransition.On()); + } + + public ILightTransitionToggleConfigurator Add(LightParameters lightParameters) + { + return Add(lightParameters.AsTransition()); + } + + public ILightTransitionToggleConfigurator Add(Func lightParametersFactory) + { + return Add(c => lightParametersFactory(c)?.AsTransition()); + } + + public ILightTransitionToggleConfigurator Add(Func lightParametersFactory) + { + return Add((c, t) => lightParametersFactory(c, t)?.AsTransition()); + } + + public ILightTransitionToggleConfigurator Add(LightTransition lightTransition) + { + return Add(_ => lightTransition); + } + + public ILightTransitionToggleConfigurator Add(Func lightTransitionFactory) + { + return Add(c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + } + + public ILightTransitionToggleConfigurator Add(Func lightTransitionFactory) + { + return Add(c => new FactoryNode(t => lightTransitionFactory(c, t))); + } + + public ILightTransitionToggleConfigurator Add() where TNode : IPipelineNode + { + activeConfigurators.Values.ForEach(c => c.Add()); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough()); + return this; + } + + public ILightTransitionToggleConfigurator Add(Func> nodeFactory) + { + activeConfigurators.Values.ForEach(c => c.Add(nodeFactory)); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough()); + return this; + } + + public ILightTransitionToggleConfigurator AddPassThrough() + { + activeConfigurators.Values.ForEach(c => c.AddPassThrough()); + inactiveConfigurators.Values.ForEach(c => c.AddPassThrough()); + return this; + } + + public ILightTransitionToggleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + + public ILightTransitionToggleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, + Action configure, + ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + var lightEntityIdsArray = + CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, activeConfigurators.Keys); + + if (lightEntityIdsArray.Length == activeConfigurators.Count) + { + configure(this); + return this; + } + + if (excludedLightBehaviour == ExcludedLightBehaviours.None) + { + if (lightEntityIdsArray.Length == 1) + { + configure(activeConfigurators[lightEntityIdsArray.First()]); + return this; + } + + configure(new CompositeLightTransitionToggleConfigurator( + activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), [])); + return this; + } + + configure(new CompositeLightTransitionToggleConfigurator( + activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + activeConfigurators.Where(kvp => !lightEntityIdsArray.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); + return this; + } + + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.EntityId), configure, excludedLightBehaviour); + + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs new file mode 100644 index 0000000..ed2db10 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs @@ -0,0 +1,145 @@ +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Lights; + +namespace CodeCasa.AutomationPipelines.Lights.Toggle +{ + /// + /// Configurator for time-based toggle behavior. Quick consecutive triggers advance through all states sequentially. + /// After a timeout period, the next trigger restarts from the beginning. + /// + public interface ILightTransitionToggleConfigurator + { + /// + /// Sets the timeout duration after which the toggle cycle restarts from the beginning. + /// + /// The timeout duration between triggers that determines when to restart the cycle. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator SetToggleTimeout(TimeSpan timeout); + + /// + /// Includes the "off" state in the toggle cycle. When the light is off and toggled, it will advance to the first state in the cycle. + /// + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator IncludeOffInToggleCycle(); + + /// + /// Excludes the "off" state from the toggle cycle. When the light is off and toggled, it will turn on to the first state but "off" won't be part of the sequential cycle. + /// + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator ExcludeOffFromToggleCycle(); + + /// + /// Adds an "off" state to the toggle sequence. + /// + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator AddOff(); + + /// + /// Adds an "on" state to the toggle sequence. + /// + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator AddOn(); + + /// + /// Adds light parameters to the toggle sequence. Quick consecutive triggers will advance through all added parameter sets. + /// + /// The light parameters to add to the toggle sequence. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(LightParameters lightParameters); + + /// + /// Adds light parameters created by a factory to the toggle sequence. + /// + /// A factory function that creates light parameters based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(Func lightParametersFactory); + + /// + /// Adds light parameters created by a factory to the toggle sequence. + /// The factory receives both the pipeline context and the current light transition. + /// + /// A factory function that creates light parameters based on the pipeline context and current transition. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(Func lightParametersFactory); + + /// + /// Adds a light transition to the toggle sequence. Quick consecutive triggers will advance through all added transitions. + /// + /// The light transition to add to the toggle sequence. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(LightTransition lightTransition); + + /// + /// Adds a light transition created by a factory to the toggle sequence. + /// + /// A factory function that creates a light transition based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(Func lightTransitionFactory); + + /// + /// Adds a light transition created by a factory to the toggle sequence. + /// The factory receives both the pipeline context and the current light transition. + /// + /// A factory function that creates a light transition based on the pipeline context and current transition. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(Func lightTransitionFactory); + + /// + /// Adds a pipeline node of type to the toggle sequence. + /// The node is resolved from the service provider. Quick consecutive triggers will advance through all added nodes. + /// + /// The type of the pipeline node to add to the toggle sequence. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add() where TNode : IPipelineNode; + + /// + /// Adds a pipeline node created by a factory to the toggle sequence. + /// Quick consecutive triggers will advance through all added nodes. + /// + /// A factory function that creates a pipeline node based on the pipeline context. + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator Add(Func> nodeFactory); + + /// + /// Adds a pass-through state to the toggle sequence that maintains the current light state. + /// + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator AddPassThrough(); + + /// + /// Creates a scoped toggle configuration for a specific light entity identified by its entity ID. + /// + /// The entity ID of the light to configure. + /// An action to configure the toggle for this specific light. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + + /// + /// Creates a scoped toggle configuration for a specific light entity. + /// + /// The light entity to configure. + /// An action to configure the toggle for this specific light. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + + /// + /// Creates a scoped toggle configuration for multiple light entities identified by their entity IDs. + /// + /// The entity IDs of the lights to configure. + /// An action to configure the toggle for these lights. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + + /// + /// Creates a scoped toggle configuration for multiple light entities. + /// + /// The light entities to configure. + /// An action to configure the toggle for these lights. + /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . + /// The configurator instance for method chaining. + ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + } +} diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs new file mode 100644 index 0000000..5c65f4a --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs @@ -0,0 +1,113 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; + +namespace CodeCasa.AutomationPipelines.Lights.Toggle +{ + internal class LightTransitionToggleConfigurator(ILight lightEntity, IScheduler scheduler) : ILightTransitionToggleConfigurator + { + public ILight LightEntity { get; } = lightEntity; + internal TimeSpan? ToggleTimeout { get; private set; } + internal bool? IncludeOffValue { get; private set; } + internal List>> NodeFactories + { + get; + } = []; + + public ILightTransitionToggleConfigurator SetToggleTimeout(TimeSpan timeout) + { + ToggleTimeout = timeout; + return this; + } + + public ILightTransitionToggleConfigurator IncludeOffInToggleCycle() + { + IncludeOffValue = true; + return this; + } + + public ILightTransitionToggleConfigurator ExcludeOffFromToggleCycle() + { + IncludeOffValue = false; + return this; + } + + public ILightTransitionToggleConfigurator AddOff() + { + return Add(); + } + + public ILightTransitionToggleConfigurator AddOn() + { + return Add(LightTransition.On()); + } + + + public ILightTransitionToggleConfigurator Add(LightParameters lightParameters) + { + return Add(lightParameters.AsTransition()); + } + + public ILightTransitionToggleConfigurator Add(Func lightParametersFactory) + { + return Add(c => lightParametersFactory(c)?.AsTransition()); + } + + public ILightTransitionToggleConfigurator Add(Func lightParametersFactory) + { + return Add((c, t) => lightParametersFactory(c, t)?.AsTransition()); + } + + public ILightTransitionToggleConfigurator Add(LightTransition lightTransition) + { + return Add(new StaticLightTransitionNode(lightTransition, scheduler)); + } + + public ILightTransitionToggleConfigurator Add(Func lightTransitionFactory) + { + return Add(c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + } + + public ILightTransitionToggleConfigurator Add(Func lightTransitionFactory) + { + return Add(c => new FactoryNode(t => lightTransitionFactory(c, t))); + } + + public ILightTransitionToggleConfigurator Add() where TNode : IPipelineNode + { + return Add(c => c.ServiceProvider.CreateInstanceWithinContext(c)); + } + + public ILightTransitionToggleConfigurator Add(IPipelineNode node) + { + return Add(_ => node); + } + + public ILightTransitionToggleConfigurator Add(Func> nodeFactory) + { + NodeFactories.Add(nodeFactory); + return this; + } + + public ILightTransitionToggleConfigurator AddPassThrough() + { + return Add(new PassThroughNode()); + } + + public ILightTransitionToggleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + + public ILightTransitionToggleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + CompositeHelper.ValidateLightEntities(LightEntity.HaContext, lightEntityIds, LightEntity.EntityId); + return this; + } + + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.EntityId), configure, excludedLightBehaviour); + + } +} From b6139cbb3b2cdfc266f168919b0312e5af953137 Mon Sep 17 00:00:00 2001 From: Jasper Date: Sun, 21 Dec 2025 10:17:13 +0100 Subject: [PATCH 02/18] Fixed the ForLight(s) methods. --- .../CompositeHelper.cs | 55 ++++++++----------- ...mpositeLightTransitionCycleConfigurator.cs | 22 +++++--- .../Cycle/LightTransitionCycleConfigurator.cs | 8 ++- .../Nodes/CompositeDimmer.cs | 1 + ...siteLightTransitionPipelineConfigurator.cs | 9 ++- .../ILightTransitionPipelineConfigurator.cs | 3 +- .../LightTransitionPipelineConfigurator.cs | 9 ++- ...LightTransitionReactiveNodeConfigurator.cs | 9 ++- ...LightTransitionReactiveNodeConfigurator.cs | 3 +- ...LightTransitionReactiveNodeConfigurator.cs | 4 +- ...positeLightTransitionToggleConfigurator.cs | 2 +- .../LightTransitionToggleConfigurator.cs | 2 +- 12 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs b/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs index cbea9f4..dcc56af 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/CompositeHelper.cs @@ -5,65 +5,56 @@ namespace CodeCasa.AutomationPipelines.Lights { internal static class CompositeHelper { - // todo: check and filter without errors. - public static string[] ResolveAndValidateLightEntities(IEnumerable lightEntityIds, IEnumerable supportedLightIds) + public static string[] ValidateLightsSupported(IEnumerable lightIds, IEnumerable supportedLightIds) { var supportedLightIdsArray = supportedLightIds.ToArray(); - var lightLeafs = lightEntityIds.SelectMany(le => le.Flatten()).DistinctBy(l => l.Id).ToArray(); - if (!lightLeafs.Any()) + var lightIdsArray = lightIds.ToArray(); + if (!lightIdsArray.Any()) { - throw new ArgumentException("At least one entity id should be provided.", nameof(lightLeafs)); + throw new ArgumentException("At least one id should be provided.", nameof(lightIdsArray)); } - var missingLights = lightLeafs - .Where(id => !supportedLightIdsArray.Contains(id.Id)) + var unsupportedLightIds = lightIdsArray + .Where(id => !supportedLightIdsArray.Contains(id)) .ToArray(); - if (missingLights.Any()) + if (unsupportedLightIds.Any()) { throw new InvalidOperationException( - $"The following light entities are not supported: {string.Join(", ", missingLights)}."); + $"The following lights are not supported: {string.Join(", ", unsupportedLightIds)}."); } - return lightLeafs.Select(l => l.Id).ToArray(); + return lightIdsArray.ToArray(); } - public static void ValidateLightEntities(IEnumerable lights, string supportedLightId) + public static void ValidateLightSupported(IEnumerable lights, string supportedLightId) { - var lightLeafs = lights.SelectMany(le => le.Flatten()).DistinctBy(l => l.Id).ToArray(); - if (!lightLeafs.Any()) + var lightsArray = lights.ToArray(); + if (!lightsArray.Any()) { - throw new ArgumentException("At least one entity id should be provided.", nameof(lightLeafs)); + throw new ArgumentException("At least one id should be provided.", nameof(lightsArray)); } - var missingLights = lightLeafs - .Where(l => l.Id != supportedLightId) + var unsupportedLightIds = lightsArray + .Where(id => id != supportedLightId) .ToArray(); - if (missingLights.Any()) + if (unsupportedLightIds.Any()) { throw new InvalidOperationException( - $"The following light entities are not supported: {string.Join(", ", missingLights)}."); + $"The following lights are not supported: {string.Join(", ", unsupportedLightIds)}."); } } - public static void ValidateLightEntities(IEnumerable lights, string supportedLightId) + public static string[] ResolveGroupsAndValidateLightsSupported(IEnumerable lights, IEnumerable supportedLightIds) { - var lightsArray = lights.ToArray(); - if (!lightsArray.Any()) - { - throw new ArgumentException("At least one entity id should be provided.", nameof(lightsArray)); - } + return ValidateLightsSupported(lights.SelectMany(le => le.Flatten()).Select(l => l.Id).Distinct(), supportedLightIds); + } - var missingLights = lightsArray - .Where(id => id != supportedLightId) - .ToArray(); - if (missingLights.Any()) - { - throw new InvalidOperationException( - $"The following light entities are not supported: {string.Join(", ", missingLights)}."); - } + public static void ResolveGroupsAndValidateLightSupported(IEnumerable lights, string supportedLightId) + { + ValidateLightSupported(lights.SelectMany(le => le.Flatten()).Select(l => l.Id).Distinct(), supportedLightId); } } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs index 59758f7..3c02051 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs @@ -85,10 +85,10 @@ public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEnti Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - var lightEntityIdsArray = - CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, activeConfigurators.Keys); + var lightIds = + CompositeHelper.ValidateLightsSupported(lightEntityIds, activeConfigurators.Keys); - if (lightEntityIdsArray.Length == activeConfigurators.Count) + if (lightIds.Length == activeConfigurators.Count) { configure(this); return this; @@ -96,25 +96,29 @@ public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEnti if (excludedLightBehaviour == ExcludedLightBehaviours.None) { - if (lightEntityIdsArray.Length == 1) + if (lightIds.Length == 1) { - configure(activeConfigurators[lightEntityIdsArray.First()]); + configure(activeConfigurators[lightIds.First()]); return this; } configure(new CompositeLightTransitionCycleConfigurator( - activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + activeConfigurators.Where(kvp => lightIds.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), [])); return this; } configure(new CompositeLightTransitionCycleConfigurator( - activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + activeConfigurators.Where(kvp => lightIds.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - activeConfigurators.Where(kvp => !lightEntityIdsArray.Contains(kvp.Key)) + activeConfigurators.Where(kvp => !lightIds.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); return this; } - public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.Id), configure, excludedLightBehaviour); + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + var lightIds = CompositeHelper.ResolveGroupsAndValidateLightsSupported(lightEntities, activeConfigurators.Keys); + return ForLights(lightIds, configure, excludedLightBehaviour); + } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs index e8055e2..9b9bd53 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs @@ -83,9 +83,13 @@ public ILightTransitionCycleConfigurator AddPassThrough(Func lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ValidateLightEntities(lightEntityIds, LightEntity.Id); + CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); return this; } - public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.EntityId), configure, excludedLightBehaviour); + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + return this; + } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs index 62dc598..2f18210 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs @@ -1,4 +1,5 @@ using System.Reactive.Linq; +using CodeCasa.Abstractions; namespace CodeCasa.AutomationPipelines.Lights.Nodes diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs index 6c98077..427ab2c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs @@ -1,4 +1,5 @@ using System.Reactive.Concurrency; +using CodeCasa.Abstractions; using CodeCasa.AutomationPipelines.Lights.Context; using CodeCasa.AutomationPipelines.Lights.Extensions; using CodeCasa.AutomationPipelines.Lights.ReactiveNode; @@ -74,7 +75,7 @@ public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightE Action compositeNodeBuilder) { var lightEntityIdsArray = - CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, NodeContainers.Keys); + CompositeHelper.ValidateLightsSupported(lightEntityIds, NodeContainers.Keys); if (lightEntityIdsArray.Length == NodeContainers.Count) { @@ -94,6 +95,10 @@ public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightE } public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, - Action compositeNodeBuilder) => ForLights(lightEntities.Select(e => e.Id), compositeNodeBuilder); + Action compositeNodeBuilder) + { + var lightIds = CompositeHelper.ResolveGroupsAndValidateLightsSupported(lightEntities, NodeContainers.Keys); + return ForLights(lightIds, compositeNodeBuilder); + } } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs index 6f23a63..91ef0a3 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs @@ -1,4 +1,5 @@ -using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Abstractions; +using CodeCasa.AutomationPipelines.Lights.Context; using CodeCasa.AutomationPipelines.Lights.ReactiveNode; using CodeCasa.Lights; diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs index d3ca556..91bd63f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs @@ -2,6 +2,7 @@ using CodeCasa.AutomationPipelines.Lights.Extensions; using CodeCasa.AutomationPipelines.Lights.ReactiveNode; using System.Reactive.Concurrency; +using CodeCasa.Abstractions; using CodeCasa.Lights; namespace CodeCasa.AutomationPipelines.Lights.Pipeline @@ -80,12 +81,16 @@ public ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, Action compositeNodeBuilder) { - CompositeHelper.ValidateLightEntities(LightEntity.HaContext, lightEntityIds, LightEntity.EntityId); + CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); return this; } public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, - Action compositeNodeBuilder) => ForLights(lightEntities.Select(e => e.EntityId), compositeNodeBuilder); + Action compositeNodeBuilder) + { + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + return this; + } public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) => AddNode(lightPipelineFactory.CreateLightPipeline(LightEntity, pipelineNodeOptions)); } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index 475e9ec..291e215 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -1,4 +1,5 @@ using System.Reactive.Concurrency; +using CodeCasa.Abstractions; using CodeCasa.AutomationPipelines.Lights.Context; using CodeCasa.AutomationPipelines.Lights.Extensions; using CodeCasa.AutomationPipelines.Lights.Pipeline; @@ -75,7 +76,7 @@ public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable lightEntityIds, Action configure) { var lightEntityIdsArray = - CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, configurators.Keys); + CompositeHelper.ValidateLightsSupported(lightEntityIds, configurators.Keys); if (lightEntityIdsArray.Length == configurators.Count) { @@ -97,5 +98,9 @@ public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable li return this; } - public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) => ForLights(lightEntities.Select(e => e.EntityId), configure); + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) + { + var lightIds = CompositeHelper.ResolveGroupsAndValidateLightsSupported(lightEntities, configurators.Keys); + return ForLights(lightIds, configure); + } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs index 1fee215..e02779f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs @@ -1,4 +1,5 @@ -using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Abstractions; +using CodeCasa.AutomationPipelines.Lights.Context; using CodeCasa.Lights; namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs index d8e1e21..c3e3732 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs @@ -93,14 +93,14 @@ public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, Action configure) { - CompositeHelper.ValidateLightEntities(lightEntityIds, LightEntity.Id); + CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); return this; } public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) { - CompositeHelper.ValidateLightEntities(lightEntities, LightEntity.Id); + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); return this; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs index 6751e5b..f7d2560 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs @@ -102,7 +102,7 @@ public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEnt ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { var lightEntityIdsArray = - CompositeHelper.ResolveAndValidateLightEntities(lightEntityIds, activeConfigurators.Keys); + CompositeHelper.ValidateLightsSupported(lightEntityIds, activeConfigurators.Keys); if (lightEntityIdsArray.Length == activeConfigurators.Count) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs index 5c65f4a..bff04ad 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs @@ -103,7 +103,7 @@ public ILightTransitionToggleConfigurator AddPassThrough() public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ValidateLightEntities(LightEntity.HaContext, lightEntityIds, LightEntity.EntityId); + CompositeHelper.ValidateLightSupported(LightEntity.HaContext, lightEntityIds, LightEntity.EntityId); return this; } From 0b5cafdf5570effa9d59e211d56a93070d1d3e73 Mon Sep 17 00:00:00 2001 From: Jasper Date: Sun, 21 Dec 2025 10:42:31 +0100 Subject: [PATCH 03/18] Added parameter equality comparer. --- .../CompositeLightTransitionCycleConfigurator.cs | 13 +++++++++---- .../Cycle/ILightTransitionCycleConfigurator.cs | 4 ++-- .../Cycle/LightTransitionCycleConfigurator.cs | 11 +++++++---- .../Extensions/LightTransitionNodeExtensions.cs | 6 +++--- ...positeLightTransitionReactiveNodeConfigurator.cs | 6 +++--- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs index 3c02051..6aff42f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs @@ -26,9 +26,9 @@ public ILightTransitionCycleConfigurator AddOn() return Add(LightTransition.On()); } - public ILightTransitionCycleConfigurator Add(LightParameters lightParameters) + public ILightTransitionCycleConfigurator Add(LightParameters lightParameters, IEqualityComparer? comparer = null) { - return Add(lightParameters.AsTransition()); + return Add(lightParameters.AsTransition(), comparer); } public ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState) @@ -41,9 +41,14 @@ public ILightTransitionCycleConfigurator Add(Func lightParametersFactory(c, t)?.AsTransition(), matchesNodeState); } - public ILightTransitionCycleConfigurator Add(LightTransition lightTransition) + public ILightTransitionCycleConfigurator Add(LightTransition lightTransition, IEqualityComparer? comparer = null) { - return Add(_ => lightTransition, _ => activeConfigurators.Values.All(c => c.LightEntity.SceneEquals(lightTransition.LightParameters))); + comparer ??= EqualityComparer.Default; + return Add( + _ => lightTransition, + _ => activeConfigurators.Values.All(c => comparer.Equals( + c.LightEntity.GetParameters(), + lightTransition.LightParameters))); } public ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs index b135e08..c5944a2 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs @@ -27,7 +27,7 @@ public interface ILightTransitionCycleConfigurator /// /// The light parameters to add to the cycle. /// The configurator instance for method chaining. - ILightTransitionCycleConfigurator Add(LightParameters lightParameters); + ILightTransitionCycleConfigurator Add(LightParameters lightParameters, IEqualityComparer? comparer = null); /// /// Adds light parameters created by a factory to the cycle, with a custom state matching function. @@ -53,7 +53,7 @@ public interface ILightTransitionCycleConfigurator /// /// The light transition to add to the cycle. /// The configurator instance for method chaining. - ILightTransitionCycleConfigurator Add(LightTransition lightTransition); + ILightTransitionCycleConfigurator Add(LightTransition lightTransition, IEqualityComparer? comparer = null); /// /// Adds a light transition created by a factory to the cycle, with a custom state matching function. diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs index 9b9bd53..d867339 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs @@ -26,9 +26,9 @@ public ILightTransitionCycleConfigurator AddOn() return Add(LightTransition.On()); } - public ILightTransitionCycleConfigurator Add(LightParameters lightParameters) + public ILightTransitionCycleConfigurator Add(LightParameters lightParameters, IEqualityComparer? comparer = null) { - return Add(lightParameters.AsTransition()); + return Add(lightParameters.AsTransition(), comparer); } public ILightTransitionCycleConfigurator Add(Func lightParametersFactory, Func matchesNodeState) @@ -41,9 +41,12 @@ public ILightTransitionCycleConfigurator Add(Func lightParametersFactory(c, t)?.AsTransition(), matchesNodeState); } - public ILightTransitionCycleConfigurator Add(LightTransition lightTransition) + public ILightTransitionCycleConfigurator Add(LightTransition lightTransition, IEqualityComparer? comparer = null) { - return Add(new StaticLightTransitionNode(lightTransition, scheduler), _ => LightEntity.SceneEquals(lightTransition.LightParameters)); + comparer ??= EqualityComparer.Default; + return Add(new StaticLightTransitionNode(lightTransition, scheduler), _ => comparer.Equals( + LightEntity.GetParameters(), + lightTransition.LightParameters)); } public ILightTransitionCycleConfigurator Add(Func lightTransitionFactory, Func matchesNodeState) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs index 6917073..a5d0379 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs @@ -10,13 +10,13 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions { public static class LightTransitionNodeExtensions { - public static IPipelineNode LightSceneThatTurnsOffAfter(this ILightPipelineContext context, - LightSceneTemplate lightSceneTemplate, + public static IPipelineNode LightParametersThatTurnsOffAfter(this ILightPipelineContext context, + LightParameters lightParameters, TimeSpan timeSpan, IObservable resetTimerObservable) { // todo: move to diferent project var scheduler = context.ServiceProvider.GetRequiredService(); - var innerNode = new StaticLightTransitionNode(lightSceneTemplate(context.LightEntity).AsTransition(), scheduler); + var innerNode = new StaticLightTransitionNode(lightParameters.AsTransition(), scheduler); return innerNode.TurnOffAfter(timeSpan, resetTimerObservable, scheduler); } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index 291e215..0ec03a9 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -52,12 +52,12 @@ public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimme var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); - var configuratorsWithLightEntity = options.ValidateAndOrderMultipleLightEntityTypes(configurators).Values.ToArray(); - var lightEntitiesInDimOrder = configuratorsWithLightEntity.Select(t => t.LightEntity).ToArray(); + var configuratorsWithLightEntity = options.ValidateAndOrderMultipleLightEntityTypes(configurators).Select(kvp => (configurator: kvp.Value, lightId: kvp.Key)).ToArray(); + var lightEntitiesInDimOrder = configuratorsWithLightEntity.Select(t => t.lightId).ToArray(); foreach (var containerAndLight in configuratorsWithLightEntity) { // Note: this is not strictly required, but I think it's neater and might prevent issues. - var lightEntitiesInDimOrderWithContainerInstance = lightEntitiesInDimOrder.Select(l => l.EntityId == containerAndLight.lightEntity.EntityId ? containerAndLight.lightEntity : l); + var lightEntitiesInDimOrderWithContainerInstance = lightEntitiesInDimOrder.Select(l => l == containerAndLight.lightId ? containerAndLight.lightId : l); containerAndLight.configurator.AddDimPulses(options, lightEntitiesInDimOrderWithContainerInstance, dimPulses, brightenPulses); } return this; From ca7d14c8e0cfc2a9a8b9890db5965ab93980a8fc Mon Sep 17 00:00:00 2001 From: Jasper Date: Sun, 21 Dec 2025 10:44:15 +0100 Subject: [PATCH 04/18] Fixed the ForLights method in the toggleconfigurator. --- .../Toggle/CompositeLightTransitionToggleConfigurator.cs | 7 +++++-- .../Toggle/LightTransitionToggleConfigurator.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs index f7d2560..83b4cf4 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs @@ -132,7 +132,10 @@ public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEnt return this; } - public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.EntityId), configure, excludedLightBehaviour); - + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + var lightIds = CompositeHelper.ResolveGroupsAndValidateLightsSupported(lightEntities, activeConfigurators.Keys); + return ForLights(lightIds, configure, excludedLightBehaviour); + } } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs index bff04ad..9618f73 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs @@ -103,11 +103,15 @@ public ILightTransitionToggleConfigurator AddPassThrough() public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ValidateLightSupported(LightEntity.HaContext, lightEntityIds, LightEntity.EntityId); + CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); return this; } - public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights(lightEntities.Select(e => e.EntityId), configure, excludedLightBehaviour); + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + { + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + return this; + } } } From 79fcb7eaf1c32afa839ed6450d5337e65e31706c Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 10:02:50 +0100 Subject: [PATCH 05/18] Adding NetDaemon project. --- CodeCasa.sln | 6 ++++++ .../CodeCasa.Abstractions.csproj | 4 ---- ...sa.AutomationPipelines.Lights.NetDaemon.csproj | 14 ++++++++++++++ .../Extensions/LightPipelineFactoryExtensions.cs | 15 +++++++++++++++ ...Extensions.cs => LightEntityCoreExtensions.cs} | 7 ++++++- 5 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj create mode 100644 src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs rename src/CodeCasa.Lights.NetDaemon/Extensions/{LightEntityCodeExtensions.cs => LightEntityCoreExtensions.cs} (98%) diff --git a/CodeCasa.sln b/CodeCasa.sln index 4836c87..00c7ba1 100644 --- a/CodeCasa.sln +++ b/CodeCasa.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights.NetDaemon", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines.Lights", "src\CodeCasa.AutomationPipelines.Lights\CodeCasa.AutomationPipelines.Lights.csproj", "{A66370F1-3944-4EAA-82EF-6A321BF273D1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines.Lights.NetDaemon", "src\CodeCasa.AutomationPipelines.Lights.NetDaemon\CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj", "{039D5B9D-F313-4DC5-9A12-FF2A1FDEB15C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -95,6 +97,10 @@ Global {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {A66370F1-3944-4EAA-82EF-6A321BF273D1}.Release|Any CPU.Build.0 = Release|Any CPU + {039D5B9D-F313-4DC5-9A12-FF2A1FDEB15C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {039D5B9D-F313-4DC5-9A12-FF2A1FDEB15C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {039D5B9D-F313-4DC5-9A12-FF2A1FDEB15C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {039D5B9D-F313-4DC5-9A12-FF2A1FDEB15C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/CodeCasa.Abstractions/CodeCasa.Abstractions.csproj b/src/CodeCasa.Abstractions/CodeCasa.Abstractions.csproj index 08e387b..e3a53a6 100644 --- a/src/CodeCasa.Abstractions/CodeCasa.Abstractions.csproj +++ b/src/CodeCasa.Abstractions/CodeCasa.Abstractions.csproj @@ -32,9 +32,5 @@ \ - - - - diff --git a/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj new file mode 100644 index 0000000..e85833d --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs new file mode 100644 index 0000000..06888c6 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs @@ -0,0 +1,15 @@ +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights.NetDaemon.Extensions; +using NetDaemon.HassModel.Entities; + +namespace CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions +{ + public static class LightPipelineFactoryExtensions + { + public static IAsyncDisposable SetupLightPipeline(this LightPipelineFactory lightPipelineFactory, ILightEntityCore lightEntity, + Action pipelineBuilder) + { + return lightPipelineFactory.SetupLightPipeline(lightEntity.AsLight(), pipelineBuilder); + } + } +} diff --git a/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCodeExtensions.cs b/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs similarity index 98% rename from src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCodeExtensions.cs rename to src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs index 5d5c058..d052487 100644 --- a/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCodeExtensions.cs +++ b/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs @@ -11,8 +11,13 @@ namespace CodeCasa.Lights.NetDaemon.Extensions; /// including turning lights on/off, applying transitions, retrieving parameters, and checking state. /// All methods are extension methods on , allowing for a fluent API. /// -public static class LightEntityCodeExtensions +public static class LightEntityCoreExtensions { + public static NetDaemonLight AsLight(this ILightEntityCore lightEntity) + { + return new NetDaemonLight(lightEntity); + } + /// /// Turns on a light entity with the specified light parameters. /// From 6a20cdcaa0db1e7adf8f982e88d23bd8d55b9ee6 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 10:26:04 +0100 Subject: [PATCH 06/18] Not supporting AddUncoupledDimmer for now. --- ...iteLightTransitionReactiveNodeConfigurator.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index 0ec03a9..4bab0dc 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -46,21 +46,7 @@ public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimme public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions) { - var options = new DimmerOptions(); - dimOptions(options); - - var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); - var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); - - var configuratorsWithLightEntity = options.ValidateAndOrderMultipleLightEntityTypes(configurators).Select(kvp => (configurator: kvp.Value, lightId: kvp.Key)).ToArray(); - var lightEntitiesInDimOrder = configuratorsWithLightEntity.Select(t => t.lightId).ToArray(); - foreach (var containerAndLight in configuratorsWithLightEntity) - { - // Note: this is not strictly required, but I think it's neater and might prevent issues. - var lightEntitiesInDimOrderWithContainerInstance = lightEntitiesInDimOrder.Select(l => l == containerAndLight.lightId ? containerAndLight.lightId : l); - containerAndLight.configurator.AddDimPulses(options, lightEntitiesInDimOrderWithContainerInstance, dimPulses, brightenPulses); - } - return this; + throw new NotSupportedException(); } public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) From a45d84e34244b0083bc8298247bf3f06dc7df2f6 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 10:33:12 +0100 Subject: [PATCH 07/18] Cleanup some todo's --- .../LightTransitionNodeExtensions.cs | 10 -------- .../Nodes/ScopedNode.cs | 25 +++---------------- .../Pipeline/LightPipelineFactory.cs | 1 - .../ReactiveNode/ReactiveNode.cs | 11 ++------ .../ReactiveNode/ReactiveNodeFactory.cs | 5 ++-- .../Utils/ObjectExtensions.cs | 19 ++++++++++++++ 6 files changed, 27 insertions(+), 44 deletions(-) create mode 100644 src/CodeCasa.AutomationPipelines.Lights/Utils/ObjectExtensions.cs diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs index a5d0379..e2499eb 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs @@ -10,16 +10,6 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions { public static class LightTransitionNodeExtensions { - public static IPipelineNode LightParametersThatTurnsOffAfter(this ILightPipelineContext context, - LightParameters lightParameters, - TimeSpan timeSpan, IObservable resetTimerObservable) - { - // todo: move to diferent project - var scheduler = context.ServiceProvider.GetRequiredService(); - var innerNode = new StaticLightTransitionNode(lightParameters.AsTransition(), scheduler); - return innerNode.TurnOffAfter(timeSpan, resetTimerObservable, scheduler); - } - public static IPipelineNode TurnOffAfter(this IPipelineNode node, TimeSpan timeSpan, IScheduler scheduler) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs index 158fbc8..b85eb2f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ScopedNode.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using CodeCasa.AutomationPipelines.Lights.Utils; +using Microsoft.Extensions.DependencyInjection; namespace CodeCasa.AutomationPipelines.Lights.Nodes { @@ -7,26 +8,8 @@ internal class ScopedNode(IServiceScope serviceScope, IPipelineNode CreateLightPipeline(ILight lightEntity, Acti internal Dictionary> CreateLightPipelines(IEnumerable lightEntities, Action pipelineBuilder) { - // todo: is this assumption correct? Make internal? // Note: we simply assume that these are not groups. var lightEntityArray = lightEntities.ToArray(); if (!lightEntityArray.Any()) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs index 4e7417c..5e2476d 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs @@ -1,6 +1,7 @@ using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; +using CodeCasa.AutomationPipelines.Lights.Utils; using CodeCasa.Lights; using Microsoft.Extensions.Logging; @@ -66,15 +67,7 @@ private void DeactivateActiveNode() if (_activeNode != null) { _activeNode.Input = null; - switch (_activeNode) - { - case IAsyncDisposable asyncDisposable: - asyncDisposable.DisposeAsync().GetAwaiter().GetResult(); - break; - case IDisposable disposable: - disposable.Dispose(); - break; - } + _activeNode.DisposeOrDisposeAsync().GetAwaiter().GetResult(); } _activeNode = null; diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs index d02c0ac..468d725 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs @@ -19,9 +19,8 @@ public IPipelineNode CreateReactiveNode(ILight lightEntity, Act return CreateReactiveNodes([lightEntity], configure)[lightEntity.Id]; } - public Dictionary> CreateReactiveNodes(IEnumerable lightEntities, Action configure) + internal Dictionary> CreateReactiveNodes(IEnumerable lightEntities, Action configure) { - // todo: is this assumption correct? Make internal? // Note: we simply assume that these are not groups. var lightEntityArray = lightEntities.ToArray(); if (!lightEntityArray.Any()) @@ -29,7 +28,7 @@ public Dictionary> CreateReactiveNodes(IE return new Dictionary>(); } - var lightPipelineFactory = serviceProvider.GetRequiredService(); // todo + var lightPipelineFactory = serviceProvider.GetRequiredService(); var reactiveConfigurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionReactiveNodeConfigurator(serviceProvider, lightPipelineFactory, this, l, scheduler)); ILightTransitionReactiveNodeConfigurator configurator = lightEntityArray.Length == 1 diff --git a/src/CodeCasa.AutomationPipelines.Lights/Utils/ObjectExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Utils/ObjectExtensions.cs new file mode 100644 index 0000000..b3fbbfc --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/Utils/ObjectExtensions.cs @@ -0,0 +1,19 @@ + +namespace CodeCasa.AutomationPipelines.Lights.Utils +{ + internal static class ObjectExtensions + { + public static async Task DisposeOrDisposeAsync(this object obj) + { + switch (obj) + { + case IAsyncDisposable asyncDisposable: + await asyncDisposable.DisposeAsync(); + break; + case IDisposable disposable: + disposable.Dispose(); + break; + } + } + } +} From cc7ad05ce32d55cb2fbd0e618e9c50bfa35d52ff Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 10:36:15 +0100 Subject: [PATCH 08/18] Minor refactor. --- .../ReactiveNode/ReactiveNodeFactory.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs index 468d725..4ad6956 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs @@ -144,12 +144,12 @@ private DimmingContext CreateDimmingContext(OrderedDictionary { - private readonly RegisterInterface _registerInterface; + private readonly IRegisterInterface _registerInterface; public ReactiveDimmerPipeline( ReactiveNode reactiveNode, ReactiveDimmerNode reactiveDimmerNode, - RegisterInterface registerInterface) + IRegisterInterface registerInterface) { _registerInterface = registerInterface; _registerInterface.Register(this); @@ -164,17 +164,17 @@ public override ValueTask DisposeAsync() } } - internal interface RegisterInterface + internal interface IRegisterInterface { void Register(T reference); void Unregister(T reference); } - internal sealed class RegistrationManager : RegisterInterface, IDisposable + internal sealed class RegistrationManager : IRegisterInterface, IDisposable { - private readonly HashSet _items = new HashSet(); - private readonly object _lock = new object(); - private readonly Subject _lastUnregistered = new Subject(); + private readonly HashSet _items = new(); + private readonly Lock _lock = new(); + private readonly Subject _lastUnregistered = new (); private bool _isDisposed; public IObservable LastUnregistered => _lastUnregistered; @@ -183,9 +183,7 @@ public void Register(T reference) { lock (_lock) { - if (_isDisposed) - throw new ObjectDisposedException(nameof(RegistrationManager)); - + ObjectDisposedException.ThrowIf(_isDisposed, typeof(RegistrationManager)); _items.Add(reference); } } From d014288dedee625ab75c32b9ab3d18a450cc0e30 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 10:48:46 +0100 Subject: [PATCH 09/18] Updated package info. --- ...utomationPipelines.Lights.NetDaemon.csproj | 27 +++++++++++++++++++ ...CodeCasa.AutomationPipelines.Lights.csproj | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj index e85833d..573ba12 100644 --- a/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj +++ b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj @@ -4,8 +4,35 @@ net10.0 enable enable + CodeCasa.AutomationPipelines.Lights.NetDaemon + Jasper Lammers + DevJasper + NetDaemon integration for light automation pipelines, enabling advanced reactive light control through CodeCasa.AutomationPipelines and CodeCasa.Lights abstractions. + https://github.com/DevJasperNL/CodeCasa + NetDaemon;Light Control;Lights;Home Automation;Home Assistant;HA Integration;Smart Home;Automation;Pipeline;Pipelines;Node;Nodes;Reactive;Rx;Reactive Extensions;C#;.NET;DotNet;Extension Methods + LICENSE + README.md + CodeCasa.AutomationPipelines.Lights.NetDaemon + True + ccnd_icon.png + https://github.com/DevJasperNL/CodeCasa/releases + + + True + \ + + + True + \ + + + True + \ + + + diff --git a/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj b/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj index 157c5bb..eb95380 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj +++ b/src/CodeCasa.AutomationPipelines.Lights/CodeCasa.AutomationPipelines.Lights.csproj @@ -4,8 +4,35 @@ net10.0 enable enable + CodeCasa.AutomationPipelines.Lights + Jasper Lammers + DevJasper + Light automation pipeline extensions for CodeCasa.AutomationPipelines, providing composable and reactive light control logic. + https://github.com/DevJasperNL/CodeCasa + Home Automation;Home Assistant;HA Integration;Smart Home;Automation;Lights;Light;Pipeline;Pipelines;Node;Nodes;Reactive;Rx;Reactive Extensions;C#;.NET;DotNet;Extension Methods + LICENSE + README.md + CodeCasa.AutomationPipelines.Lights + True + cc_icon.png + https://github.com/DevJasperNL/CodeCasa/releases + + + True + \ + + + True + \ + + + True + \ + + + From 191aeb1dafda4bff184c53c82211f4ca5fe98b4f Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 10:55:48 +0100 Subject: [PATCH 10/18] Fixed warnings. --- .../Pipeline/LightPipelineFactory.cs | 2 +- .../Pipeline/LightTransitionPipelineConfigurator.cs | 4 ++-- .../Extensions/LightEntityCoreExtensions.cs | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs index fbfdcad..ad54707 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs @@ -35,7 +35,7 @@ internal Dictionary> CreateLightPipelines(IEn return new Dictionary>(); } - var configurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionPipelineConfigurator(serviceProvider, this, reactiveNodeFactory, l, scheduler)); + var configurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionPipelineConfigurator(serviceProvider, this, reactiveNodeFactory, l)); ILightTransitionPipelineConfigurator configurator = lightEntityArray.Length == 1 ? configurators[lightEntityArray[0].Id] : new CompositeLightTransitionPipelineConfigurator( diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs index 91bd63f..e067eb3 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs @@ -7,12 +7,12 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline { + /// public partial class LightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, - ILight lightEntity, - IScheduler scheduler) + ILight lightEntity) : ILightTransitionPipelineConfigurator { private readonly List> _nodes = new(); diff --git a/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs b/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs index d052487..d086775 100644 --- a/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs +++ b/src/CodeCasa.Lights.NetDaemon/Extensions/LightEntityCoreExtensions.cs @@ -13,6 +13,11 @@ namespace CodeCasa.Lights.NetDaemon.Extensions; /// public static class LightEntityCoreExtensions { + /// + /// Converts an to a wrapper. + /// + /// The light entity to convert. + /// A wrapper around the provided light entity. public static NetDaemonLight AsLight(this ILightEntityCore lightEntity) { return new NetDaemonLight(lightEntity); From e7a1fc0df43c5e998877e698ab9ddd2f19cc8302 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:18:20 +0100 Subject: [PATCH 11/18] Adding xml comments. --- .../LightPipelineFactoryExtensions.cs | 10 ++++++ .../CompositeAsyncDisposable.cs | 21 ++++++++++-- .../Context/ILightPipelineContext.cs | 10 ++++++ .../Context/LightPipelineContext.cs | 9 ++++++ .../DimmerOptions.cs | 26 +++++++++++++++ .../ExcludedLightBehaviours.cs | 3 ++ .../Extensions/ServiceCollectionExtensions.cs | 8 +++++ .../Nodes/LightTransitionNode.cs | 32 +++++++++++++++---- .../Pipeline/LightPipelineFactory.cs | 21 ++++++++++++ 9 files changed, 131 insertions(+), 9 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs index 06888c6..8c0240f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights.NetDaemon/Extensions/LightPipelineFactoryExtensions.cs @@ -4,8 +4,18 @@ namespace CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions { + /// + /// Extension methods for to work with NetDaemon light entities. + /// public static class LightPipelineFactoryExtensions { + /// + /// Sets up a light pipeline for a NetDaemon light entity. + /// + /// The light pipeline factory. + /// The NetDaemon light entity to set up the pipeline for. + /// An action to configure the pipeline behavior. + /// An async disposable representing the created pipeline(s) that can be disposed to clean up resources. public static IAsyncDisposable SetupLightPipeline(this LightPipelineFactory lightPipelineFactory, ILightEntityCore lightEntity, Action pipelineBuilder) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs b/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs index 26ae2a7..058f62b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/CompositeAsyncDisposable.cs @@ -1,27 +1,44 @@ - -namespace CodeCasa.AutomationPipelines.Lights +namespace CodeCasa.AutomationPipelines.Lights { + /// + /// A composite disposable that manages both synchronous and asynchronous disposable resources. + /// public sealed class CompositeAsyncDisposable : IAsyncDisposable { private readonly List _asyncDisposables = new(); private readonly List _disposables = new(); private bool _disposed; + /// + /// Adds an asynchronous disposable resource to be disposed when this composite is disposed. + /// + /// The asynchronous disposable resource to add. public void Add(IAsyncDisposable asyncDisposable) { _asyncDisposables.Add(asyncDisposable); } + /// + /// Adds a synchronous disposable resource to be disposed when this composite is disposed. + /// + /// The synchronous disposable resource to add. public void Add(IDisposable disposable) { _disposables.Add(disposable); } + /// + /// Adds a range of synchronous disposable resources to be disposed when this composite is disposed. + /// + /// The collection of synchronous disposable resources to add. public void AddRange(IEnumerable disposables) { _disposables.AddRange(disposables); } + /// + /// Disposes all managed asynchronous and synchronous resources. + /// public async ValueTask DisposeAsync() { if (_disposed) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs index a58e30e..93c57f7 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs @@ -2,8 +2,18 @@ namespace CodeCasa.AutomationPipelines.Lights.Context; +/// +/// Represents the context for a light pipeline, providing access to the service provider and the light entity being controlled. +/// public interface ILightPipelineContext { + /// + /// Gets the service provider instance used to resolve dependencies in the pipeline. + /// IServiceProvider ServiceProvider { get; } + + /// + /// Gets the light entity being controlled by the pipeline. + /// ILight LightEntity { get; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs index 2f90efa..2c5d742 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs @@ -2,14 +2,23 @@ namespace CodeCasa.AutomationPipelines.Lights.Context; +/// public class LightPipelineContext : ILightPipelineContext { + /// + /// Initializes a new instance of the class. + /// + /// The service provider instance used to resolve dependencies in the pipeline. + /// The light entity being controlled by the pipeline. internal LightPipelineContext(IServiceProvider serviceProvider, ILight lightEntity) { ServiceProvider = serviceProvider; LightEntity = lightEntity; } + /// public IServiceProvider ServiceProvider { get; } + + /// public ILight LightEntity { get; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs b/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs index 9ecec2c..a9cd554 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs @@ -2,16 +2,42 @@ namespace CodeCasa.AutomationPipelines.Lights; +/// +/// Configuration options for dimmer behavior, including brightness levels, step sizes, and timing. +/// public record DimmerOptions { + /// + /// Gets or sets the minimum brightness level. Defaults to 2. + /// public int MinBrightness { get; set; } = 2; + + /// + /// Gets or sets the brightness step size for each dimming increment. Defaults to 51. + /// public int BrightnessStep { get; set; } = 51; + + /// + /// Gets or sets the time delay between each brightness step. Defaults to 500 milliseconds. + /// public TimeSpan TimeBetweenSteps { get; set; } = TimeSpan.FromMilliseconds(500); + + /// + /// Gets or sets the collection of light entity IDs that define the order for dimming operations. + /// public IEnumerable? DimOrderLightEntities { get; set; } + /// + /// Sets the light order for dimming operations based on the provided collection of light entities. + /// + /// The light entities that define the dimming order. public void SetLightOrder(IEnumerable lightEntities) => DimOrderLightEntities = lightEntities.Select(l => l.Id).ToArray(); + /// + /// Sets the light order for dimming operations based on the provided light entities. + /// + /// The light entities that define the dimming order. public void SetLightOrder(params ILight[] lightEntities) => DimOrderLightEntities = lightEntities.Select(l => l.Id).ToArray(); } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs b/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs index 8a7cd89..a2436e3 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ExcludedLightBehaviours.cs @@ -1,5 +1,8 @@ namespace CodeCasa.AutomationPipelines.Lights; +/// +/// Specifies the behavior for lights that are excluded from specific pipeline operations. +/// public enum ExcludedLightBehaviours { /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs index 13bc25e..50b27d8 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceCollectionExtensions.cs @@ -5,8 +5,16 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions; +/// +/// Extension methods for registering light pipeline services in the dependency injection container. +/// public static class ServiceCollectionExtensions { + /// + /// Registers all required services for light pipelines in the service collection. + /// + /// The service collection to register services in. + /// The service collection for method chaining. public static IServiceCollection AddLightPipelines(this IServiceCollection serviceCollection) { return serviceCollection diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs index 6dd5ef4..f4179fc 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/LightTransitionNode.cs @@ -6,6 +6,10 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes { + /// + /// Base class for pipeline nodes that work with light transitions, extending IPipelineNode functionality. + /// Provides features for managing light transition states, scheduling, and pass-through behavior. + /// public abstract class LightTransitionNode(IScheduler scheduler) : IPipelineNode { private readonly Subject _newOutputSubject = new(); @@ -18,6 +22,9 @@ public abstract class LightTransitionNode(IScheduler scheduler) : IPipelineNode< private bool _passThrough; private IDisposable? _scheduledAction; + /// + /// Gets the source light parameters from the previous input, useful for interpolating transitions. + /// protected LightParameters? InputLightSourceParameters { get; private set; } /// @@ -53,15 +60,16 @@ public LightTransition? Input } /// - /// Called when the input is received. + /// Called when the input is received. Override this method to implement custom input handling logic. /// + /// The light transition input that was received. protected virtual void InputReceived(LightTransition? input) { // Ignore input by default. } /// - /// Turns on pass-through mode for the node, meaning it will pass the input directly to the output without processing it. + /// Enables pass-through mode for the node, causing it to pass the input directly to the output without processing. /// protected void PassInputThrough() { @@ -69,8 +77,8 @@ protected void PassInputThrough() } /// - /// Sets the output state of the node. This will trigger the processing of the input. - /// If the node is disabled, it will be enabled when setting an output value. + /// Gets or sets the output state of the node. + /// Setting this value will trigger output processing and disable pass-through mode. /// public LightTransition? Output { @@ -84,6 +92,11 @@ protected set } } + /// + /// Schedules an interpolated light transition that will animate from source to desired parameters using the input's transition time. + /// + /// The source light parameters to transition from. + /// The desired light parameters to transition to. protected void ScheduleInterpolatedLightTransitionUsingInputTransitionTime(LightParameters? sourceLightParameters, LightParameters? desiredLightParameters) { PassThrough = false; @@ -91,6 +104,10 @@ protected void ScheduleInterpolatedLightTransitionUsingInputTransitionTime(Light desiredLightParameters, _inputStartOfTransition, _inputEndOfTransition, SetOutputInternal); } + /// + /// Gets or sets a value indicating whether the node should pass its input directly to the output. + /// When true, the node does not call InputReceived and instead passes the input through unchanged. + /// public bool PassThrough { get => _passThrough; @@ -116,9 +133,10 @@ public bool PassThrough } /// - /// Changes the output state of the node and enables pass-through mode after the next input. - /// This can be useful for nodes that should influence pipeline behavior once. For example a light switch or a motion sensor detection. + /// Changes the output state of the node and enables pass-through mode after the next input is received. + /// This is useful for nodes that should influence pipeline behavior once, such as light switches or motion sensors. /// + /// The output light transition to set. protected void ChangeOutputAndTurnOnPassThroughOnNextInput(LightTransition? output) { Output = output; @@ -127,7 +145,7 @@ protected void ChangeOutputAndTurnOnPassThroughOnNextInput(LightTransition? outp /// /// Keeps the current output but enables pass-through mode after receiving the next input. - /// This can be useful for nodes that should influence pipeline behavior once. For example a light switch or a motion sensor detection. + /// This is useful for nodes that should influence pipeline behavior once, such as light switches or motion sensors. /// protected void TurnOnPassThroughOnNextInput() { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs index ad54707..2c121ba 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs @@ -6,9 +6,18 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline { + /// + /// Factory for creating and configuring light transition pipelines. + /// public class LightPipelineFactory( ILogger> logger, IServiceProvider serviceProvider, ReactiveNodeFactory reactiveNodeFactory, IScheduler scheduler) { + /// + /// Sets up a light pipeline for the specified light entity and configures it with the provided builder action. + /// + /// The light entity to set up the pipeline for. + /// An action to configure the pipeline behavior. + /// An async disposable representing the created pipeline(s) that can be disposed to clean up resources. public IAsyncDisposable SetupLightPipeline(ILight lightEntity, Action pipelineBuilder) { @@ -21,11 +30,23 @@ public IAsyncDisposable SetupLightPipeline(ILight lightEntity, return disposables; } + /// + /// Creates a single light pipeline for the specified light entity. + /// + /// The light entity to create a pipeline for. + /// An action to configure the pipeline behavior. + /// A configured pipeline for controlling the specified light. internal IPipeline CreateLightPipeline(ILight lightEntity, Action pipelineBuilder) { return CreateLightPipelines([lightEntity], pipelineBuilder)[lightEntity.Id]; } + /// + /// Creates light pipelines for multiple light entities. + /// + /// The light entities to create pipelines for. + /// An action to configure the pipeline behavior. + /// A dictionary mapping light entity IDs to their corresponding pipelines. internal Dictionary> CreateLightPipelines(IEnumerable lightEntities, Action pipelineBuilder) { // Note: we simply assume that these are not groups. From 2146030fab05e4dd2737c6a770e432e34c02cda2 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:27:34 +0100 Subject: [PATCH 12/18] Added xml comments. --- .../Nodes/CompositeDimmer.cs | 10 +++++ .../Nodes/DimHelper.cs | 2 +- .../Nodes/FactoryNode.cs | 3 +- .../Nodes/PassThroughNode.cs | 2 +- .../Nodes/ResettableTimeoutNode.cs | 16 +++----- .../Nodes/StaticLightTransitionNode.cs | 2 +- .../Nodes/TurnOffThenPassThroughNode.cs | 3 +- ...LightTransitionReactiveNodeConfigurator.cs | 1 + .../ReactiveNode/ReactiveDimmerNode.cs | 34 ++++++++++++++- .../ReactiveNode/ReactiveNode.cs | 15 +++++++ .../ReactiveNode/ReactiveNodeFactory.cs | 41 +++++++++++++++++++ 11 files changed, 112 insertions(+), 17 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs index 2f18210..d009ed0 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/CompositeDimmer.cs @@ -4,8 +4,16 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes { + /// + /// A composite dimmer that combines multiple dimmers and emits true when any of them are actively dimming or brightening. + /// public class CompositeDimmer : IDimmer { + /// + /// Initializes a new instance of the class. + /// + /// The collection of dimmers to combine. Must contain at least one dimmer. + /// Thrown when the dimmers collection is null or empty. public CompositeDimmer(IEnumerable dimmers) { dimmers = dimmers.ToArray(); @@ -23,8 +31,10 @@ public CompositeDimmer(IEnumerable dimmers) .DistinctUntilChanged(); } + /// public IObservable Dimming { get; } + /// public IObservable Brightening { get; } } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs index 55bfd83..4e60596 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/DimHelper.cs @@ -2,7 +2,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes; -public class DimHelper( +internal class DimHelper( ILight subject, IEnumerable lightsInDimOrder, int minBrightness, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs index 99627df..267d579 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs @@ -1,8 +1,9 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes; -public class FactoryNode(Func lightTransitionFactory) +internal class FactoryNode(Func lightTransitionFactory) : PipelineNode { + /// protected override void InputReceived(TState? input) { Output = lightTransitionFactory(input); diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs index 88f064c..370220a 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/PassThroughNode.cs @@ -1,6 +1,6 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes; -public class PassThroughNode : PipelineNode +internal class PassThroughNode : PipelineNode { public PassThroughNode() { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs index 9663fe8..a9da7a4 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/ResettableTimeoutNode.cs @@ -5,9 +5,9 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes { - public class ResettableTimeoutNode : LightTransitionNode - { - public ResettableTimeoutNode(IPipelineNode childNode, TimeSpan turnOffTime, IObservable refreshObservable, IScheduler scheduler) : base(scheduler) + internal class ResettableTimeoutNode : LightTransitionNode{ + public ResettableTimeoutNode(IPipelineNode childNode, TimeSpan turnOffTime, + IObservable refreshObservable, IScheduler scheduler) : base(scheduler) { var serializedChild = childNode.OnNewOutput.Prepend(childNode.Output).ObserveOn(scheduler); @@ -20,16 +20,10 @@ public ResettableTimeoutNode(IPipelineNode childNode, TimeSpan serializedChild .TakeUntil(serializedTurnOff) - .Subscribe(output => - { - Output = output; - }); + .Subscribe(output => { Output = output; }); serializedTurnOff - .Subscribe(_ => - { - ChangeOutputAndTurnOnPassThroughOnNextInput(LightTransition.Off()); - }); + .Subscribe(_ => { ChangeOutputAndTurnOnPassThroughOnNextInput(LightTransition.Off()); }); } } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs index 9e5d1c8..bde921d 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/StaticLightTransitionNode.cs @@ -3,7 +3,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes; -public class StaticLightTransitionNode : LightTransitionNode +internal class StaticLightTransitionNode : LightTransitionNode { public StaticLightTransitionNode(LightTransition? output, IScheduler scheduler) : base(scheduler) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs b/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs index fec52cd..3ad407e 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Nodes/TurnOffThenPassThroughNode.cs @@ -2,7 +2,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes; -public class TurnOffThenPassThroughNode : PipelineNode +internal class TurnOffThenPassThroughNode : PipelineNode { public TurnOffThenPassThroughNode() { @@ -10,6 +10,7 @@ public TurnOffThenPassThroughNode() Output = LightTransition.Off(); } + /// protected override void InputReceived(LightTransition? input) { TurnOnPassThroughOnNextInput(); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index 4bab0dc..e9e6698 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -46,6 +46,7 @@ public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimme public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions) { + // todo: support. throw new NotSupportedException(); } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs index 2fc4e72..e19d92b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs @@ -4,19 +4,36 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode { - // todo: investigate why off is sent twice in a row + /// + /// Represents the context for dimming operations, containing the ordered output parameters of all dimmer nodes. + /// + /// An array of tuples containing entity IDs and their current light parameters, ordered for dimming operations. public class DimmingContext( (string entityId, LightParameters? parametersAfterDim)[] dimmerNodeOutputParametersInOrder) { + /// + /// Gets the ordered output parameters of all dimmer nodes, used to coordinate dimming behavior across multiple lights. + /// public (string entityId, LightParameters? parametersAfterDim)[] DimmerNodeOutputParametersInOrder { get; } = dimmerNodeOutputParametersInOrder; } + /// + /// A light transition node that handles dimming and brightening operations in response to dimmer input. + /// internal class ReactiveDimmerNode : LightTransitionNode { private readonly int _minBrightness; private readonly int _brightnessStep; private int _dimSteps; // negative is dimming, positive is brightening. + /// + /// Initializes a new instance of the class. + /// + /// The reactive node to monitor for state changes. + /// The entity ID of the light this dimmer node controls. + /// The minimum brightness level before the light turns off. + /// The step size for each dimming/brightening increment. + /// The scheduler used for timing operations. public ReactiveDimmerNode( ReactiveNode reactiveNode, string lightEntityId, @@ -32,14 +49,21 @@ public ReactiveDimmerNode( _brightnessStep = brightnessStep; } + /// + /// Gets the entity ID of the light this dimmer node controls. + /// public string LightEntityId { get; } + /// + /// Resets the dimming state back to pass-through mode. + /// public void Reset() { PassThrough = true; _dimSteps = 0; } + /// protected override void InputReceived(LightTransition? input) { if (input == null) @@ -52,6 +76,10 @@ protected override void InputReceived(LightTransition? input) Output = input with { LightParameters = input.LightParameters with { Brightness = newBrightness } }; } + /// + /// Executes one step of dimming in response to dimmer input. + /// + /// The dimming context containing current light parameters. public void DimStep(DimmingContext context) { if (!ShouldDim(context)) @@ -68,6 +96,10 @@ public void DimStep(DimmingContext context) ScheduleInterpolatedLightTransition(); } + /// + /// Executes one step of brightening in response to dimmer input. + /// + /// The dimming context containing current light parameters. public void BrightenStep(DimmingContext context) { if (!ShouldBrighten(context)) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs index 5e2476d..ad3f53a 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNode.cs @@ -7,6 +7,10 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; +/// +/// A pipeline node that dynamically switches between different child nodes based on an observable source. +/// The active node can change at runtime, allowing for reactive behavior switching. +/// public class ReactiveNode : PipelineNode { private readonly Lock _lock = new(); @@ -16,6 +20,12 @@ public class ReactiveNode : PipelineNode private IPipelineNode? _activeNode; private IDisposable? _activeNodeSubscription; + /// + /// Initializes a new instance of the class. + /// + /// Optional name for the reactive node, used for logging purposes. + /// An observable that emits the pipeline nodes to activate. Null values deactivate the current node. + /// Optional logger for diagnostic information. public ReactiveNode(string? name, IObservable?> nodeObservable, ILogger logger) { _name = name; @@ -44,8 +54,12 @@ public ReactiveNode(string? name, IObservable?> n private string LogPrefix => _name == null ? "" : $"{_name}: "; + /// + /// Gets an observable that emits whenever the active node changes. + /// public IObservable NodeChanged => _nodeChangedSubject.AsObservable(); + /// protected override void InputReceived(LightTransition? input) { if (_activeNode == null) @@ -102,5 +116,6 @@ private void ActivateNode(IPipelineNode node) PassThrough = false; } + /// public override string ToString() => _name ?? base.ToString(); } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs index 4ad6956..2df33f7 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs @@ -12,13 +12,28 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode { + /// + /// Factory for creating reactive nodes that dynamically switch between child nodes based on observable inputs. + /// public class ReactiveNodeFactory(IServiceProvider serviceProvider, IScheduler scheduler) { + /// + /// Creates a reactive node for a single light entity. + /// + /// The light entity to create the reactive node for. + /// An action to configure the reactive node. + /// A configured reactive node for the specified light entity. public IPipelineNode CreateReactiveNode(ILight lightEntity, Action configure) { return CreateReactiveNodes([lightEntity], configure)[lightEntity.Id]; } + /// + /// Creates reactive nodes for multiple light entities. + /// + /// The light entities to create reactive nodes for. + /// An action to configure the reactive nodes. + /// A dictionary mapping light entity IDs to their corresponding reactive nodes. internal Dictionary> CreateReactiveNodes(IEnumerable lightEntities, Action configure) { // Note: we simply assume that these are not groups. @@ -142,10 +157,16 @@ private DimmingContext CreateDimmingContext(OrderedDictionary + /// A pipeline that combines a reactive node with a reactive dimmer node for managing light transitions. + /// internal class ReactiveDimmerPipeline : Pipeline { private readonly IRegisterInterface _registerInterface; + /// + /// Initializes a new instance of the class. + /// public ReactiveDimmerPipeline( ReactiveNode reactiveNode, ReactiveDimmerNode reactiveDimmerNode, @@ -157,6 +178,7 @@ public ReactiveDimmerPipeline( RegisterNode(reactiveDimmerNode); } + /// public override ValueTask DisposeAsync() { _registerInterface.Unregister(this); @@ -164,12 +186,25 @@ public override ValueTask DisposeAsync() } } + /// + /// Interface for managing registrations and tracking when the last item is unregistered. + /// internal interface IRegisterInterface { + /// + /// Registers an item. + /// void Register(T reference); + + /// + /// Unregisters an item. + /// void Unregister(T reference); } + /// + /// Manages registration of items and notifies when the last item is unregistered. + /// internal sealed class RegistrationManager : IRegisterInterface, IDisposable { private readonly HashSet _items = new(); @@ -177,8 +212,12 @@ internal sealed class RegistrationManager : IRegisterInterface, IDisposabl private readonly Subject _lastUnregistered = new (); private bool _isDisposed; + /// + /// Gets an observable that emits when the last registered item is unregistered. + /// public IObservable LastUnregistered => _lastUnregistered; + /// public void Register(T reference) { lock (_lock) @@ -188,6 +227,7 @@ public void Register(T reference) } } + /// public void Unregister(T reference) { bool becameEmpty = false; @@ -205,6 +245,7 @@ public void Unregister(T reference) _lastUnregistered.OnNext(Unit.Default); } + /// public void Dispose() { lock (_lock) From be2425691dae2322d754ad18af06ea8a3fd71fcc Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:33:40 +0100 Subject: [PATCH 13/18] Updated xml comments. --- ...ransitionReactiveNodeConfigurator.Cycle.cs | 5 +++++ ...htTransitionReactiveNodeConfigurator.On.cs | 11 ++++++++++ ...ansitionReactiveNodeConfigurator.Toggle.cs | 7 ++++++ ...LightTransitionReactiveNodeConfigurator.cs | 14 ++++++++++++ ...ransitionReactiveNodeConfigurator.Cycle.cs | 5 +++++ ...htTransitionReactiveNodeConfigurator.On.cs | 11 ++++++++++ ...ansitionReactiveNodeConfigurator.Toggle.cs | 7 ++++++ ...LightTransitionReactiveNodeConfigurator.cs | 22 +++++++++++++++++++ 8 files changed, 82 insertions(+) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs index 07deddd..c6537bc 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs @@ -9,9 +9,11 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; public partial class CompositeLightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) => AddCycle(triggerObservable, lightParameters.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, params LightParameters[] lightParameters) { @@ -24,9 +26,11 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg }); } + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightTransitions) => AddCycle(triggerObservable, lightTransitions.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, params LightTransition[] lightTransitions) { @@ -39,6 +43,7 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg }); } + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, Action configure) { var cycleConfigurators = configurators.ToDictionary(kvp => kvp.Key, diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs index 1efd8b3..2fd7e41 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -10,30 +10,37 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; public partial class CompositeLightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightParameters lightParameters) => On(triggerObservable, lightParameters.AsTransition()); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func lightParametersFactory) => On(triggerObservable, c => lightParametersFactory(c).AsTransition()); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightTransition lightTransition) => On(triggerObservable, c => new StaticLightTransitionNode(lightTransition, c.ServiceProvider.GetRequiredService())); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func lightTransitionFactory) => On(triggerObservable, c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable) where TNode : IPipelineNode { configurators.Values.ForEach(c => c.On(triggerObservable)); return this; } + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func> nodeFactory) { configurators.Values.ForEach(c => c.On(triggerObservable, nodeFactory)); return this; } + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. @@ -43,6 +50,7 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObse return this; } + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. @@ -52,18 +60,21 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObse return this; } + /// public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) { configurators.Values.ForEach(c => c.PassThroughOn(triggerObservable)); return this; } + /// public ILightTransitionReactiveNodeConfigurator TurnOffWhen(IObservable triggerObservable) { configurators.Values.ForEach(c => c.TurnOffWhen(triggerObservable)); return this; } + /// public ILightTransitionReactiveNodeConfigurator TurnOnWhen(IObservable triggerObservable) { return On(triggerObservable, LightTransition.On()); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs index 616d6d5..6d9091c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs @@ -10,9 +10,11 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; public partial class CompositeLightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) => AddToggle(triggerObservable, lightParameters.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params LightParameters[] lightParameters) { @@ -25,9 +27,11 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig }); } + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightTransitions) => AddToggle(triggerObservable, lightTransitions.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params LightTransition[] lightTransitions) { @@ -40,9 +44,11 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig }); } + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable>> nodeFactories) => AddToggle(triggerObservable, nodeFactories.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params Func>[] nodeFactories) { return AddToggle(triggerObservable, configure => @@ -54,6 +60,7 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig }); } + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, Action configure) { var toggleConfigurators = configurators.ToDictionary(kvp => kvp.Key, diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index e9e6698..39673a0 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -7,6 +7,10 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; +/// +/// Configures light transition reactive nodes for multiple light entities as a composite. +/// This configurator applies configurations across all included lights and allows for selective scoping to subsets of lights. +/// public partial class CompositeLightTransitionReactiveNodeConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, @@ -15,12 +19,14 @@ public partial class CompositeLightTransitionReactiveNodeConfigurator( IScheduler scheduler) : ILightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator SetName(string name) { configurators.Values.ForEach(c => c.SetName(name)); return this; } + /// public ILightTransitionReactiveNodeConfigurator AddReactiveDimmer(IDimmer dimmer) { foreach (var configurator in configurators) @@ -30,6 +36,7 @@ public ILightTransitionReactiveNodeConfigurator AddReactiveDimmer(IDimmer dimmer return this; } + /// public ILightTransitionReactiveNodeConfigurator SetReactiveDimmerOptions(DimmerOptions dimmerOptions) { foreach (var configurator in configurators) @@ -39,27 +46,33 @@ public ILightTransitionReactiveNodeConfigurator SetReactiveDimmerOptions(DimmerO return this; } + /// public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer) { return AddUncoupledDimmer(dimmer, _ => { }); } + /// public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions) { // todo: support. throw new NotSupportedException(); } + /// public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) { configurators.Values.ForEach(c => c.AddNodeSource(nodeFactorySource)); return this; } + /// public ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, Action configure) => ForLights([lightEntityId], configure); + /// public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, Action configure) => ForLights([lightEntity], configure); + /// public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, Action configure) { var lightEntityIdsArray = @@ -85,6 +98,7 @@ public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable li return this; } + /// public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) { var lightIds = CompositeHelper.ResolveGroupsAndValidateLightsSupported(lightEntities, configurators.Keys); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs index d28cbf4..5574a9f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs @@ -9,9 +9,11 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; public partial class LightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) => AddCycle(triggerObservable, lightParameters.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, params LightParameters[] lightParameters) { @@ -24,9 +26,11 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg }); } + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightTransitions) => AddCycle(triggerObservable, lightTransitions.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, params LightTransition[] lightTransitions) { @@ -39,6 +43,7 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg }); } + /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, Action configure) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs index aad06ef..0018120 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -11,47 +11,58 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; public partial class LightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightParameters lightParameters) => On(triggerObservable, lightParameters.AsTransition()); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func lightParametersFactory) => On(triggerObservable, c => lightParametersFactory(c).AsTransition()); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightTransition lightTransition) => On(triggerObservable, c => new StaticLightTransitionNode(lightTransition, c.ServiceProvider.GetRequiredService())); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func lightTransitionFactory) => On(triggerObservable, c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable) where TNode : IPipelineNode => AddNodeSource(triggerObservable.Select(_ => new Func?>(c => c.ServiceProvider.CreateInstanceWithinContext(c)))); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Func> nodeFactory) => AddNodeSource(triggerObservable.Select(_ => nodeFactory)); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) => On(triggerObservable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) => On(triggerObservable, c => reactiveNodeFactory.CreateReactiveNode(c.LightEntity, configure)); + /// public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) { AddNodeSource(triggerObservable.Select(_ => new PassThroughNode())); return this; } + /// public ILightTransitionReactiveNodeConfigurator TurnOffWhen(IObservable triggerObservable) { AddNodeSource(triggerObservable.Select(_ => new TurnOffThenPassThroughNode())); return this; } + /// public ILightTransitionReactiveNodeConfigurator TurnOnWhen(IObservable triggerObservable) { return On(triggerObservable, LightTransition.On()); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs index 6426866..67ada4f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs @@ -10,9 +10,11 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; public partial class LightTransitionReactiveNodeConfigurator { + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) => AddToggle(triggerObservable, lightParameters.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params LightParameters[] lightParameters) { @@ -25,9 +27,11 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig }); } + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightTransitions) => AddToggle(triggerObservable, lightTransitions.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params LightTransition[] lightTransitions) { @@ -40,9 +44,11 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig }); } + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable>> nodeFactories) => AddToggle(triggerObservable, nodeFactories.ToArray()); + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, params Func>[] nodeFactories) { return AddToggle(triggerObservable, configure => @@ -54,6 +60,7 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig }); } + /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, Action configure) { var toggleConfigurator = new LightTransitionToggleConfigurator(LightEntity, scheduler); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs index c3e3732..23ba5d2 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs @@ -10,6 +10,10 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; +/// +/// Configures a light transition reactive node for a single light entity. +/// This configurator allows adding reactive dimmer controls, node sources, and scoped configurations for light automation. +/// public partial class LightTransitionReactiveNodeConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, @@ -17,6 +21,9 @@ public partial class LightTransitionReactiveNodeConfigurator( ILight lightEntity, IScheduler scheduler) : ILightTransitionReactiveNodeConfigurator { + /// + /// Gets the light entity associated with this configurator. + /// public ILight LightEntity { get; } = lightEntity; internal string? Name { get; private set; } @@ -24,29 +31,34 @@ public partial class LightTransitionReactiveNodeConfigurator( internal List Dimmers { get; } = new(); internal DimmerOptions DimmerOptions { get; private set; } = new (); + /// public ILightTransitionReactiveNodeConfigurator SetName(string name) { Name = name; return this; } + /// public ILightTransitionReactiveNodeConfigurator AddReactiveDimmer(IDimmer dimmer) { Dimmers.Add(dimmer); return this; } + /// public ILightTransitionReactiveNodeConfigurator SetReactiveDimmerOptions(DimmerOptions dimmerOptions) { DimmerOptions = dimmerOptions; return this; } + /// public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer) { return AddUncoupledDimmer(dimmer, _ => { }); } + /// public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimmer, Action dimOptions) { var options = new DimmerOptions(); @@ -73,23 +85,32 @@ internal void AddDimPulses(DimmerOptions options, IEnumerable lightsInDi .Select(t => (IPipelineNode)(t == LightTransition.Off() ? new TurnOffThenPassThroughNode() : new StaticLightTransitionNode(t, scheduler)))); } + /// + /// Adds a node source observable to the reactive node. + /// + /// An observable that emits pipeline nodes. + /// The configurator instance for method chaining. public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?> nodeSource) { NodeObservables.Add(nodeSource); return this; } + /// public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) { return AddNodeSource(nodeFactorySource.Select(f => f(new LightPipelineContext(serviceProvider, LightEntity)))); } + /// public ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, Action configure) => ForLights([lightEntityId], configure); + /// public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, Action configure) => ForLights([lightEntity], configure); + /// public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, Action configure) { @@ -97,6 +118,7 @@ public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable li return this; } + /// public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) { From 5bbcf063cd77903ab96644dd07a4716a05367673 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:36:33 +0100 Subject: [PATCH 14/18] Using light instead of light entity --- .../Context/ILightPipelineContext.cs | 6 ++-- .../Context/LightPipelineContext.cs | 8 ++--- .../Context/LightPipelineContextProvider.cs | 2 +- ...mpositeLightTransitionCycleConfigurator.cs | 12 +++---- .../ILightTransitionCycleConfigurator.cs | 16 ++++----- .../Cycle/LightTransitionCycleConfigurator.cs | 18 +++++----- .../DimmerOptions.cs | 2 +- .../Extensions/DimmerOptionsExtensions.cs | 16 ++++----- .../Extensions/ServiceProviderExtensions.cs | 6 ++-- ...siteLightTransitionPipelineConfigurator.cs | 26 +++++++------- .../ILightTransitionPipelineConfigurator.cs | 16 ++++----- .../Pipeline/LightPipelineFactory.cs | 32 ++++++++--------- ...htTransitionPipelineConfigurator.Switch.cs | 10 +++--- ...ightTransitionPipelineConfigurator.When.cs | 10 +++--- .../LightTransitionPipelineConfigurator.cs | 26 +++++++------- ...ransitionReactiveNodeConfigurator.Cycle.cs | 4 +-- ...htTransitionReactiveNodeConfigurator.On.cs | 8 ++--- ...ansitionReactiveNodeConfigurator.Toggle.cs | 6 ++-- ...LightTransitionReactiveNodeConfigurator.cs | 18 +++++----- ...LightTransitionReactiveNodeConfigurator.cs | 16 ++++----- ...ransitionReactiveNodeConfigurator.Cycle.cs | 4 +-- ...htTransitionReactiveNodeConfigurator.On.cs | 4 +-- ...ansitionReactiveNodeConfigurator.Toggle.cs | 6 ++-- ...LightTransitionReactiveNodeConfigurator.cs | 30 ++++++++-------- .../ReactiveNode/ReactiveDimmerNode.cs | 16 ++++----- .../ReactiveNode/ReactiveNodeFactory.cs | 36 +++++++++---------- ...positeLightTransitionToggleConfigurator.cs | 22 ++++++------ .../ILightTransitionToggleConfigurator.cs | 16 ++++----- .../LightTransitionToggleConfigurator.cs | 14 ++++---- 29 files changed, 203 insertions(+), 203 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs index 93c57f7..14f0e94 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/ILightPipelineContext.cs @@ -3,7 +3,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Context; /// -/// Represents the context for a light pipeline, providing access to the service provider and the light entity being controlled. +/// Represents the context for a light pipeline, providing access to the service provider and the light being controlled. /// public interface ILightPipelineContext { @@ -13,7 +13,7 @@ public interface ILightPipelineContext IServiceProvider ServiceProvider { get; } /// - /// Gets the light entity being controlled by the pipeline. + /// Gets the light being controlled by the pipeline. /// - ILight LightEntity { get; } + ILight Light { get; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs index 2c5d742..d725d59 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContext.cs @@ -9,16 +9,16 @@ public class LightPipelineContext : ILightPipelineContext /// Initializes a new instance of the class. /// /// The service provider instance used to resolve dependencies in the pipeline. - /// The light entity being controlled by the pipeline. - internal LightPipelineContext(IServiceProvider serviceProvider, ILight lightEntity) + /// The light being controlled by the pipeline. + internal LightPipelineContext(IServiceProvider serviceProvider, ILight light) { ServiceProvider = serviceProvider; - LightEntity = lightEntity; + Light = light; } /// public IServiceProvider ServiceProvider { get; } /// - public ILight LightEntity { get; } + public ILight Light { get; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs index 5e0d95e..fd2e978 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Context/LightPipelineContextProvider.cs @@ -14,7 +14,7 @@ public void SetLightPipelineContext(ILightPipelineContext context) _lightPipelineContext = context; } - public void ResetLightEntity() + public void ResetLight() { _lightPipelineContext = null; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs index 6aff42f..abfc27b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs @@ -15,7 +15,7 @@ internal class CompositeLightTransitionCycleConfigurator( { public ILightTransitionCycleConfigurator AddOff() { - var matchesNodeState = () => activeConfigurators.Values.All(c => c.LightEntity.IsOff()); + var matchesNodeState = () => activeConfigurators.Values.All(c => c.Light.IsOff()); activeConfigurators.Values.ForEach(c => c.Add(_ => matchesNodeState())); inactiveConfigurators.Values.ForEach(c => c.AddPassThrough(_ => matchesNodeState())); return this; @@ -47,7 +47,7 @@ public ILightTransitionCycleConfigurator Add(LightTransition lightTransition, IE return Add( _ => lightTransition, _ => activeConfigurators.Values.All(c => comparer.Equals( - c.LightEntity.GetParameters(), + c.Light.GetParameters(), lightTransition.LightParameters))); } @@ -82,16 +82,16 @@ public ILightTransitionCycleConfigurator AddPassThrough(Func configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + public ILightTransitionCycleConfigurator ForLight(string lightId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightId], configure, excludedLightBehaviour); - public ILightTransitionCycleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + public ILightTransitionCycleConfigurator ForLight(ILight light, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([light], configure, excludedLightBehaviour); - public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntityIds, + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { var lightIds = - CompositeHelper.ValidateLightsSupported(lightEntityIds, activeConfigurators.Keys); + CompositeHelper.ValidateLightsSupported(lightIds, activeConfigurators.Keys); if (lightIds.Length == activeConfigurators.Count) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs index c5944a2..f3ae372 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs @@ -102,31 +102,31 @@ public interface ILightTransitionCycleConfigurator ILightTransitionCycleConfigurator AddPassThrough(Func matchesNodeState); /// - /// Creates a scoped cycle configuration for a specific light entity identified by its entity ID. + /// Creates a scoped cycle configuration for a specific light identified by its entity ID. /// - /// The entity ID of the light to configure. + /// The entity ID of the light to configure. /// An action to configure the cycle for this specific light. /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . /// The configurator instance for method chaining. - ILightTransitionCycleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + ILightTransitionCycleConfigurator ForLight(string lightId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); /// - /// Creates a scoped cycle configuration for a specific light entity. + /// Creates a scoped cycle configuration for a specific light. /// - /// The light entity to configure. + /// The light to configure. /// An action to configure the cycle for this specific light. /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . /// The configurator instance for method chaining. - ILightTransitionCycleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + ILightTransitionCycleConfigurator ForLight(ILight light, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); /// /// Creates a scoped cycle configuration for multiple light entities identified by their entity IDs. /// - /// The entity IDs of the lights to configure. + /// The entity IDs of the lights to configure. /// An action to configure the cycle for these lights. /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . /// The configurator instance for method chaining. - ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + ILightTransitionCycleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); /// /// Creates a scoped cycle configuration for multiple light entities. diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs index d867339..8350ca1 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/LightTransitionCycleConfigurator.cs @@ -7,9 +7,9 @@ namespace CodeCasa.AutomationPipelines.Lights.Cycle; -internal class LightTransitionCycleConfigurator(ILight lightEntity, IScheduler scheduler) : ILightTransitionCycleConfigurator +internal class LightTransitionCycleConfigurator(ILight light, IScheduler scheduler) : ILightTransitionCycleConfigurator { - public ILight LightEntity { get; } = lightEntity; + public ILight Light { get; } = light; internal List<(Func> nodeFactory, Func matchesNodeState)> CycleNodeFactories { @@ -18,7 +18,7 @@ internal class LightTransitionCycleConfigurator(ILight lightEntity, IScheduler s public ILightTransitionCycleConfigurator AddOff() { - return Add(_ => LightEntity.IsOff()); + return Add(_ => Light.IsOff()); } public ILightTransitionCycleConfigurator AddOn() @@ -45,7 +45,7 @@ public ILightTransitionCycleConfigurator Add(LightTransition lightTransition, IE { comparer ??= EqualityComparer.Default; return Add(new StaticLightTransitionNode(lightTransition, scheduler), _ => comparer.Equals( - LightEntity.GetParameters(), + Light.GetParameters(), lightTransition.LightParameters)); } @@ -80,19 +80,19 @@ public ILightTransitionCycleConfigurator AddPassThrough(Func(), matchesNodeState); } - public ILightTransitionCycleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + public ILightTransitionCycleConfigurator ForLight(string lightId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightId], configure, excludedLightBehaviour); - public ILightTransitionCycleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + public ILightTransitionCycleConfigurator ForLight(ILight light, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([light], configure, excludedLightBehaviour); - public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + public ILightTransitionCycleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); + CompositeHelper.ValidateLightSupported(lightIds, Light.Id); return this; } public ILightTransitionCycleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, Light.Id); return this; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs b/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs index a9cd554..977319f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/DimmerOptions.cs @@ -23,7 +23,7 @@ public record DimmerOptions public TimeSpan TimeBetweenSteps { get; set; } = TimeSpan.FromMilliseconds(500); /// - /// Gets or sets the collection of light entity IDs that define the order for dimming operations. + /// Gets or sets the collection of light IDs that define the order for dimming operations. /// public IEnumerable? DimOrderLightEntities { get; set; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs index 55deb5e..c7ad5a7 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/DimmerOptionsExtensions.cs @@ -2,33 +2,33 @@ { internal static class DimmerOptionsExtensions { - public static void ValidateSingleLightEntity(this DimmerOptions dimmerOptions, string lightEntityId) + public static void ValidateSingleLight(this DimmerOptions dimmerOptions, string lightId) { var dimOrderLightEntitiesArray = dimmerOptions.DimOrderLightEntities?.ToArray(); if (dimOrderLightEntitiesArray != null && dimOrderLightEntitiesArray.Any()) { - var extraEntities = dimOrderLightEntitiesArray.Where(l => l != lightEntityId).ToArray(); + var extraEntities = dimOrderLightEntitiesArray.Where(l => l != lightId).ToArray(); if (extraEntities.Any()) { throw new InvalidOperationException( - $"Builder only supports entity {lightEntityId}. Please remove extra entities {string.Join(", ", extraEntities)}."); + $"Builder only supports entity {lightId}. Please remove extra entities {string.Join(", ", extraEntities)}."); } } } - public static OrderedDictionary ValidateAndOrderMultipleLightEntityTypes(this DimmerOptions dimmerOptions, Dictionary typesByLightEntityIds) + public static OrderedDictionary ValidateAndOrderMultipleLightTypes(this DimmerOptions dimmerOptions, Dictionary typesByLightIds) { var dimOrderLightEntitiesArray = dimmerOptions.DimOrderLightEntities?.ToArray(); if (dimOrderLightEntitiesArray != null && dimOrderLightEntitiesArray.Any()) { - var missingEntities = typesByLightEntityIds.Keys.Except(dimOrderLightEntitiesArray).ToArray(); + var missingEntities = typesByLightIds.Keys.Except(dimOrderLightEntitiesArray).ToArray(); if (missingEntities.Any()) { throw new InvalidOperationException( $"When providing dim order, all entities should be provided. The following entities are missing: {string.Join(", ", missingEntities)}. Make sure to provide low level entities."); } - var extraEntities = dimOrderLightEntitiesArray.Except(typesByLightEntityIds.Keys).ToArray(); + var extraEntities = dimOrderLightEntitiesArray.Except(typesByLightIds.Keys).ToArray(); if (extraEntities.Any()) { throw new InvalidOperationException( @@ -36,10 +36,10 @@ public static OrderedDictionary ValidateAndOrderMultipleLightEntityTy } return new OrderedDictionary(dimOrderLightEntitiesArray - .Select(e => new KeyValuePair(e, typesByLightEntityIds[e]))); + .Select(e => new KeyValuePair(e, typesByLightIds[e]))); } - return new OrderedDictionary(typesByLightEntityIds); + return new OrderedDictionary(typesByLightIds); } } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs index 011d9c7..f069930 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ServiceProviderExtensions.cs @@ -7,8 +7,8 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions; internal static class ServiceProviderExtensions { public static T - CreateInstanceWithinContext(this IServiceProvider serviceProvider, ILight lightEntity) => - serviceProvider.CreateInstanceWithinContext(new LightPipelineContext(serviceProvider, lightEntity)); + CreateInstanceWithinContext(this IServiceProvider serviceProvider, ILight light) => + serviceProvider.CreateInstanceWithinContext(new LightPipelineContext(serviceProvider, light)); public static T CreateInstanceWithinContext(this IServiceProvider serviceProvider, ILightPipelineContext context) => @@ -20,7 +20,7 @@ public static object CreateInstanceWithinContext(this IServiceProvider servicePr var contextProvider = serviceProvider.GetRequiredService(); contextProvider.SetLightPipelineContext(context); var instance = ActivatorUtilities.CreateInstance(serviceProvider, instanceType); - contextProvider.ResetLightEntity(); + contextProvider.ResetLight(); return instance; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs index 427ab2c..cfadec6 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs @@ -38,7 +38,7 @@ public ILightTransitionPipelineConfigurator AddNode(Func configure) { - var nodes = reactiveNodeFactory.CreateReactiveNodes(NodeContainers.Select(nc => nc.Value.LightEntity), + var nodes = reactiveNodeFactory.CreateReactiveNodes(NodeContainers.Select(nc => nc.Value.Light), configure); NodeContainers.ForEach(kvp => kvp.Value.AddNode(nodes[kvp.Key])); return this; @@ -46,7 +46,7 @@ public ILightTransitionPipelineConfigurator AddReactiveNode( public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) { - var pipelines = lightPipelineFactory.CreateLightPipelines(NodeContainers.Select(c => c.Value.LightEntity), + var pipelines = lightPipelineFactory.CreateLightPipelines(NodeContainers.Select(c => c.Value.Light), pipelineNodeOptions); NodeContainers.ForEach(kvp => kvp.Value.AddNode(pipelines[kvp.Key])); return this; @@ -65,31 +65,31 @@ public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action compositeNodeBuilder) => ForLights([lightEntityId], compositeNodeBuilder); + public ILightTransitionPipelineConfigurator ForLight(string lightId, + Action compositeNodeBuilder) => ForLights([lightId], compositeNodeBuilder); - public ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, - Action compositeNodeBuilder) => ForLights([lightEntity], compositeNodeBuilder); + public ILightTransitionPipelineConfigurator ForLight(ILight light, + Action compositeNodeBuilder) => ForLights([light], compositeNodeBuilder); - public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, + public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightIds, Action compositeNodeBuilder) { - var lightEntityIdsArray = - CompositeHelper.ValidateLightsSupported(lightEntityIds, NodeContainers.Keys); + var lightIdsArray = + CompositeHelper.ValidateLightsSupported(lightIds, NodeContainers.Keys); - if (lightEntityIdsArray.Length == NodeContainers.Count) + if (lightIdsArray.Length == NodeContainers.Count) { compositeNodeBuilder(this); return this; } - if (lightEntityIdsArray.Length == 1) + if (lightIdsArray.Length == 1) { - compositeNodeBuilder(NodeContainers[lightEntityIdsArray.First()]); + compositeNodeBuilder(NodeContainers[lightIdsArray.First()]); return this; } compositeNodeBuilder(new CompositeLightTransitionPipelineConfigurator(serviceProvider, lightPipelineFactory, reactiveNodeFactory, NodeContainers - .Where(kvp => lightEntityIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + .Where(kvp => lightIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), scheduler)); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs index 91ef0a3..cb87555 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs @@ -59,28 +59,28 @@ public partial interface ILightTransitionPipelineConfigurator ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action dimOptions); /// - /// Creates a scoped pipeline configuration for a specific light entity identified by its entity ID. + /// Creates a scoped pipeline configuration for a specific light identified by its entity ID. /// - /// The entity ID of the light to configure. + /// The entity ID of the light to configure. /// An action to configure the pipeline for this specific light. /// The configurator instance for method chaining. - ILightTransitionPipelineConfigurator ForLight(string lightEntityId, Action compositeNodeBuilder); + ILightTransitionPipelineConfigurator ForLight(string lightId, Action compositeNodeBuilder); /// - /// Creates a scoped pipeline configuration for a specific light entity. + /// Creates a scoped pipeline configuration for a specific light. /// - /// The light entity to configure. + /// The light to configure. /// An action to configure the pipeline for this specific light. /// The configurator instance for method chaining. - ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, Action compositeNodeBuilder); + ILightTransitionPipelineConfigurator ForLight(ILight light, Action compositeNodeBuilder); /// /// Creates a scoped pipeline configuration for multiple light entities identified by their entity IDs. /// - /// The entity IDs of the lights to configure. + /// The entity IDs of the lights to configure. /// An action to configure the pipeline for these lights. /// The configurator instance for method chaining. - ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, Action compositeNodeBuilder); + ILightTransitionPipelineConfigurator ForLights(IEnumerable lightIds, Action compositeNodeBuilder); /// /// Creates a scoped pipeline configuration for multiple light entities. diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs index 2c121ba..c0c1a6d 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs @@ -13,16 +13,16 @@ public class LightPipelineFactory( ILogger> logger, IServiceProvider serviceProvider, ReactiveNodeFactory reactiveNodeFactory, IScheduler scheduler) { /// - /// Sets up a light pipeline for the specified light entity and configures it with the provided builder action. + /// Sets up a light pipeline for the specified light and configures it with the provided builder action. /// - /// The light entity to set up the pipeline for. + /// The light to set up the pipeline for. /// An action to configure the pipeline behavior. /// An async disposable representing the created pipeline(s) that can be disposed to clean up resources. - public IAsyncDisposable SetupLightPipeline(ILight lightEntity, + public IAsyncDisposable SetupLightPipeline(ILight light, Action pipelineBuilder) { var disposables = new CompositeAsyncDisposable(); - var pipelines = CreateLightPipelines(lightEntity.Flatten(), pipelineBuilder); + var pipelines = CreateLightPipelines(light.Flatten(), pipelineBuilder); foreach (var pipeline in pipelines.Values) { disposables.Add(pipeline); @@ -31,14 +31,14 @@ public IAsyncDisposable SetupLightPipeline(ILight lightEntity, } /// - /// Creates a single light pipeline for the specified light entity. + /// Creates a single light pipeline for the specified light. /// - /// The light entity to create a pipeline for. + /// The light to create a pipeline for. /// An action to configure the pipeline behavior. /// A configured pipeline for controlling the specified light. - internal IPipeline CreateLightPipeline(ILight lightEntity, Action pipelineBuilder) + internal IPipeline CreateLightPipeline(ILight light, Action pipelineBuilder) { - return CreateLightPipelines([lightEntity], pipelineBuilder)[lightEntity.Id]; + return CreateLightPipelines([light], pipelineBuilder)[light.Id]; } /// @@ -46,19 +46,19 @@ internal IPipeline CreateLightPipeline(ILight lightEntity, Acti /// /// The light entities to create pipelines for. /// An action to configure the pipeline behavior. - /// A dictionary mapping light entity IDs to their corresponding pipelines. + /// A dictionary mapping light IDs to their corresponding pipelines. internal Dictionary> CreateLightPipelines(IEnumerable lightEntities, Action pipelineBuilder) { // Note: we simply assume that these are not groups. - var lightEntityArray = lightEntities.ToArray(); - if (!lightEntityArray.Any()) + var lightArray = lightEntities.ToArray(); + if (!lightArray.Any()) { return new Dictionary>(); } - var configurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionPipelineConfigurator(serviceProvider, this, reactiveNodeFactory, l)); - ILightTransitionPipelineConfigurator configurator = lightEntityArray.Length == 1 - ? configurators[lightEntityArray[0].Id] + var configurators = lightArray.ToDictionary(l => l.Id, l => new LightTransitionPipelineConfigurator(serviceProvider, this, reactiveNodeFactory, l)); + ILightTransitionPipelineConfigurator configurator = lightArray.Length == 1 + ? configurators[lightArray[0].Id] : new CompositeLightTransitionPipelineConfigurator( serviceProvider, this, @@ -70,10 +70,10 @@ internal Dictionary> CreateLightPipelines(IEn { var conf = kvp.Value; return (IPipeline)new Pipeline( - conf.Name ?? conf.LightEntity.Id, + conf.Name ?? conf.Light.Id, LightTransition.Off(), conf.Nodes, - conf.LightEntity.ApplyTransition, + conf.Light.ApplyTransition, logger); }); } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs index fe0d178..18e533d 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs @@ -66,7 +66,7 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, public ILightTransitionPipelineConfigurator Switch(Func> trueNodeFactory, Func> falseNodeFactory) where TObservable : IObservable { - var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + var observable = serviceProvider.CreateInstanceWithinContext(Light); return Switch(observable, trueNodeFactory, falseNodeFactory); } @@ -80,7 +80,7 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, public ILightTransitionPipelineConfigurator Switch() where TObservable : IObservable where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode { - var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + var observable = serviceProvider.CreateInstanceWithinContext(Light); return Switch(observable); } @@ -93,7 +93,7 @@ public ILightTransitionPipelineConfigurator Switch(IObser public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable { - var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + var observable = serviceProvider.CreateInstanceWithinContext(Light); return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); } @@ -107,13 +107,13 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable(Action trueConfigure, Action falseConfigure) where TObservable : IObservable { - return Switch(c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, falseConfigure)); + return Switch(c => lightPipelineFactory.CreateLightPipeline(c.Light, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.Light, falseConfigure)); } public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action trueConfigure, Action falseConfigure) { - return Switch(observable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, falseConfigure)); + return Switch(observable, c => lightPipelineFactory.CreateLightPipeline(c.Light, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.Light, falseConfigure)); } public ILightTransitionPipelineConfigurator TurnOnOff() where TObservable : IObservable diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs index f471b34..116b326 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs @@ -62,7 +62,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, public ILightTransitionPipelineConfigurator When( Func> nodeFactory) where TObservable : IObservable { - var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + var observable = serviceProvider.CreateInstanceWithinContext(Light); return When(observable, nodeFactory); } @@ -78,7 +78,7 @@ public ILightTransitionPipelineConfigurator When() where TObservable : IObservable where TNode : IPipelineNode { - var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + var observable = serviceProvider.CreateInstanceWithinContext(Light); return When(observable); } @@ -92,7 +92,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action configure) where TObservable : IObservable { - var observable = serviceProvider.CreateInstanceWithinContext(LightEntity); + var observable = serviceProvider.CreateInstanceWithinContext(Light); return AddReactiveNodeWhen(observable, configure); } @@ -105,12 +105,12 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable(Action pipelineConfigurator) where TObservable : IObservable { - return When(c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + return When(c => lightPipelineFactory.CreateLightPipeline(c.Light, pipelineConfigurator)); } public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action pipelineConfigurator) { - return When(observable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + return When(observable, c => lightPipelineFactory.CreateLightPipeline(c.Light, pipelineConfigurator)); } public ILightTransitionPipelineConfigurator TurnOffWhen() where TObservable : IObservable diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs index e067eb3..506485e 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs @@ -12,12 +12,12 @@ public partial class LightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, - ILight lightEntity) + ILight light) : ILightTransitionPipelineConfigurator { private readonly List> _nodes = new(); - internal ILight LightEntity { get; } = lightEntity; + internal ILight Light { get; } = light; internal string? Name { get; private set; } public IReadOnlyCollection> Nodes => _nodes.AsReadOnly(); @@ -36,7 +36,7 @@ public ILightTransitionPipelineConfigurator SetName(string name) public ILightTransitionPipelineConfigurator AddNode() where TNode : IPipelineNode { - _nodes.Add(serviceProvider.CreateInstanceWithinContext(LightEntity)); + _nodes.Add(serviceProvider.CreateInstanceWithinContext(Light)); return this; } @@ -48,14 +48,14 @@ public ILightTransitionPipelineConfigurator AddNode(IPipelineNode> nodeFactory) { - _nodes.Add(nodeFactory(new LightPipelineContext(serviceProvider, LightEntity))); + _nodes.Add(nodeFactory(new LightPipelineContext(serviceProvider, Light))); return this; } public ILightTransitionPipelineConfigurator AddReactiveNode( Action configure) { - return AddNode(reactiveNodeFactory.CreateReactiveNode(LightEntity, configure)); + return AddNode(reactiveNodeFactory.CreateReactiveNode(Light, configure)); } public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer) @@ -71,27 +71,27 @@ public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action compositeNodeBuilder) => - ForLights([lightEntityId], compositeNodeBuilder); + ForLights([lightId], compositeNodeBuilder); - public ILightTransitionPipelineConfigurator ForLight(ILight lightEntity, - Action compositeNodeBuilder) => ForLights([lightEntity], compositeNodeBuilder); + public ILightTransitionPipelineConfigurator ForLight(ILight light, + Action compositeNodeBuilder) => ForLights([light], compositeNodeBuilder); - public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntityIds, + public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightIds, Action compositeNodeBuilder) { - CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); + CompositeHelper.ValidateLightSupported(lightIds, Light.Id); return this; } public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, Action compositeNodeBuilder) { - CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, Light.Id); return this; } - public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) => AddNode(lightPipelineFactory.CreateLightPipeline(LightEntity, pipelineNodeOptions)); + public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) => AddNode(lightPipelineFactory.CreateLightPipeline(Light, pipelineNodeOptions)); } } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs index c6537bc..a70a6c2 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs @@ -47,13 +47,13 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, Action configure) { var cycleConfigurators = configurators.ToDictionary(kvp => kvp.Key, - kvp => new LightTransitionCycleConfigurator(kvp.Value.LightEntity, scheduler)); + kvp => new LightTransitionCycleConfigurator(kvp.Value.Light, scheduler)); var compositeCycleConfigurator = new CompositeLightTransitionCycleConfigurator(cycleConfigurators, []); configure(compositeCycleConfigurator); configurators.ForEach(kvp => kvp.Value.AddNodeSource(triggerObservable.ToCycleObservable(cycleConfigurators[kvp.Key].CycleNodeFactories.Select(tuple => { var serviceScope = serviceProvider.CreateScope(); - var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.LightEntity); + var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.Light); var factory = new Func>(() => new ScopedNode(serviceScope, tuple.nodeFactory(context))); var valueIsActiveFunc = () => tuple.matchesNodeState(context); return (factory, valueIsActiveFunc); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs index 2fd7e41..d6f6dfc 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -44,9 +44,9 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObse public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. - var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.LightEntity), + var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.Light), pipelineConfigurator); - configurators.Values.ForEach(c => c.On(triggerObservable, ctx => pipelines[ctx.LightEntity.Id])); + configurators.Values.ForEach(c => c.On(triggerObservable, ctx => pipelines[ctx.Light.Id])); return this; } @@ -54,9 +54,9 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObse public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. - var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.LightEntity), + var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.Light), configure); - configurators.Values.ForEach(c => c.On(triggerObservable, ctx => nodes[ctx.LightEntity.Id])); + configurators.Values.ForEach(c => c.On(triggerObservable, ctx => nodes[ctx.Light.Id])); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs index 6d9091c..76a9e19 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs @@ -64,18 +64,18 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, Action configure) { var toggleConfigurators = configurators.ToDictionary(kvp => kvp.Key, - kvp => new LightTransitionToggleConfigurator(kvp.Value.LightEntity, scheduler)); + kvp => new LightTransitionToggleConfigurator(kvp.Value.Light, scheduler)); var compositeCycleConfigurator = new CompositeLightTransitionToggleConfigurator(toggleConfigurators, []); configure(compositeCycleConfigurator); configurators.ForEach(kvp => kvp.Value.AddNodeSource(triggerObservable.ToToggleObservable( - () => configurators.Values.Any(c => c.LightEntity.IsOn()), + () => configurators.Values.Any(c => c.Light.IsOn()), () => new TurnOffThenPassThroughNode(), toggleConfigurators[kvp.Key].NodeFactories.Select(fact => { return new Func>(() => { var serviceScope = serviceProvider.CreateScope(); - var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.LightEntity); + var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.Light); return new ScopedNode(serviceScope, fact(context)); }); }), diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index 39673a0..54b8100 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -67,25 +67,25 @@ public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable - public ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, Action configure) => ForLights([lightEntityId], configure); + public ILightTransitionReactiveNodeConfigurator ForLight(string lightId, Action configure) => ForLights([lightId], configure); /// - public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, Action configure) => ForLights([lightEntity], configure); + public ILightTransitionReactiveNodeConfigurator ForLight(ILight light, Action configure) => ForLights([light], configure); /// - public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, Action configure) + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightIds, Action configure) { - var lightEntityIdsArray = - CompositeHelper.ValidateLightsSupported(lightEntityIds, configurators.Keys); + var lightIdsArray = + CompositeHelper.ValidateLightsSupported(lightIds, configurators.Keys); - if (lightEntityIdsArray.Length == configurators.Count) + if (lightIdsArray.Length == configurators.Count) { configure(this); return this; } - if (lightEntityIdsArray.Length == 1) + if (lightIdsArray.Length == 1) { - configure(configurators[lightEntityIdsArray.First()]); + configure(configurators[lightIdsArray.First()]); return this; } @@ -94,7 +94,7 @@ public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable li lightPipelineFactory, reactiveNodeFactory, configurators - .Where(kvp => lightEntityIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), scheduler)); + .Where(kvp => lightIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), scheduler)); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs index e02779f..2484262 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs @@ -59,30 +59,30 @@ public partial interface ILightTransitionReactiveNodeConfigurator nodeFactorySource); /// - /// Creates a scoped reactive node configuration for a specific light entity identified by its entity ID. + /// Creates a scoped reactive node configuration for a specific light identified by its entity ID. /// - /// The entity ID of the light to configure. + /// The entity ID of the light to configure. /// An action to configure the reactive node for this specific light. /// The configurator instance for method chaining. - ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, + ILightTransitionReactiveNodeConfigurator ForLight(string lightId, Action configure); /// - /// Creates a scoped reactive node configuration for a specific light entity. + /// Creates a scoped reactive node configuration for a specific light. /// - /// The light entity to configure. + /// The light to configure. /// An action to configure the reactive node for this specific light. /// The configurator instance for method chaining. - ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, + ILightTransitionReactiveNodeConfigurator ForLight(ILight light, Action configure); /// /// Creates a scoped reactive node configuration for multiple light entities identified by their entity IDs. /// - /// The entity IDs of the lights to configure. + /// The entity IDs of the lights to configure. /// An action to configure the reactive node for these lights. /// The configurator instance for method chaining. - ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, + ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightIds, Action configure); /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs index 5574a9f..62cf8ce 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs @@ -47,12 +47,12 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, Action configure) { - var cycleConfigurator = new LightTransitionCycleConfigurator(LightEntity, scheduler); + var cycleConfigurator = new LightTransitionCycleConfigurator(Light, scheduler); configure(cycleConfigurator); AddNodeSource(triggerObservable.ToCycleObservable(cycleConfigurator.CycleNodeFactories.Select(tuple => { var serviceScope = serviceProvider.CreateScope(); - var context = new LightPipelineContext(serviceScope.ServiceProvider, LightEntity); + var context = new LightPipelineContext(serviceScope.ServiceProvider, Light); var factory = new Func>(() => new ScopedNode(serviceScope, tuple.nodeFactory(context))); var valueIsActiveFunc = () => tuple.matchesNodeState(context); return (factory, valueIsActiveFunc); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs index 0018120..db9eaca 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -43,10 +43,10 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObse AddNodeSource(triggerObservable.Select(_ => nodeFactory)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) => On(triggerObservable, c => lightPipelineFactory.CreateLightPipeline(c.LightEntity, pipelineConfigurator)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action pipelineConfigurator) => On(triggerObservable, c => lightPipelineFactory.CreateLightPipeline(c.Light, pipelineConfigurator)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) => On(triggerObservable, c => reactiveNodeFactory.CreateReactiveNode(c.LightEntity, configure)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action configure) => On(triggerObservable, c => reactiveNodeFactory.CreateReactiveNode(c.Light, configure)); /// public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs index 67ada4f..966513a 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs @@ -63,17 +63,17 @@ public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable trig /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, Action configure) { - var toggleConfigurator = new LightTransitionToggleConfigurator(LightEntity, scheduler); + var toggleConfigurator = new LightTransitionToggleConfigurator(Light, scheduler); configure(toggleConfigurator); AddNodeSource(triggerObservable.ToToggleObservable( - () => LightEntity.IsOn(), + () => Light.IsOn(), () => new TurnOffThenPassThroughNode(), toggleConfigurator.NodeFactories.Select(fact => { return new Func>(() => { var serviceScope = serviceProvider.CreateScope(); - var context = new LightPipelineContext(serviceScope.ServiceProvider, LightEntity); + var context = new LightPipelineContext(serviceScope.ServiceProvider, Light); return new ScopedNode(serviceScope, fact(context)); }); }), diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs index 23ba5d2..7df4053 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs @@ -11,20 +11,20 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; /// -/// Configures a light transition reactive node for a single light entity. +/// Configures a light transition reactive node for a single light. /// This configurator allows adding reactive dimmer controls, node sources, and scoped configurations for light automation. /// public partial class LightTransitionReactiveNodeConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, - ILight lightEntity, + ILight light, IScheduler scheduler) : ILightTransitionReactiveNodeConfigurator { /// - /// Gets the light entity associated with this configurator. + /// Gets the light associated with this configurator. /// - public ILight LightEntity { get; } = lightEntity; + public ILight Light { get; } = light; internal string? Name { get; private set; } internal List?>> NodeObservables { get; } = new(); @@ -63,18 +63,18 @@ public ILightTransitionReactiveNodeConfigurator AddUncoupledDimmer(IDimmer dimme { var options = new DimmerOptions(); dimOptions(options); - options.ValidateSingleLightEntity(LightEntity.Id); + options.ValidateSingleLight(Light.Id); var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(options.TimeBetweenSteps, scheduler); - AddDimPulses(options, [LightEntity], dimPulses, brightenPulses); + AddDimPulses(options, [Light], dimPulses, brightenPulses); return this; } internal void AddDimPulses(DimmerOptions options, IEnumerable lightsInDimOrder, IObservable dimPulses, IObservable brightenPulses) { - var dimHelper = new DimHelper(LightEntity, lightsInDimOrder, options.MinBrightness, options.BrightnessStep); + var dimHelper = new DimHelper(Light, lightsInDimOrder, options.MinBrightness, options.BrightnessStep); AddNodeSource(dimPulses .Select(_ => dimHelper.DimStep()) .Where(t => t != null) @@ -99,22 +99,22 @@ public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) { - return AddNodeSource(nodeFactorySource.Select(f => f(new LightPipelineContext(serviceProvider, LightEntity)))); + return AddNodeSource(nodeFactorySource.Select(f => f(new LightPipelineContext(serviceProvider, Light)))); } /// - public ILightTransitionReactiveNodeConfigurator ForLight(string lightEntityId, - Action configure) => ForLights([lightEntityId], configure); + public ILightTransitionReactiveNodeConfigurator ForLight(string lightId, + Action configure) => ForLights([lightId], configure); /// - public ILightTransitionReactiveNodeConfigurator ForLight(ILight lightEntity, - Action configure) => ForLights([lightEntity], configure); + public ILightTransitionReactiveNodeConfigurator ForLight(ILight light, + Action configure) => ForLights([light], configure); /// - public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntityIds, + public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightIds, Action configure) { - CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); + CompositeHelper.ValidateLightSupported(lightIds, Light.Id); return this; } @@ -122,7 +122,7 @@ public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable li public ILightTransitionReactiveNodeConfigurator ForLights(IEnumerable lightEntities, Action configure) { - CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, Light.Id); return this; } } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs index e19d92b..8922732 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveDimmerNode.cs @@ -30,13 +30,13 @@ internal class ReactiveDimmerNode : LightTransitionNode /// Initializes a new instance of the class. /// /// The reactive node to monitor for state changes. - /// The entity ID of the light this dimmer node controls. + /// The entity ID of the light this dimmer node controls. /// The minimum brightness level before the light turns off. /// The step size for each dimming/brightening increment. /// The scheduler used for timing operations. public ReactiveDimmerNode( ReactiveNode reactiveNode, - string lightEntityId, + string lightId, int minBrightness, int brightnessStep, IScheduler scheduler) : base(scheduler) @@ -44,7 +44,7 @@ public ReactiveDimmerNode( reactiveNode.NodeChanged.Subscribe(_ => Reset()); PassThrough = true; - LightEntityId = lightEntityId; + LightId = lightId; _minBrightness = minBrightness; _brightnessStep = brightnessStep; } @@ -52,7 +52,7 @@ public ReactiveDimmerNode( /// /// Gets the entity ID of the light this dimmer node controls. /// - public string LightEntityId { get; } + public string LightId { get; } /// /// Resets the dimming state back to pass-through mode. @@ -118,7 +118,7 @@ public void BrightenStep(DimmingContext context) private bool ShouldDim(DimmingContext context) { - var subjectParameters = context.DimmerNodeOutputParametersInOrder.Single(x => x.entityId == LightEntityId).parametersAfterDim; + var subjectParameters = context.DimmerNodeOutputParametersInOrder.Single(x => x.entityId == LightId).parametersAfterDim; var subjectBrightness = subjectParameters?.Brightness ?? 0; if (subjectBrightness > _minBrightness) { @@ -147,12 +147,12 @@ private bool ShouldDim(DimmingContext context) lightToTurnOff ??= entityId; } - return lightToTurnOff == LightEntityId; + return lightToTurnOff == LightId; } private bool ShouldBrighten(DimmingContext context) { - var subjectParameters = context.DimmerNodeOutputParametersInOrder.Single(x => x.entityId == LightEntityId).parametersAfterDim; + var subjectParameters = context.DimmerNodeOutputParametersInOrder.Single(x => x.entityId == LightId).parametersAfterDim; var subjectBrightness = subjectParameters?.Brightness ?? 0; if (subjectBrightness > _minBrightness) { @@ -176,7 +176,7 @@ private bool ShouldBrighten(DimmingContext context) lightToTurnOn ??= entityId; } - return lightToTurnOn == null || lightToTurnOn == LightEntityId; + return lightToTurnOn == null || lightToTurnOn == LightId; } private void ScheduleInterpolatedLightTransition() diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs index 2df33f7..8135f09 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ReactiveNodeFactory.cs @@ -18,14 +18,14 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode public class ReactiveNodeFactory(IServiceProvider serviceProvider, IScheduler scheduler) { /// - /// Creates a reactive node for a single light entity. + /// Creates a reactive node for a single light. /// - /// The light entity to create the reactive node for. + /// The light to create the reactive node for. /// An action to configure the reactive node. - /// A configured reactive node for the specified light entity. - public IPipelineNode CreateReactiveNode(ILight lightEntity, Action configure) + /// A configured reactive node for the specified light. + public IPipelineNode CreateReactiveNode(ILight light, Action configure) { - return CreateReactiveNodes([lightEntity], configure)[lightEntity.Id]; + return CreateReactiveNodes([light], configure)[light.Id]; } /// @@ -33,21 +33,21 @@ public IPipelineNode CreateReactiveNode(ILight lightEntity, Act /// /// The light entities to create reactive nodes for. /// An action to configure the reactive nodes. - /// A dictionary mapping light entity IDs to their corresponding reactive nodes. + /// A dictionary mapping light IDs to their corresponding reactive nodes. internal Dictionary> CreateReactiveNodes(IEnumerable lightEntities, Action configure) { // Note: we simply assume that these are not groups. - var lightEntityArray = lightEntities.ToArray(); - if (!lightEntityArray.Any()) + var lightArray = lightEntities.ToArray(); + if (!lightArray.Any()) { return new Dictionary>(); } var lightPipelineFactory = serviceProvider.GetRequiredService(); - var reactiveConfigurators = lightEntityArray.ToDictionary(l => l.Id, l => new LightTransitionReactiveNodeConfigurator(serviceProvider, lightPipelineFactory, + var reactiveConfigurators = lightArray.ToDictionary(l => l.Id, l => new LightTransitionReactiveNodeConfigurator(serviceProvider, lightPipelineFactory, this, l, scheduler)); - ILightTransitionReactiveNodeConfigurator configurator = lightEntityArray.Length == 1 - ? reactiveConfigurators[lightEntityArray[0].Id] + ILightTransitionReactiveNodeConfigurator configurator = lightArray.Length == 1 + ? reactiveConfigurators[lightArray[0].Id] : new CompositeLightTransitionReactiveNodeConfigurator( serviceProvider, lightPipelineFactory, @@ -69,7 +69,7 @@ internal Dictionary> CreateReactiveNodes( if (!dimmers.Any()) { - return lightEntityArray.ToDictionary(l => l.Id, l => + return lightArray.ToDictionary(l => l.Id, l => { var reactiveNode = CreateReactiveNode(reactiveConfigurators[l.Id]); return (IPipelineNode)reactiveNode; @@ -80,23 +80,23 @@ internal Dictionary> CreateReactiveNodes( var dimmerNodes = new Dictionary(); var result = new Dictionary>(); - foreach (var lightEntity in lightEntityArray) + foreach (var light in lightArray) { - var reactiveNodeConfigurator = reactiveConfigurators[lightEntity.Id]; + var reactiveNodeConfigurator = reactiveConfigurators[light.Id]; var reactiveNode = CreateReactiveNode(reactiveNodeConfigurator); var lightDimmerOptions = reactiveNodeConfigurator.DimmerOptions; var dimmerNode = new ReactiveDimmerNode( reactiveNode, - lightEntity.Id, + light.Id, lightDimmerOptions.MinBrightness, lightDimmerOptions.BrightnessStep, scheduler); - dimmerNodes.Add(lightEntity.Id, dimmerNode); + dimmerNodes.Add(light.Id, dimmerNode); var innerPipeline = new ReactiveDimmerPipeline(reactiveNode, dimmerNode, registrationManager); - result.Add(lightEntity.Id, innerPipeline); + result.Add(light.Id, innerPipeline); } /* @@ -110,7 +110,7 @@ internal Dictionary> CreateReactiveNodes( var dimPulses = dimmer.Dimming.ToPulsesWhenTrue(dimmerOptions.TimeBetweenSteps, scheduler); var brightenPulses = dimmer.Brightening.ToPulsesWhenTrue(dimmerOptions.TimeBetweenSteps, scheduler); - var orderedDimNodes = dimmerOptions.ValidateAndOrderMultipleLightEntityTypes(dimmerNodes); + var orderedDimNodes = dimmerOptions.ValidateAndOrderMultipleLightTypes(dimmerNodes); var dimSubscriptionDisposables = new CompositeDisposable(); SubscribeToPulses(dimPulses, dimmerNodes, orderedDimNodes, dimSubscriptionDisposables, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs index 83b4cf4..17f3189 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/CompositeLightTransitionToggleConfigurator.cs @@ -93,18 +93,18 @@ public ILightTransitionToggleConfigurator AddPassThrough() return this; } - public ILightTransitionToggleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + public ILightTransitionToggleConfigurator ForLight(string lightId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightId], configure, excludedLightBehaviour); - public ILightTransitionToggleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + public ILightTransitionToggleConfigurator ForLight(ILight light, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([light], configure, excludedLightBehaviour); - public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - var lightEntityIdsArray = - CompositeHelper.ValidateLightsSupported(lightEntityIds, activeConfigurators.Keys); + var lightIdsArray = + CompositeHelper.ValidateLightsSupported(lightIds, activeConfigurators.Keys); - if (lightEntityIdsArray.Length == activeConfigurators.Count) + if (lightIdsArray.Length == activeConfigurators.Count) { configure(this); return this; @@ -112,22 +112,22 @@ public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEnt if (excludedLightBehaviour == ExcludedLightBehaviours.None) { - if (lightEntityIdsArray.Length == 1) + if (lightIdsArray.Length == 1) { - configure(activeConfigurators[lightEntityIdsArray.First()]); + configure(activeConfigurators[lightIdsArray.First()]); return this; } configure(new CompositeLightTransitionToggleConfigurator( - activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + activeConfigurators.Where(kvp => lightIdsArray.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), [])); return this; } configure(new CompositeLightTransitionToggleConfigurator( - activeConfigurators.Where(kvp => lightEntityIdsArray.Contains(kvp.Key)) + activeConfigurators.Where(kvp => lightIdsArray.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - activeConfigurators.Where(kvp => !lightEntityIdsArray.Contains(kvp.Key)) + activeConfigurators.Where(kvp => !lightIdsArray.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs index ed2db10..137f56a 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/ILightTransitionToggleConfigurator.cs @@ -107,31 +107,31 @@ public interface ILightTransitionToggleConfigurator ILightTransitionToggleConfigurator AddPassThrough(); /// - /// Creates a scoped toggle configuration for a specific light entity identified by its entity ID. + /// Creates a scoped toggle configuration for a specific light identified by its entity ID. /// - /// The entity ID of the light to configure. + /// The entity ID of the light to configure. /// An action to configure the toggle for this specific light. /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . /// The configurator instance for method chaining. - ILightTransitionToggleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + ILightTransitionToggleConfigurator ForLight(string lightId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); /// - /// Creates a scoped toggle configuration for a specific light entity. + /// Creates a scoped toggle configuration for a specific light. /// - /// The light entity to configure. + /// The light to configure. /// An action to configure the toggle for this specific light. /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . /// The configurator instance for method chaining. - ILightTransitionToggleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + ILightTransitionToggleConfigurator ForLight(ILight light, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); /// /// Creates a scoped toggle configuration for multiple light entities identified by their entity IDs. /// - /// The entity IDs of the lights to configure. + /// The entity IDs of the lights to configure. /// An action to configure the toggle for these lights. /// Specifies the behavior for lights not included in this scoped configuration. Defaults to . /// The configurator instance for method chaining. - ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); + ILightTransitionToggleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None); /// /// Creates a scoped toggle configuration for multiple light entities. diff --git a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs index 9618f73..00e7738 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Toggle/LightTransitionToggleConfigurator.cs @@ -7,9 +7,9 @@ namespace CodeCasa.AutomationPipelines.Lights.Toggle { - internal class LightTransitionToggleConfigurator(ILight lightEntity, IScheduler scheduler) : ILightTransitionToggleConfigurator + internal class LightTransitionToggleConfigurator(ILight light, IScheduler scheduler) : ILightTransitionToggleConfigurator { - public ILight LightEntity { get; } = lightEntity; + public ILight Light { get; } = light; internal TimeSpan? ToggleTimeout { get; private set; } internal bool? IncludeOffValue { get; private set; } internal List>> NodeFactories @@ -97,19 +97,19 @@ public ILightTransitionToggleConfigurator AddPassThrough() return Add(new PassThroughNode()); } - public ILightTransitionToggleConfigurator ForLight(string lightEntityId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntityId], configure, excludedLightBehaviour); + public ILightTransitionToggleConfigurator ForLight(string lightId, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightId], configure, excludedLightBehaviour); - public ILightTransitionToggleConfigurator ForLight(ILight lightEntity, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([lightEntity], configure, excludedLightBehaviour); + public ILightTransitionToggleConfigurator ForLight(ILight light, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) => ForLights([light], configure, excludedLightBehaviour); - public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntityIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) + public ILightTransitionToggleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ValidateLightSupported(lightEntityIds, LightEntity.Id); + CompositeHelper.ValidateLightSupported(lightIds, Light.Id); return this; } public ILightTransitionToggleConfigurator ForLights(IEnumerable lightEntities, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, LightEntity.Id); + CompositeHelper.ResolveGroupsAndValidateLightSupported(lightEntities, Light.Id); return this; } From ce52de6ad0dba2728f04f967399b88463ee8f2a8 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:39:54 +0100 Subject: [PATCH 15/18] Removing redundant scheduler. --- .../CompositeLightTransitionCycleConfigurator.cs | 14 +++++++------- ...CompositeLightTransitionPipelineConfigurator.cs | 6 ++---- .../Pipeline/LightPipelineFactory.cs | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs index abfc27b..df9b5e9 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/CompositeLightTransitionCycleConfigurator.cs @@ -90,10 +90,10 @@ public ILightTransitionCycleConfigurator ForLights(IEnumerable lightIds, Action configure, ExcludedLightBehaviours excludedLightBehaviour = ExcludedLightBehaviours.None) { - var lightIds = + var lightIdArray = CompositeHelper.ValidateLightsSupported(lightIds, activeConfigurators.Keys); - if (lightIds.Length == activeConfigurators.Count) + if (lightIdArray.Length == activeConfigurators.Count) { configure(this); return this; @@ -101,22 +101,22 @@ public ILightTransitionCycleConfigurator ForLights(IEnumerable lightIds, if (excludedLightBehaviour == ExcludedLightBehaviours.None) { - if (lightIds.Length == 1) + if (lightIdArray.Length == 1) { - configure(activeConfigurators[lightIds.First()]); + configure(activeConfigurators[lightIdArray.First()]); return this; } configure(new CompositeLightTransitionCycleConfigurator( - activeConfigurators.Where(kvp => lightIds.Contains(kvp.Key)) + activeConfigurators.Where(kvp => lightIdArray.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), [])); return this; } configure(new CompositeLightTransitionCycleConfigurator( - activeConfigurators.Where(kvp => lightIds.Contains(kvp.Key)) + activeConfigurators.Where(kvp => lightIdArray.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - activeConfigurators.Where(kvp => !lightIds.Contains(kvp.Key)) + activeConfigurators.Where(kvp => !lightIdArray.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs index cfadec6..15ab23b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs @@ -11,8 +11,7 @@ public partial class CompositeLightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, - Dictionary nodeContainers, - IScheduler scheduler) + Dictionary nodeContainers) : ILightTransitionPipelineConfigurator { public Dictionary NodeContainers { get; } = nodeContainers; @@ -89,8 +88,7 @@ public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightI } compositeNodeBuilder(new CompositeLightTransitionPipelineConfigurator(serviceProvider, lightPipelineFactory, reactiveNodeFactory, NodeContainers - .Where(kvp => lightIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - scheduler)); + .Where(kvp => lightIdsArray.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs index c0c1a6d..85ecb8e 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs @@ -63,7 +63,7 @@ internal Dictionary> CreateLightPipelines(IEn serviceProvider, this, reactiveNodeFactory, - configurators, scheduler); + configurators); pipelineBuilder(configurator); return configurators.ToDictionary(kvp => kvp.Key, kvp => From b6e54ed584f345c89d114b0035e72747dc105ce8 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:43:47 +0100 Subject: [PATCH 16/18] Adding xml comments. --- ...ionPipelineConfigurator.Reactive.Toggle.cs | 7 +++++++ ...htTransitionPipelineConfigurator.Switch.cs | 18 ++++++++++++++++ ...ightTransitionPipelineConfigurator.When.cs | 20 ++++++++++++++++++ ...siteLightTransitionPipelineConfigurator.cs | 15 +++++++++++++ ...ionPipelineConfigurator.Reactive.Toggle.cs | 7 +++++++ ...htTransitionPipelineConfigurator.Switch.cs | 18 ++++++++++++++++ ...ightTransitionPipelineConfigurator.When.cs | 20 ++++++++++++++++++ .../LightTransitionPipelineConfigurator.cs | 21 ++++++++++++++++++- 8 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs index 205bff2..eea0b87 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -6,42 +6,49 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; public partial class CompositeLightTransitionPipelineConfigurator { + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, params LightParameters[] lightParameters) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightTransitions) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, params LightTransition[] lightTransitions) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, IEnumerable>> nodeFactories) { return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, params Func>[] nodeFactories) { return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, Action configure) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs index 68e37bd..5f7f035 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs @@ -11,6 +11,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; public partial class CompositeLightTransitionPipelineConfigurator { + /// public ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, LightParameters falseLightParameters) where TObservable : IObservable { @@ -18,6 +19,7 @@ public ILightTransitionPipelineConfigurator Switch(LightParameters return this; } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightParameters trueLightParameters, LightParameters falseLightParameters) { @@ -25,6 +27,7 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator Switch(Func trueLightParametersFactory, Func falseLightParametersFactory) where TObservable : IObservable { @@ -32,6 +35,7 @@ public ILightTransitionPipelineConfigurator Switch(Func public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightParametersFactory, Func falseLightParametersFactory) { @@ -39,6 +43,7 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator Switch(LightTransition trueLightTransition, LightTransition falseLightTransition) where TObservable : IObservable { @@ -46,6 +51,7 @@ public ILightTransitionPipelineConfigurator Switch(LightTransition return this; } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightTransition trueLightTransition, LightTransition falseLightTransition) { @@ -53,6 +59,7 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator Switch(Func trueLightTransitionFactory, Func falseLightTransitionFactory) where TObservable : IObservable { @@ -60,6 +67,7 @@ public ILightTransitionPipelineConfigurator Switch(Func public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightTransitionFactory, Func falseLightTransitionFactory) { @@ -67,12 +75,14 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator Switch(Func> trueNodeFactory, Func> falseNodeFactory) where TObservable : IObservable { NodeContainers.Values.ForEach(b => b.Switch(trueNodeFactory, falseNodeFactory)); return this; } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func> trueNodeFactory, Func> falseNodeFactory) { @@ -80,18 +90,21 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator Switch() where TObservable : IObservable where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode { NodeContainers.Values.ForEach(b => b.Switch()); return this; } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable) where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode { NodeContainers.Values.ForEach(b => b.Switch(observable)); return this; } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable { @@ -104,6 +117,7 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(A return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action trueConfigure, Action falseConfigure) { @@ -113,12 +127,14 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable !x), falseConfigure)); } + /// public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable { var observable = ActivatorUtilities.CreateInstance(serviceProvider); return AddPipelineSwitch(observable, trueConfigure, falseConfigure); } + /// public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action trueConfigure, Action falseConfigure) { @@ -128,11 +144,13 @@ public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable .On(observable.Where(x => x), falseConfigure)); } + /// public ILightTransitionPipelineConfigurator TurnOnOff() where TObservable : IObservable { return Switch(LightTransition.On(), LightTransition.Off()); } + /// public ILightTransitionPipelineConfigurator TurnOnOff(IObservable observable) { return Switch(observable, LightTransition.On(), LightTransition.Off()); diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs index 5ec8750..729e8bf 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs @@ -11,6 +11,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; public partial class CompositeLightTransitionPipelineConfigurator { + /// public ILightTransitionPipelineConfigurator When(LightParameters lightParameters) where TObservable : IObservable { @@ -18,6 +19,7 @@ public ILightTransitionPipelineConfigurator When(LightParameters li return this; } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, LightParameters lightParameters) { @@ -25,6 +27,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator When( Func lightParametersFactory) where TObservable : IObservable { @@ -32,6 +35,7 @@ public ILightTransitionPipelineConfigurator When( return this; } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, Func lightParametersFactory) { @@ -39,6 +43,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator When(LightTransition lightTransition) where TObservable : IObservable { @@ -46,6 +51,7 @@ public ILightTransitionPipelineConfigurator When(LightTransition li return this; } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, LightTransition lightTransition) { @@ -53,6 +59,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator When( Func lightTransitionFactory) where TObservable : IObservable { @@ -60,6 +67,7 @@ public ILightTransitionPipelineConfigurator When( return this; } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, Func lightTransitionFactory) { @@ -67,6 +75,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator When( Func> nodeFactory) where TObservable : IObservable { @@ -74,6 +83,7 @@ public ILightTransitionPipelineConfigurator When( return this; } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, Func> nodeFactory) { @@ -81,6 +91,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, return this; } + /// public ILightTransitionPipelineConfigurator When() where TObservable : IObservable where TNode : IPipelineNode { @@ -88,6 +99,7 @@ public ILightTransitionPipelineConfigurator When() return this; } + /// public ILightTransitionPipelineConfigurator When(IObservable observable) where TNode : IPipelineNode { @@ -95,6 +107,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observ return this; } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action configure) where TObservable : IObservable { /* @@ -106,6 +119,7 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Act return AddReactiveNodeWhen(observable, configure); } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action configure) { // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. @@ -114,12 +128,14 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable !x))); } + /// public ILightTransitionPipelineConfigurator AddPipelineWhen(Action configure) where TObservable : IObservable { var observable = ActivatorUtilities.CreateInstance(serviceProvider); return AddPipelineWhen(observable, configure); } + /// public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action configure) { // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. @@ -128,23 +144,27 @@ public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable ob .PassThroughOn(observable.Where(x => !x))); } + /// public ILightTransitionPipelineConfigurator TurnOffWhen() where TObservable : IObservable { NodeContainers.Values.ForEach(b => b.TurnOffWhen()); return this; } + /// public ILightTransitionPipelineConfigurator TurnOffWhen(IObservable observable) { NodeContainers.Values.ForEach(b => b.TurnOffWhen(observable)); return this; } + /// public ILightTransitionPipelineConfigurator TurnOnWhen() where TObservable : IObservable { return When(LightTransition.On()); } + /// public ILightTransitionPipelineConfigurator TurnOnWhen(IObservable observable) { return When(observable, LightTransition.On()); diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs index 15ab23b..97de4f2 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs @@ -7,6 +7,10 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline { + /// + /// Configures light transition pipelines for multiple light entities as a composite. + /// This configurator applies configurations across all included lights and allows for selective scoping to subsets of lights. + /// public partial class CompositeLightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, @@ -16,24 +20,28 @@ public partial class CompositeLightTransitionPipelineConfigurator( { public Dictionary NodeContainers { get; } = nodeContainers; + /// public ILightTransitionPipelineConfigurator SetName(string name) { NodeContainers.Values.ForEach(b => b.SetName(name)); return this; } + /// public ILightTransitionPipelineConfigurator AddNode() where TNode : IPipelineNode { NodeContainers.Values.ForEach(b => b.AddNode()); return this; } + /// public ILightTransitionPipelineConfigurator AddNode(Func> nodeFactory) { NodeContainers.Values.ForEach(b => b.AddNode(nodeFactory)); return this; } + /// public ILightTransitionPipelineConfigurator AddReactiveNode( Action configure) { @@ -43,6 +51,7 @@ public ILightTransitionPipelineConfigurator AddReactiveNode( return this; } + /// public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) { var pipelines = lightPipelineFactory.CreateLightPipelines(NodeContainers.Select(c => c.Value.Light), @@ -51,11 +60,13 @@ public ILightTransitionPipelineConfigurator AddPipeline(Action public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer) { return AddDimmer(dimmer, _ => { }); } + /// public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action dimOptions) { return AddReactiveNode(c => @@ -64,12 +75,15 @@ public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action public ILightTransitionPipelineConfigurator ForLight(string lightId, Action compositeNodeBuilder) => ForLights([lightId], compositeNodeBuilder); + /// public ILightTransitionPipelineConfigurator ForLight(ILight light, Action compositeNodeBuilder) => ForLights([light], compositeNodeBuilder); + /// public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightIds, Action compositeNodeBuilder) { @@ -92,6 +106,7 @@ public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightI return this; } + /// public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, Action compositeNodeBuilder) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs index af01003..76c5315 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -6,42 +6,49 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; public partial class LightTransitionPipelineConfigurator { + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, params LightParameters[] lightParameters) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightParameters)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightTransitions) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, params LightTransition[] lightTransitions) { return AddReactiveNode(c => c.AddToggle(triggerObservable, lightTransitions)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, IEnumerable>> nodeFactories) { return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, params Func>[] nodeFactories) { return AddReactiveNode(c => c.AddToggle(triggerObservable, nodeFactories)); } + /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, Action configure) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs index 18e533d..52ad465 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs @@ -11,42 +11,49 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; public partial class LightTransitionPipelineConfigurator { + /// public ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, LightParameters falseLightParameters) where TObservable : IObservable { return Switch(trueLightParameters.AsTransition(), falseLightParameters.AsTransition()); } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightParameters trueLightParameters, LightParameters falseLightParameters) { return Switch(observable, trueLightParameters.AsTransition(), falseLightParameters.AsTransition()); } + /// public ILightTransitionPipelineConfigurator Switch(Func trueLightParametersFactory, Func falseLightParametersFactory) where TObservable : IObservable { return Switch(c => falseLightParametersFactory(c).AsTransition(), c => trueLightParametersFactory(c).AsTransition()); } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightParametersFactory, Func falseLightParametersFactory) { return Switch(observable, c => trueLightParametersFactory(c).AsTransition(), c => falseLightParametersFactory(c).AsTransition()); } + /// public ILightTransitionPipelineConfigurator Switch(LightTransition trueLightTransition, LightTransition falseLightTransition) where TObservable : IObservable { return Switch(trueLightTransition, falseLightTransition); } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, LightTransition trueLightTransition, LightTransition falseLightTransition) { return Switch(observable, trueLightTransition, falseLightTransition); } + /// public ILightTransitionPipelineConfigurator Switch(Func trueLightTransitionFactory, Func falseLightTransitionFactory) where TObservable : IObservable { @@ -55,6 +62,7 @@ public ILightTransitionPipelineConfigurator Switch(Func new StaticLightTransitionNode(falseLightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func trueLightTransitionFactory, Func falseLightTransitionFactory) { @@ -64,12 +72,14 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, c => new StaticLightTransitionNode(falseLightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); } + /// public ILightTransitionPipelineConfigurator Switch(Func> trueNodeFactory, Func> falseNodeFactory) where TObservable : IObservable { var observable = serviceProvider.CreateInstanceWithinContext(Light); return Switch(observable, trueNodeFactory, falseNodeFactory); } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable, Func> trueNodeFactory, Func> falseNodeFactory) { @@ -78,12 +88,14 @@ public ILightTransitionPipelineConfigurator Switch(IObservable observable, .On(observable.Where(x => !x), falseNodeFactory)); } + /// public ILightTransitionPipelineConfigurator Switch() where TObservable : IObservable where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode { var observable = serviceProvider.CreateInstanceWithinContext(Light); return Switch(observable); } + /// public ILightTransitionPipelineConfigurator Switch(IObservable observable) where TTrueNode : IPipelineNode where TFalseNode : IPipelineNode { return AddReactiveNode(c => c @@ -91,12 +103,14 @@ public ILightTransitionPipelineConfigurator Switch(IObser .On(observable.Where(x => !x))); } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable { var observable = serviceProvider.CreateInstanceWithinContext(Light); return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action trueConfigure, Action falseConfigure) { @@ -105,22 +119,26 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable !x), falseConfigure)); } + /// public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action trueConfigure, Action falseConfigure) where TObservable : IObservable { return Switch(c => lightPipelineFactory.CreateLightPipeline(c.Light, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.Light, falseConfigure)); } + /// public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action trueConfigure, Action falseConfigure) { return Switch(observable, c => lightPipelineFactory.CreateLightPipeline(c.Light, trueConfigure), c => lightPipelineFactory.CreateLightPipeline(c.Light, falseConfigure)); } + /// public ILightTransitionPipelineConfigurator TurnOnOff() where TObservable : IObservable { return Switch(LightTransition.On(), LightTransition.Off()); } + /// public ILightTransitionPipelineConfigurator TurnOnOff(IObservable observable) { return Switch(observable, LightTransition.On(), LightTransition.Off()); diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs index 116b326..f46f234 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs @@ -11,54 +11,63 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; public partial class LightTransitionPipelineConfigurator { + /// public ILightTransitionPipelineConfigurator When(LightParameters lightParameters) where TObservable : IObservable { return When(lightParameters.AsTransition()); } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, LightParameters lightParameters) { return When(observable, lightParameters.AsTransition()); } + /// public ILightTransitionPipelineConfigurator When( Func lightParametersFactory) where TObservable : IObservable { return When(c => lightParametersFactory(c).AsTransition()); } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, Func lightParametersFactory) { return When(observable, c => lightParametersFactory(c).AsTransition()); } + /// public ILightTransitionPipelineConfigurator When(LightTransition lightTransition) where TObservable : IObservable { return When(_ => lightTransition); } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, LightTransition lightTransition) { return When(observable, _ => lightTransition); } + /// public ILightTransitionPipelineConfigurator When( Func lightTransitionFactory) where TObservable : IObservable { return When(c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, Func lightTransitionFactory) { return When(observable, c => new StaticLightTransitionNode(lightTransitionFactory(c), c.ServiceProvider.GetRequiredService())); } + /// public ILightTransitionPipelineConfigurator When( Func> nodeFactory) where TObservable : IObservable { @@ -66,6 +75,7 @@ public ILightTransitionPipelineConfigurator When( return When(observable, nodeFactory); } + /// public ILightTransitionPipelineConfigurator When(IObservable observable, Func> nodeFactory) { @@ -74,6 +84,7 @@ public ILightTransitionPipelineConfigurator When(IObservable observable, .PassThroughOn(observable.Where(x => !x))); } + /// public ILightTransitionPipelineConfigurator When() where TObservable : IObservable where TNode : IPipelineNode @@ -82,6 +93,7 @@ public ILightTransitionPipelineConfigurator When() return When(observable); } + /// public ILightTransitionPipelineConfigurator When(IObservable observable) where TNode : IPipelineNode { @@ -90,12 +102,14 @@ public ILightTransitionPipelineConfigurator When(IObservable observ .PassThroughOn(observable.Where(x => !x))); } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action configure) where TObservable : IObservable { var observable = serviceProvider.CreateInstanceWithinContext(Light); return AddReactiveNodeWhen(observable, configure); } + /// public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action configure) { return AddReactiveNode(c => c @@ -103,31 +117,37 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable !x))); } + /// public ILightTransitionPipelineConfigurator AddPipelineWhen(Action pipelineConfigurator) where TObservable : IObservable { return When(c => lightPipelineFactory.CreateLightPipeline(c.Light, pipelineConfigurator)); } + /// public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action pipelineConfigurator) { return When(observable, c => lightPipelineFactory.CreateLightPipeline(c.Light, pipelineConfigurator)); } + /// public ILightTransitionPipelineConfigurator TurnOffWhen() where TObservable : IObservable { return When(LightTransition.Off()); } + /// public ILightTransitionPipelineConfigurator TurnOffWhen(IObservable observable) { return When(observable, LightTransition.Off()); } + /// public ILightTransitionPipelineConfigurator TurnOnWhen() where TObservable : IObservable { return When(LightTransition.On()); } + /// public ILightTransitionPipelineConfigurator TurnOnWhen(IObservable observable) { return When(observable, LightTransition.On()); diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs index 506485e..c026d86 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs @@ -7,7 +7,10 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline { - /// + /// + /// Configures a light transition pipeline for a single light entity. + /// This configurator allows adding pipeline nodes, reactive nodes, dimmers, and conditional logic to light automation. + /// public partial class LightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, @@ -28,41 +31,52 @@ public ILightTransitionPipelineConfigurator throw new NotImplementedException(); } + /// public ILightTransitionPipelineConfigurator SetName(string name) { Name = name; return this; } + /// public ILightTransitionPipelineConfigurator AddNode() where TNode : IPipelineNode { _nodes.Add(serviceProvider.CreateInstanceWithinContext(Light)); return this; } + /// + /// Adds a pipeline node to the pipeline. + /// + /// The pipeline node to add. + /// The configurator instance for method chaining. public ILightTransitionPipelineConfigurator AddNode(IPipelineNode node) { _nodes.Add(node); return this; } + /// public ILightTransitionPipelineConfigurator AddNode(Func> nodeFactory) { _nodes.Add(nodeFactory(new LightPipelineContext(serviceProvider, Light))); return this; } + /// public ILightTransitionPipelineConfigurator AddReactiveNode( Action configure) { return AddNode(reactiveNodeFactory.CreateReactiveNode(Light, configure)); } + /// public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer) { return AddDimmer(dimmer, _ => { }); } + /// public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action dimOptions) { return AddReactiveNode(c => @@ -71,13 +85,16 @@ public ILightTransitionPipelineConfigurator AddDimmer(IDimmer dimmer, Action public ILightTransitionPipelineConfigurator ForLight(string lightId, Action compositeNodeBuilder) => ForLights([lightId], compositeNodeBuilder); + /// public ILightTransitionPipelineConfigurator ForLight(ILight light, Action compositeNodeBuilder) => ForLights([light], compositeNodeBuilder); + /// public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightIds, Action compositeNodeBuilder) { @@ -85,6 +102,7 @@ public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightI return this; } + /// public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightEntities, Action compositeNodeBuilder) { @@ -92,6 +110,7 @@ public ILightTransitionPipelineConfigurator ForLights(IEnumerable lightE return this; } + /// public ILightTransitionPipelineConfigurator AddPipeline(Action pipelineNodeOptions) => AddNode(lightPipelineFactory.CreateLightPipeline(Light, pipelineNodeOptions)); } } From a7cae12715e37f95af69267c8f00c7d2ded0d0c6 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:45:28 +0100 Subject: [PATCH 17/18] Making classes internal. --- ...mpositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs | 2 +- ...positeLightTransitionPipelineConfigurator.Reactive.Toggle.cs | 2 +- .../CompositeLightTransitionPipelineConfigurator.Switch.cs | 2 +- .../CompositeLightTransitionPipelineConfigurator.When.cs | 2 +- .../Pipeline/CompositeLightTransitionPipelineConfigurator.cs | 2 +- .../LightTransitionPipelineConfigurator.Reactive.Cycle.cs | 2 +- .../LightTransitionPipelineConfigurator.Reactive.Toggle.cs | 2 +- .../Pipeline/LightTransitionPipelineConfigurator.Switch.cs | 2 +- .../Pipeline/LightTransitionPipelineConfigurator.When.cs | 2 +- .../Pipeline/LightTransitionPipelineConfigurator.cs | 2 +- .../CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs | 2 +- .../CompositeLightTransitionReactiveNodeConfigurator.On.cs | 2 +- .../CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs | 2 +- .../CompositeLightTransitionReactiveNodeConfigurator.cs | 2 +- .../LightTransitionReactiveNodeConfigurator.Cycle.cs | 2 +- .../ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs | 2 +- .../LightTransitionReactiveNodeConfigurator.Toggle.cs | 2 +- .../ReactiveNode/LightTransitionReactiveNodeConfigurator.cs | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs index 53420f8..8646086 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Cycle.cs @@ -3,7 +3,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class CompositeLightTransitionPipelineConfigurator +internal partial class CompositeLightTransitionPipelineConfigurator { public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs index eea0b87..ecdf339 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -4,7 +4,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class CompositeLightTransitionPipelineConfigurator +internal partial class CompositeLightTransitionPipelineConfigurator { /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs index 5f7f035..5bbdb00 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs @@ -9,7 +9,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class CompositeLightTransitionPipelineConfigurator +internal partial class CompositeLightTransitionPipelineConfigurator { /// public ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs index 729e8bf..320c1a6 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs @@ -9,7 +9,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class CompositeLightTransitionPipelineConfigurator +internal partial class CompositeLightTransitionPipelineConfigurator { /// public ILightTransitionPipelineConfigurator When(LightParameters lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs index 97de4f2..dffa322 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs @@ -11,7 +11,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline /// Configures light transition pipelines for multiple light entities as a composite. /// This configurator applies configurations across all included lights and allows for selective scoping to subsets of lights. /// - public partial class CompositeLightTransitionPipelineConfigurator( + internal partial class CompositeLightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs index 03308f6..064a965 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Cycle.cs @@ -3,7 +3,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class LightTransitionPipelineConfigurator +internal partial class LightTransitionPipelineConfigurator { public ILightTransitionPipelineConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs index 76c5315..c81a7d8 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Reactive.Toggle.cs @@ -4,7 +4,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class LightTransitionPipelineConfigurator +internal partial class LightTransitionPipelineConfigurator { /// public ILightTransitionPipelineConfigurator AddToggle(IObservable triggerObservable, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs index 52ad465..9294ed2 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs @@ -9,7 +9,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class LightTransitionPipelineConfigurator +internal partial class LightTransitionPipelineConfigurator { /// public ILightTransitionPipelineConfigurator Switch(LightParameters trueLightParameters, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs index f46f234..c2616f2 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs @@ -9,7 +9,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; -public partial class LightTransitionPipelineConfigurator +internal partial class LightTransitionPipelineConfigurator { /// public ILightTransitionPipelineConfigurator When(LightParameters lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs index c026d86..60153b1 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.cs @@ -11,7 +11,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline /// Configures a light transition pipeline for a single light entity. /// This configurator allows adding pipeline nodes, reactive nodes, dimmers, and conditional logic to light automation. /// - public partial class LightTransitionPipelineConfigurator( + internal partial class LightTransitionPipelineConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs index a70a6c2..cf08992 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs @@ -7,7 +7,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public partial class CompositeLightTransitionReactiveNodeConfigurator +internal partial class CompositeLightTransitionReactiveNodeConfigurator { /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs index d6f6dfc..38299aa 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -8,7 +8,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public partial class CompositeLightTransitionReactiveNodeConfigurator +internal partial class CompositeLightTransitionReactiveNodeConfigurator { /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, LightParameters lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs index 76a9e19..1d4762b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Toggle.cs @@ -8,7 +8,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public partial class CompositeLightTransitionReactiveNodeConfigurator +internal partial class CompositeLightTransitionReactiveNodeConfigurator { /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs index 54b8100..20efd58 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.cs @@ -11,7 +11,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; /// Configures light transition reactive nodes for multiple light entities as a composite. /// This configurator applies configurations across all included lights and allows for selective scoping to subsets of lights. /// -public partial class CompositeLightTransitionReactiveNodeConfigurator( +internal partial class CompositeLightTransitionReactiveNodeConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs index 62cf8ce..019de03 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs @@ -7,7 +7,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public partial class LightTransitionReactiveNodeConfigurator +internal partial class LightTransitionReactiveNodeConfigurator { /// public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable triggerObservable, IEnumerable lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs index db9eaca..3dc809c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -9,7 +9,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public partial class LightTransitionReactiveNodeConfigurator +internal partial class LightTransitionReactiveNodeConfigurator { /// public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs index 966513a..d9b56ed 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Toggle.cs @@ -8,7 +8,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public partial class LightTransitionReactiveNodeConfigurator +internal partial class LightTransitionReactiveNodeConfigurator { /// public ILightTransitionReactiveNodeConfigurator AddToggle(IObservable triggerObservable, IEnumerable lightParameters) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs index 7df4053..2393404 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs @@ -14,7 +14,7 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; /// Configures a light transition reactive node for a single light. /// This configurator allows adding reactive dimmer controls, node sources, and scoped configurations for light automation. /// -public partial class LightTransitionReactiveNodeConfigurator( +internal partial class LightTransitionReactiveNodeConfigurator( IServiceProvider serviceProvider, LightPipelineFactory lightPipelineFactory, ReactiveNodeFactory reactiveNodeFactory, From 0a5083bec7068f9fc0f158e9d06e99475728af81 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 30 Dec 2025 11:52:07 +0100 Subject: [PATCH 18/18] Fixing last warnings. --- .../ILightTransitionCycleConfigurator.cs | 2 ++ .../Extensions/EnumerableExtensions.cs | 2 +- .../LightTransitionNodeExtensions.cs | 22 +++++++++++++++++++ .../Extensions/ObservableExtensions.cs | 2 +- .../ILightTransitionPipelineConfigurator.cs | 4 ++++ .../Pipeline/LightPipelineFactory.cs | 2 +- ...LightTransitionReactiveNodeConfigurator.cs | 4 ++++ 7 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs index f3ae372..17aa318 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Cycle/ILightTransitionCycleConfigurator.cs @@ -26,6 +26,7 @@ public interface ILightTransitionCycleConfigurator /// Adds light parameters to the cycle. The cycle will advance to these parameters when the current state matches the previous entry in the cycle. /// /// The light parameters to add to the cycle. + /// An optional equality comparer for determining if light parameters match. If null, the default equality comparison is used. /// The configurator instance for method chaining. ILightTransitionCycleConfigurator Add(LightParameters lightParameters, IEqualityComparer? comparer = null); @@ -52,6 +53,7 @@ public interface ILightTransitionCycleConfigurator /// Adds a light transition to the cycle. The cycle will advance to this transition when the current state matches the previous entry in the cycle. /// /// The light transition to add to the cycle. + /// An optional equality comparer for determining if light parameters match. If null, the default equality comparison is used. /// The configurator instance for method chaining. ILightTransitionCycleConfigurator Add(LightTransition lightTransition, IEqualityComparer? comparer = null); diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs index 694a6ec..e4ead0a 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/EnumerableExtensions.cs @@ -1,6 +1,6 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions { - public static class EnumerableExtensions + internal static class EnumerableExtensions { public static void ForEach(this IEnumerable source, Action action) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs index e2499eb..7fc455e 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionNodeExtensions.cs @@ -8,14 +8,36 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions { + /// + /// Provides extension methods for instances. + /// These extensions enable adding timeout behavior to light transition nodes. + /// public static class LightTransitionNodeExtensions { + /// + /// Creates a timeout node that automatically turns off the light after the specified time span. + /// The timeout is not reset by any external events. + /// + /// The pipeline node to wrap with timeout behavior. + /// The duration after which the light will turn off. + /// The scheduler to use for timing operations. + /// A new pipeline node that wraps the original node with timeout behavior. public static IPipelineNode TurnOffAfter(this IPipelineNode node, TimeSpan timeSpan, IScheduler scheduler) { return new ResettableTimeoutNode(node, timeSpan, Observable.Empty(), scheduler); } + /// + /// Creates a timeout node that automatically turns off the light after the specified time span. + /// The timeout can be reset when the observable emits a value. + /// + /// The type of values emitted by the reset timer observable. + /// The pipeline node to wrap with timeout behavior. + /// The duration after which the light will turn off. + /// An observable that resets the timeout timer when it emits a value. + /// The scheduler to use for timing operations. + /// A new pipeline node that wraps the original node with resettable timeout behavior. public static IPipelineNode TurnOffAfter(this IPipelineNode node, TimeSpan timeSpan, IObservable resetTimerObservable, IScheduler scheduler) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs index 71d9081..d745840 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Extensions/ObservableExtensions.cs @@ -4,7 +4,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Extensions { - public static class ObservableExtensions + internal static class ObservableExtensions { public static IObservable ToPulsesWhenTrue(this IObservable source, TimeSpan timeBetweenPulses, IScheduler scheduler) { diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs index cb87555..c8a602d 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.cs @@ -5,6 +5,10 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline; +/// +/// Configures a light transition pipeline for controlling light behavior through layered logic nodes. +/// Supports adding nodes, reactive nodes, dimmers, and conditional logic with When/Switch operators. +/// public partial interface ILightTransitionPipelineConfigurator { /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs index 85ecb8e..ae9f52c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs @@ -10,7 +10,7 @@ namespace CodeCasa.AutomationPipelines.Lights.Pipeline /// Factory for creating and configuring light transition pipelines. /// public class LightPipelineFactory( - ILogger> logger, IServiceProvider serviceProvider, ReactiveNodeFactory reactiveNodeFactory, IScheduler scheduler) + ILogger> logger, IServiceProvider serviceProvider, ReactiveNodeFactory reactiveNodeFactory) { /// /// Sets up a light pipeline for the specified light and configures it with the provided builder action. diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs index 2484262..29742dd 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.cs @@ -4,6 +4,10 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; +/// +/// Configures a light transition reactive node for controlling light behavior through reactive event handling. +/// Supports adding reactive dimmers, uncoupled dimmers, dynamic node sources, and scoped configurations. +/// public partial interface ILightTransitionReactiveNodeConfigurator { ///