Skip to content

Commit 84a3dbb

Browse files
authored
Allowing AddReactiveDimmer to be used on ForLights (#227)
* Dimmers are not possible to be configured per light. * Grouping dimmers by time between steps * Using only relevant dimmers in the dimming context. * Using the options of a group rather than one option for all dimmers. * Passing light context through node and pipeline libraries. * Adding pipeline context to pipeline lights.
1 parent b840320 commit 84a3dbb

9 files changed

Lines changed: 160 additions & 111 deletions

src/CodeCasa.AutomationPipelines.Lights/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static ILightTransitionReactiveNodeConfigurator<T> HandleExternalLightSta
1515
configurator.AddNodeSource(sp =>
1616
{
1717
var light = sp.GetRequiredService<ILight>();
18-
var context = sp.GetRequiredService<LightPipelineContext>();
18+
var context = sp.GetService<LightPipelineContext>() ?? throw new InvalidOperationException($"{nameof(HandleExternalLightStateChanges)} can only be applies to reactive nodes hosted in a pipeline.");
1919
return light.StateChanges()
2020
.Where(l =>
2121
l.New?.Brightness == 0 &&

src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public ILightTransitionPipelineConfigurator<TLight> AddNode(Func<IServiceProvide
5959
public ILightTransitionPipelineConfigurator<TLight> AddReactiveNode(
6060
Action<ILightTransitionReactiveNodeConfigurator<TLight>> configure)
6161
{
62-
var nodes = reactiveNodeFactory.CreateReactiveNodes(serviceProvider, NodeContainers.Select(nc => nc.Value.Light),
62+
var nodes = reactiveNodeFactory.CreateReactiveNodes(serviceProvider, NodeContainers.ToDictionary(nc => nc.Value.Light, nc => nc.Value.ServiceProvider),
6363
configure
6464
.ApplyHierarchySettings(HierarchyPath, LoggingEnabled ?? false)
6565
.SetObservableSharingStrategy(_observableSharingStrategy));
@@ -70,9 +70,10 @@ public ILightTransitionPipelineConfigurator<TLight> AddReactiveNode(
7070
/// <inheritdoc/>
7171
public ILightTransitionPipelineConfigurator<TLight> AddPipeline(Action<ILightTransitionPipelineConfigurator<TLight>> configure)
7272
{
73-
var pipelines = lightPipelineFactory.CreateLightPipelines(serviceProvider, NodeContainers.Select(c => c.Value.Light), configure
74-
.ApplyHierarchySettings(HierarchyPath, LoggingEnabled ?? false)
75-
.SetObservableSharingStrategy(_observableSharingStrategy));
73+
var pipelines = lightPipelineFactory.CreateLightPipelines(serviceProvider, NodeContainers.ToDictionary(nc => nc.Value.Light, nc => nc.Value.ServiceProvider),
74+
configure
75+
.ApplyHierarchySettings(HierarchyPath, LoggingEnabled ?? false)
76+
.SetObservableSharingStrategy(_observableSharingStrategy));
7677
NodeContainers.ForEach(kvp => kvp.Value.AddNode(pipelines[kvp.Key]));
7778
return this;
7879
}

src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightPipelineFactory.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public IAsyncDisposable SetupLightPipeline<TLight>(TLight light,
2828
Action<ILightTransitionPipelineConfigurator<TLight>> pipelineBuilder) where TLight : ILight
2929
{
3030
var disposables = new CompositeAsyncDisposable();
31-
var pipelines = CreateLightPipelines(rootServiceProvider, light.Flatten().Cast<TLight>(), pipelineBuilder);
31+
var pipelines = CreateLightPipelines(rootServiceProvider, light.Flatten().Cast<TLight>().ToDictionary(l => l, _ => rootServiceProvider), pipelineBuilder);
3232
foreach (var pipeline in pipelines.Values)
3333
{
3434
disposables.Add(pipeline);
@@ -49,13 +49,13 @@ public IPipeline<LightTransition> CreateLightPipeline<TLight>(TLight light,
4949
/// <summary>
5050
/// Creates a single light pipeline for the specified light.
5151
/// </summary>
52-
/// <param name="serviceProvider">The service provider passed to the configurators. This method is used within the library to allow passing the composite service provider. This is necessary because the factory will only receive the root service provider even if resolved inside the context scope.</param>
52+
/// <param name="compositeServiceProvider">The service provider passed to the configurators. This method is used within the library to allow passing the composite service provider. This is necessary because the factory will only receive the root service provider even if resolved inside the context scope.</param>
5353
/// <param name="light">The light to create a pipeline for.</param>
5454
/// <param name="pipelineBuilder">An action to configure the pipeline behavior.</param>
5555
/// <returns>A configured pipeline for controlling the specified light.</returns>
56-
internal IPipeline<LightTransition> CreateLightPipeline<TLight>(IServiceProvider serviceProvider, TLight light, Action<ILightTransitionPipelineConfigurator<TLight>> pipelineBuilder) where TLight : ILight
56+
internal IPipeline<LightTransition> CreateLightPipeline<TLight>(IServiceProvider compositeServiceProvider, TLight light, Action<ILightTransitionPipelineConfigurator<TLight>> pipelineBuilder) where TLight : ILight
5757
{
58-
return CreateLightPipelines(serviceProvider, [light], pipelineBuilder)[light.Id];
58+
return CreateLightPipelines(compositeServiceProvider, new Dictionary<TLight, IServiceProvider> { { light, compositeServiceProvider } }, pipelineBuilder)[light.Id];
5959
}
6060

6161
/// <summary>
@@ -65,15 +65,15 @@ internal IPipeline<LightTransition> CreateLightPipeline<TLight>(IServiceProvider
6565
/// </summary>
6666
/// <typeparam name="TLight">The type of light, constrained to <see cref="ILight"/>.</typeparam>
6767
/// <param name="pipelineConfigurator">The configuration action used to initialize the pipeline logic.</param>
68-
/// <param name="lights">The collection of lights for which pipelines will be created.</param>
68+
/// <param name="lightsAndProviders">Mapping to the lights to their respective service providers. This allows the factory to create context-specific scopes for each light.</param>
6969
/// <returns>
7070
/// A <see cref="Dictionary{TKey, TValue}"/> where the key is the light ID and the value is a
7171
/// function that resolves the corresponding <see cref="IPipelineNode{LightTransition}"/>.
7272
/// </returns>
73-
internal Dictionary<string, Func<IServiceProvider, IPipelineNode<LightTransition>>> CreateCompositePipelineFactoryMap<TLight>(Action<ILightTransitionPipelineConfigurator<TLight>> pipelineConfigurator, TLight[] lights) where TLight : ILight
73+
internal Dictionary<string, Func<IServiceProvider, IPipelineNode<LightTransition>>> CreateCompositePipelineFactoryMap<TLight>(Action<ILightTransitionPipelineConfigurator<TLight>> pipelineConfigurator, Dictionary<TLight, IServiceProvider> lightsAndProviders) where TLight : ILight
7474
{
75-
var baseFactory = new CompositePipelineFactory<TLight>(pipelineConfigurator, lights);
76-
return lights
75+
var baseFactory = new CompositePipelineFactory<TLight>(pipelineConfigurator, lightsAndProviders);
76+
return lightsAndProviders.Keys
7777
.ToDictionary(
7878
l => l.Id,
7979
l =>
@@ -86,20 +86,20 @@ internal Dictionary<string, Func<IServiceProvider, IPipelineNode<LightTransition
8686
/// <summary>
8787
/// Creates light pipelines for multiple light entities.
8888
/// </summary>
89-
/// <param name="serviceProvider">The service provider passed to the configurators. This method is used within the library to allow passing the composite service provider. This is necessary because the factory will only receive the root service provider even if resolved inside the context scope.</param>
90-
/// <param name="lights">The light entities to create pipelines for.</param>
89+
/// <param name="compositeServiceProvider">The service provider passed to the configurators. This method is used within the library to allow passing the composite service provider. This is necessary because the factory will only receive the root service provider even if resolved inside the context scope.</param>
90+
/// <param name="lightsAndProviders">Mapping to the lights to their respective service providers. This allows the factory to create context-specific scopes for each light.</param>
9191
/// <param name="pipelineBuilder">An action to configure the pipeline behavior.</param>
9292
/// <returns>A dictionary mapping light IDs to their corresponding pipelines.</returns>
93-
internal Dictionary<string, IPipeline<LightTransition>> CreateLightPipelines<TLight>(IServiceProvider serviceProvider, IEnumerable<TLight> lights, Action<ILightTransitionPipelineConfigurator<TLight>> pipelineBuilder) where TLight : ILight
93+
internal Dictionary<string, IPipeline<LightTransition>> CreateLightPipelines<TLight>(IServiceProvider compositeServiceProvider, Dictionary<TLight, IServiceProvider> lightsAndProviders, Action<ILightTransitionPipelineConfigurator<TLight>> pipelineBuilder) where TLight : ILight
9494
{
9595
// Note: we simply assume that these are not groups.
96-
var lightArray = lights.ToArray();
96+
var lightArray = lightsAndProviders.Keys.ToArray();
9797
if (!lightArray.Any())
9898
{
9999
return new Dictionary<string, IPipeline<LightTransition>>();
100100
}
101101

102-
var lightContextScopes = lightArray.ToDictionary(l => l.Id, serviceProvider.CreateLightPipelineContextScope);
102+
var lightContextScopes = lightsAndProviders.ToDictionary(kvp => kvp.Key.Id, kvp => kvp.Value.CreateLightPipelineContextScope(kvp.Key));
103103
var configurators =
104104
lightArray.ToDictionary(l => l.Id,
105105
l =>
@@ -111,9 +111,9 @@ internal Dictionary<string, IPipeline<LightTransition>> CreateLightPipelines<TLi
111111
ILightTransitionPipelineConfigurator<TLight> configurator = lightArray.Length == 1
112112
? configurators[lightArray[0].Id]
113113
: new CompositeLightTransitionPipelineConfigurator<TLight>(
114-
serviceProvider,
115-
serviceProvider.GetRequiredService<LightPipelineFactory>(),
116-
serviceProvider.GetRequiredService<ReactiveNodeFactory>(),
114+
compositeServiceProvider,
115+
compositeServiceProvider.GetRequiredService<LightPipelineFactory>(),
116+
compositeServiceProvider.GetRequiredService<ReactiveNodeFactory>(),
117117
configurators);
118118
pipelineBuilder(configurator);
119119

@@ -164,7 +164,7 @@ internal Dictionary<string, IPipeline<LightTransition>> CreateLightPipelines<TLi
164164
});
165165
}
166166

167-
private class CompositePipelineFactory<TLight>(Action<ILightTransitionPipelineConfigurator<TLight>> pipelineConfigurator, IEnumerable<TLight> lights) where TLight : ILight
167+
private class CompositePipelineFactory<TLight>(Action<ILightTransitionPipelineConfigurator<TLight>> pipelineConfigurator, Dictionary<TLight, IServiceProvider> lightsAndProviders) where TLight : ILight
168168
{
169169
private readonly Lock _lock = new();
170170
private Dictionary<string, IPipeline<LightTransition>>? _pipelines;
@@ -176,7 +176,7 @@ public IPipeline<LightTransition> GetOrCreatePipeline(IServiceProvider servicePr
176176
if (_pipelines == null)
177177
{
178178
var pipelineFactory = serviceProvider.GetRequiredService<LightPipelineFactory>();
179-
_pipelines = pipelineFactory.CreateLightPipelines(serviceProvider, lights, pipelineConfigurator);
179+
_pipelines = pipelineFactory.CreateLightPipelines(serviceProvider, lightsAndProviders, pipelineConfigurator);
180180
}
181181

182182
return _pipelines[lightId];

src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public ILightTransitionPipelineConfigurator<TLight> Switch(IObservable<bool> obs
7575
/// <inheritdoc/>
7676
public ILightTransitionPipelineConfigurator<TLight> Switch<TObservable>(Func<IServiceProvider, IPipelineNode<LightTransition>> trueNodeFactory, Func<IServiceProvider, IPipelineNode<LightTransition>> falseNodeFactory) where TObservable : IObservable<bool>
7777
{
78-
var observable = ActivatorUtilities.CreateInstance<TObservable>(_serviceProvider);
78+
var observable = ActivatorUtilities.CreateInstance<TObservable>(ServiceProvider);
7979
return Switch(observable, trueNodeFactory, falseNodeFactory);
8080
}
8181

@@ -92,7 +92,7 @@ public ILightTransitionPipelineConfigurator<TLight> Switch(IObservable<bool> obs
9292
/// <inheritdoc/>
9393
public ILightTransitionPipelineConfigurator<TLight> Switch<TObservable, TTrueNode, TFalseNode>() where TObservable : IObservable<bool> where TTrueNode : IPipelineNode<LightTransition> where TFalseNode : IPipelineNode<LightTransition>
9494
{
95-
var observable = ActivatorUtilities.CreateInstance<TObservable>(_serviceProvider);
95+
var observable = ActivatorUtilities.CreateInstance<TObservable>(ServiceProvider);
9696
return Switch<TTrueNode, TFalseNode>(observable);
9797
}
9898

@@ -108,7 +108,7 @@ public ILightTransitionPipelineConfigurator<TLight> Switch<TTrueNode, TFalseNode
108108
/// <inheritdoc/>
109109
public ILightTransitionPipelineConfigurator<TLight> AddReactiveNodeSwitch<TObservable>(Action<ILightTransitionReactiveNodeConfigurator<TLight>> trueConfigure, Action<ILightTransitionReactiveNodeConfigurator<TLight>> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable<bool>
110110
{
111-
var observable = ActivatorUtilities.CreateInstance<TObservable>(_serviceProvider);
111+
var observable = ActivatorUtilities.CreateInstance<TObservable>(ServiceProvider);
112112
return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure, instantiationScope);
113113
}
114114

@@ -125,7 +125,7 @@ public ILightTransitionPipelineConfigurator<TLight> AddReactiveNodeSwitch(IObser
125125
/// <inheritdoc/>
126126
public ILightTransitionPipelineConfigurator<TLight> AddPipelineSwitch<TObservable>(Action<ILightTransitionPipelineConfigurator<TLight>> trueConfigure, Action<ILightTransitionPipelineConfigurator<TLight>> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable<bool>
127127
{
128-
var observable = ActivatorUtilities.CreateInstance<TObservable>(_serviceProvider);
128+
var observable = ActivatorUtilities.CreateInstance<TObservable>(ServiceProvider);
129129
return AddPipelineSwitch(observable, trueConfigure, falseConfigure, instantiationScope);
130130
}
131131

@@ -143,7 +143,7 @@ public ILightTransitionPipelineConfigurator<TLight> AddPipelineSwitch(IObservabl
143143
public ILightTransitionPipelineConfigurator<TLight> Switch<TObservable>(Action<ILightTransitionSwitchConfigurator<TLight>> configure)
144144
where TObservable : IObservable<bool>
145145
{
146-
var observable = ActivatorUtilities.CreateInstance<TObservable>(_serviceProvider);
146+
var observable = ActivatorUtilities.CreateInstance<TObservable>(ServiceProvider);
147147
return Switch(observable, configure);
148148
}
149149

0 commit comments

Comments
 (0)