From 6818be887e8d406bee95910c46f7e058763d06ca Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 00:20:50 +0100 Subject: [PATCH 01/18] Add operator to get date-time from Harp timestamps --- src/Aeon.Acquisition/GetDateTime.cs | 30 +++++++++++++++++++++++++++++ src/Aeon.Acquisition/GroupByTime.cs | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/Aeon.Acquisition/GetDateTime.cs diff --git a/src/Aeon.Acquisition/GetDateTime.cs b/src/Aeon.Acquisition/GetDateTime.cs new file mode 100644 index 0000000..76a73dc --- /dev/null +++ b/src/Aeon.Acquisition/GetDateTime.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; +using Bonsai.Harp; + +namespace Aeon.Acquisition +{ + [Combinator] + [WorkflowElementCategory(ElementCategory.Transform)] + [Description("Converts a sequence of referenced Harp timestamps into system date-time objects.")] + public class GetDateTime + { + static DateTime FromSeconds(double seconds) + { + return GroupByTime.ReferenceTime.AddSeconds(seconds); + } + + public IObservable Process(IObservable source) + { + return source.Select(message => FromSeconds(message.GetTimestamp())); + } + + public IObservable Process(IObservable> source) + { + return source.Select(_ => FromSeconds(_.Seconds)); + } + } +} diff --git a/src/Aeon.Acquisition/GroupByTime.cs b/src/Aeon.Acquisition/GroupByTime.cs index 307db7e..5ba2b44 100644 --- a/src/Aeon.Acquisition/GroupByTime.cs +++ b/src/Aeon.Acquisition/GroupByTime.cs @@ -20,7 +20,7 @@ public GroupByTime() } // The default real-time reference is unix time in total seconds from 1904 - static readonly DateTime ReferenceTime = new DateTime(1904, 1, 1); + internal static readonly DateTime ReferenceTime = new(1904, 1, 1); [Description("The size of each chunk, in whole hours.")] public int ChunkSize { get; set; } From 8a8ca6b57253b6829c4cb17899d4bdda14df3a4a Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 00:26:00 +0100 Subject: [PATCH 02/18] Add simple file-based light cycle model --- src/Aeon.Environment/LightCycle.bonsai | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/Aeon.Environment/LightCycle.bonsai diff --git a/src/Aeon.Environment/LightCycle.bonsai b/src/Aeon.Environment/LightCycle.bonsai new file mode 100644 index 0000000..8468704 --- /dev/null +++ b/src/Aeon.Environment/LightCycle.bonsai @@ -0,0 +1,57 @@ + + + Implements a simple light cycle model where light levels are sampled using the current time of day. + + + + + + + lightcycle.config + %i,%i,%i,%i + 1 + + + new( +Item1 as Minute, +Item2 as Red, +Item3 as ColdWhite, +Item4 as WarmWhite) + + + Minute + + + SynchronizerEvents + + + + + + Hour * 60 + Minute + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 05eabae08844d3a44d66015989ebfc320fb9a360 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 01:17:32 +0100 Subject: [PATCH 03/18] Add object model for room light presets --- .../CreateRoomLightMessage.cs | 5 +- src/Aeon.Environment/CreateRoomLightPreset.cs | 59 +++++++++++++++++++ src/Aeon.Environment/RoomLightMessage.cs | 3 + src/Aeon.Environment/RoomLightPreset.cs | 23 ++++++++ 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/Aeon.Environment/CreateRoomLightPreset.cs create mode 100644 src/Aeon.Environment/RoomLightPreset.cs diff --git a/src/Aeon.Environment/CreateRoomLightMessage.cs b/src/Aeon.Environment/CreateRoomLightMessage.cs index 09a8ddf..1ad6e7e 100644 --- a/src/Aeon.Environment/CreateRoomLightMessage.cs +++ b/src/Aeon.Environment/CreateRoomLightMessage.cs @@ -8,13 +8,10 @@ namespace Aeon.Environment [Description("Creates a room light controller message for the specified light.")] public class CreateRoomLightMessage : Source { - const int NoChange = -1; - const int MaxLightValue = 254; - [Description("The unique ID of the channel for this light.")] public int Channel { get; set; } - [Range(NoChange, MaxLightValue)] + [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] [Description("The intensity to set on the channel for the specified light.")] public int Value { get; set; } diff --git a/src/Aeon.Environment/CreateRoomLightPreset.cs b/src/Aeon.Environment/CreateRoomLightPreset.cs new file mode 100644 index 0000000..dc618e0 --- /dev/null +++ b/src/Aeon.Environment/CreateRoomLightPreset.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using System.Reactive.Linq; +using Bonsai; + +namespace Aeon.Environment +{ + [TypeConverter(typeof(SettingsConverter))] + [Description("Creates a light controller preset for the specified room.")] + public class CreateRoomLightPreset : Source + { + [Description("The unique ID of the channel map on which to apply this preset.")] + public string Name { get; set; } + + [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Description("The intensity to set on the cold-white channels.")] + public int ColdWhite { get; set; } + + [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Description("The intensity to set on the warm-white channels.")] + public int WarmWhite { get; set; } + + [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Description("The intensity to set on the red light channels.")] + public int Red { get; set; } + + public override IObservable Generate() + { + return Observable.Return(new RoomLightPreset(Name, ColdWhite, WarmWhite, Red)); + } + + public IObservable Generate(IObservable source) + { + return source.Select(value => new RoomLightPreset(Name, ColdWhite, WarmWhite, Red)); + } + + class SettingsConverter : ExpandableObjectConverter + { + public override PropertyDescriptorCollection GetProperties( + ITypeDescriptorContext context, + object value, + Attribute[] attributes) + { + return base + .GetProperties(context, value, attributes) + .Sort(new[] + { + nameof(Name), + nameof(ColdWhite), + nameof(WarmWhite), + nameof(Red) + }); + } + } + } +} diff --git a/src/Aeon.Environment/RoomLightMessage.cs b/src/Aeon.Environment/RoomLightMessage.cs index 381be20..989a9c0 100644 --- a/src/Aeon.Environment/RoomLightMessage.cs +++ b/src/Aeon.Environment/RoomLightMessage.cs @@ -2,6 +2,9 @@ { public struct RoomLightMessage { + internal const int NoChange = -1; + internal const int MaxLightValue = 254; + public int Channel; public int Value; diff --git a/src/Aeon.Environment/RoomLightPreset.cs b/src/Aeon.Environment/RoomLightPreset.cs new file mode 100644 index 0000000..424bdd8 --- /dev/null +++ b/src/Aeon.Environment/RoomLightPreset.cs @@ -0,0 +1,23 @@ +namespace Aeon.Environment +{ + public struct RoomLightPreset + { + public string Name; + public int ColdWhite; + public int WarmWhite; + public int Red; + + public RoomLightPreset(string name, int coldWhite, int warmWhite, int red) + { + Name = name; + ColdWhite = coldWhite; + WarmWhite = warmWhite; + Red = red; + } + + public override readonly string ToString() + { + return $"RoomLightPreset({Name}, {ColdWhite}, {WarmWhite}, {Red})"; + } + } +} From 0435eaca1b8a11a2e776e221b8654f881a2271f9 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 01:20:21 +0100 Subject: [PATCH 04/18] Update light controller to work on presets --- src/Aeon.Environment/LightController.bonsai | 34 ++++++--------------- src/Aeon.Environment/LightCycle.bonsai | 23 +++++++++++++- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Aeon.Environment/LightController.bonsai b/src/Aeon.Environment/LightController.bonsai index f8d8332..e8142a9 100644 --- a/src/Aeon.Environment/LightController.bonsai +++ b/src/Aeon.Environment/LightController.bonsai @@ -6,7 +6,7 @@ xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" - xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:aeon-env="clr-namespace:Aeon.Environment;assembly=Aeon.Environment" xmlns="https://bonsai-rx.org/2018/workflow"> Provides control and acquisition functionality for automated room lighting. @@ -48,26 +48,14 @@ Item2 as Value) LightEvents - - SetRedLightLevel - - - /red - - - SetColdWhiteLightLevel - - - /cold + + - - SetWarmWhiteLightLevel + + LightCommands - /warm - - - + /preset Buffer.Array @@ -94,14 +82,10 @@ Item2 as Value) - + - - - - - - + + \ No newline at end of file diff --git a/src/Aeon.Environment/LightCycle.bonsai b/src/Aeon.Environment/LightCycle.bonsai index 8468704..19867af 100644 --- a/src/Aeon.Environment/LightCycle.bonsai +++ b/src/Aeon.Environment/LightCycle.bonsai @@ -5,12 +5,13 @@ xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:aeon="clr-namespace:Aeon.Acquisition;assembly=Aeon.Acquisition" + xmlns:aeon-env="clr-namespace:Aeon.Environment;assembly=Aeon.Environment" xmlns="https://bonsai-rx.org/2018/workflow"> Implements a simple light cycle model where light levels are sampled using the current time of day. - + lightcycle.config @@ -40,6 +41,23 @@ Item4 as WarmWhite) + + + + + + + + + + + + + 0 + 0 + 0 + + @@ -52,6 +70,9 @@ Item4 as WarmWhite) + + + \ No newline at end of file From 8c9717e023e12377f62a7284a6fd0c7a59f07173 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 02:00:06 +0100 Subject: [PATCH 05/18] Use only defined object model in light controller --- src/Aeon.Environment/LightController.bonsai | 37 ++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Aeon.Environment/LightController.bonsai b/src/Aeon.Environment/LightController.bonsai index e8142a9..774b341 100644 --- a/src/Aeon.Environment/LightController.bonsai +++ b/src/Aeon.Environment/LightController.bonsai @@ -3,10 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:zmq="clr-namespace:Bonsai.ZeroMQ;assembly=Bonsai.ZeroMQ" xmlns:osc="clr-namespace:Bonsai.Osc;assembly=Bonsai.Osc" - xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" + xmlns:aeon-env="clr-namespace:Aeon.Environment;assembly=Aeon.Environment" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" - xmlns:aeon-env="clr-namespace:Aeon.Environment;assembly=Aeon.Environment" xmlns="https://bonsai-rx.org/2018/workflow"> Provides control and acquisition functionality for automated room lighting. @@ -27,10 +26,17 @@ /channel ii - - new( -Item1 as Channel, -Item2 as Value) + + + + + + + + + 0 + 0 + SynchronizerEvents @@ -75,17 +81,18 @@ Item2 as Value) - - - - - - - + + + + + + + - - + + + \ No newline at end of file From 63d1c71582cc9d2bc69ea9833fabc1d3fecd3a0c Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 02:01:23 +0100 Subject: [PATCH 06/18] Add simple implementation of a light server --- src/Aeon.Environment/LightServer.bonsai | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/Aeon.Environment/LightServer.bonsai diff --git a/src/Aeon.Environment/LightServer.bonsai b/src/Aeon.Environment/LightServer.bonsai new file mode 100644 index 0000000..a2618b1 --- /dev/null +++ b/src/Aeon.Environment/LightServer.bonsai @@ -0,0 +1,97 @@ + + + Implements a router of light preset commands and room light message responses over the local network. + + + + + + + LightMessages + + + + + + + + PT0.1S + + + + /channel + + + Buffer.Array + + + + + + + @tcp://*:4303 + LightEvents + + + + + + + + @tcp://*:4304 + LightCommands + + + + Last.Buffer + + + /preset + siii + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + LightPresets + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 30486b4afccde16e91baabf69725f5d0b55df64f Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 29 Sep 2023 13:20:44 +0100 Subject: [PATCH 07/18] Add support for standard units in light presets --- src/Aeon.Environment/CreateRoomLightPreset.cs | 14 +++++++------- src/Aeon.Environment/LightServer.bonsai | 2 +- src/Aeon.Environment/RoomLightPreset.cs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Aeon.Environment/CreateRoomLightPreset.cs b/src/Aeon.Environment/CreateRoomLightPreset.cs index dc618e0..3bc83ee 100644 --- a/src/Aeon.Environment/CreateRoomLightPreset.cs +++ b/src/Aeon.Environment/CreateRoomLightPreset.cs @@ -6,7 +6,7 @@ namespace Aeon.Environment { [TypeConverter(typeof(SettingsConverter))] - [Description("Creates a light controller preset for the specified room.")] + [Description("Creates a light controller preset for the specified channel map.")] public class CreateRoomLightPreset : Source { [Description("The unique ID of the channel map on which to apply this preset.")] @@ -14,18 +14,18 @@ public class CreateRoomLightPreset : Source [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Description("The intensity to set on the cold-white channels.")] - public int ColdWhite { get; set; } + [Description("The normalized light level to set on the cold-white channels.")] + public float ColdWhite { get; set; } [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Description("The intensity to set on the warm-white channels.")] - public int WarmWhite { get; set; } + [Description("The normalized light level to set on the warm-white channels.")] + public float WarmWhite { get; set; } [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Description("The intensity to set on the red light channels.")] - public int Red { get; set; } + [Description("The normalized light level to set on the red light channels.")] + public float Red { get; set; } public override IObservable Generate() { diff --git a/src/Aeon.Environment/LightServer.bonsai b/src/Aeon.Environment/LightServer.bonsai index a2618b1..1b36343 100644 --- a/src/Aeon.Environment/LightServer.bonsai +++ b/src/Aeon.Environment/LightServer.bonsai @@ -53,7 +53,7 @@ /preset - siii + sfff diff --git a/src/Aeon.Environment/RoomLightPreset.cs b/src/Aeon.Environment/RoomLightPreset.cs index 424bdd8..a70cf86 100644 --- a/src/Aeon.Environment/RoomLightPreset.cs +++ b/src/Aeon.Environment/RoomLightPreset.cs @@ -3,11 +3,11 @@ public struct RoomLightPreset { public string Name; - public int ColdWhite; - public int WarmWhite; - public int Red; + public float ColdWhite; + public float WarmWhite; + public float Red; - public RoomLightPreset(string name, int coldWhite, int warmWhite, int red) + public RoomLightPreset(string name, float coldWhite, float warmWhite, float red) { Name = name; ColdWhite = coldWhite; From e2cdeb9d8c4d60d45e7a4542be89439eba78ee7d Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 2 Oct 2023 22:36:28 +0100 Subject: [PATCH 08/18] Group individual preset requests using topics --- src/Aeon.Environment/CreateRoomLightPreset.cs | 10 +- src/Aeon.Environment/LightController.bonsai | 31 ++-- src/Aeon.Environment/LightServer.bonsai | 173 +++++++++++++++--- src/Aeon.Environment/RoomLightPreset.cs | 6 +- 4 files changed, 175 insertions(+), 45 deletions(-) diff --git a/src/Aeon.Environment/CreateRoomLightPreset.cs b/src/Aeon.Environment/CreateRoomLightPreset.cs index 3bc83ee..e95d395 100644 --- a/src/Aeon.Environment/CreateRoomLightPreset.cs +++ b/src/Aeon.Environment/CreateRoomLightPreset.cs @@ -6,12 +6,9 @@ namespace Aeon.Environment { [TypeConverter(typeof(SettingsConverter))] - [Description("Creates a light controller preset for the specified channel map.")] + [Description("Creates a light controller preset.")] public class CreateRoomLightPreset : Source { - [Description("The unique ID of the channel map on which to apply this preset.")] - public string Name { get; set; } - [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] [Description("The normalized light level to set on the cold-white channels.")] @@ -29,12 +26,12 @@ public class CreateRoomLightPreset : Source public override IObservable Generate() { - return Observable.Return(new RoomLightPreset(Name, ColdWhite, WarmWhite, Red)); + return Observable.Return(new RoomLightPreset(ColdWhite, WarmWhite, Red)); } public IObservable Generate(IObservable source) { - return source.Select(value => new RoomLightPreset(Name, ColdWhite, WarmWhite, Red)); + return source.Select(value => new RoomLightPreset(ColdWhite, WarmWhite, Red)); } class SettingsConverter : ExpandableObjectConverter @@ -48,7 +45,6 @@ public override PropertyDescriptorCollection GetProperties( .GetProperties(context, value, attributes) .Sort(new[] { - nameof(Name), nameof(ColdWhite), nameof(WarmWhite), nameof(Red) diff --git a/src/Aeon.Environment/LightController.bonsai b/src/Aeon.Environment/LightController.bonsai index 774b341..7ffb80f 100644 --- a/src/Aeon.Environment/LightController.bonsai +++ b/src/Aeon.Environment/LightController.bonsai @@ -13,10 +13,13 @@ + + + >tcp://localhost:4303 - LightEvents + @@ -72,27 +75,29 @@ >tcp://localhost:4304 - LightCommands + - - + + + - - - - - - - + + + + + + + - - + + + \ No newline at end of file diff --git a/src/Aeon.Environment/LightServer.bonsai b/src/Aeon.Environment/LightServer.bonsai index 1b36343..7e2c8b4 100644 --- a/src/Aeon.Environment/LightServer.bonsai +++ b/src/Aeon.Environment/LightServer.bonsai @@ -2,6 +2,8 @@ - + LightMessages + + FlattenMessages + + + + Source1 + + + LightGroup + + + LightGroup + + + Key + + + LightGroup + + + + + + + + + + + + + + + + + + + - - - PT0.1S - - - - /channel - - - Buffer.Array + + RoomLightDevice + + + + Source1 + + + Item2 + + + + + + + + PT0.1S + + + + /channel + + + Buffer.Array + + + Item1 + + + + + + + + + + + + + + + + + + + + + PackResponses + + + + Source1 + + + Item2 + + + + + + Item1 + + + + + + + + + + + + + + + + + + + + + + + @@ -36,7 +149,7 @@ @tcp://*:4303 - LightEvents + @@ -45,9 +158,15 @@ @tcp://*:4304 - LightCommands + + + First + + + + Last.Buffer @@ -57,7 +176,6 @@ - @@ -70,28 +188,41 @@ 0 + + + + + Item1 + Item2 + - + LightPresets + - - - + + + + - + - - + + + + + + \ No newline at end of file diff --git a/src/Aeon.Environment/RoomLightPreset.cs b/src/Aeon.Environment/RoomLightPreset.cs index a70cf86..e919c18 100644 --- a/src/Aeon.Environment/RoomLightPreset.cs +++ b/src/Aeon.Environment/RoomLightPreset.cs @@ -2,14 +2,12 @@ { public struct RoomLightPreset { - public string Name; public float ColdWhite; public float WarmWhite; public float Red; - public RoomLightPreset(string name, float coldWhite, float warmWhite, float red) + public RoomLightPreset(float coldWhite, float warmWhite, float red) { - Name = name; ColdWhite = coldWhite; WarmWhite = warmWhite; Red = red; @@ -17,7 +15,7 @@ public RoomLightPreset(string name, float coldWhite, float warmWhite, float red) public override readonly string ToString() { - return $"RoomLightPreset({Name}, {ColdWhite}, {WarmWhite}, {Red})"; + return $"RoomLightPreset({ColdWhite}, {WarmWhite}, {Red})"; } } } From 59a8b9ebdaaefe7fd84873d57c4041529840480f Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 2 Oct 2023 23:10:02 +0100 Subject: [PATCH 09/18] Avoid tying server to a specific light controller --- src/Aeon.Environment/LightServer.bonsai | 226 ++++++++++-------------- 1 file changed, 98 insertions(+), 128 deletions(-) diff --git a/src/Aeon.Environment/LightServer.bonsai b/src/Aeon.Environment/LightServer.bonsai index 7e2c8b4..66a34e2 100644 --- a/src/Aeon.Environment/LightServer.bonsai +++ b/src/Aeon.Environment/LightServer.bonsai @@ -5,8 +5,8 @@ xmlns:p1="clr-namespace:System.Reactive.Linq;assembly=System.Reactive.Interfaces" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:aeon-env="clr-namespace:Aeon.Environment;assembly=Aeon.Environment" - xmlns:osc="clr-namespace:Bonsai.Osc;assembly=Bonsai.Osc" xmlns:zmq="clr-namespace:Bonsai.ZeroMQ;assembly=Bonsai.ZeroMQ" + xmlns:osc="clr-namespace:Bonsai.Osc;assembly=Bonsai.Osc" xmlns="https://bonsai-rx.org/2018/workflow"> Implements a router of light preset commands and room light message responses over the local network. @@ -18,7 +18,7 @@ LightMessages - FlattenMessages + PackResponses @@ -33,101 +33,132 @@ Key + + + LightGroup - - - - - - - - - - - - - - - - - - - - - RoomLightDevice - - - - Source1 - - - Item2 - - - - - - - - PT0.1S - - /channel Buffer.Array - - Item1 + + - + + + + + + + Source1 + + + Item1 + + + Item2 + + + + + + + + + + + + + + + + + + - - - + - - - + + + + + + + - - PackResponses + + + + + + @tcp://*:4303 + + + + + + + + + @tcp://*:4304 + + + + + GroupRequests Source1 - Item2 + First - + - Item1 + Last.Buffer - - + + /preset + fff + + + + + + + - + + 0 + 0 + 0 + - + + + + Item1 + Item2 @@ -135,66 +166,16 @@ - + - + - + + + - - - - - - @tcp://*:4303 - - - - - - - - - @tcp://*:4304 - - - - - First - - - - - - Last.Buffer - - - /preset - sfff - - - - - - - - - - - 0 - 0 - 0 - - - - - - - Item1 - Item2 - @@ -208,21 +189,10 @@ - - - - - - - - - - - - - - - + + + + \ No newline at end of file From 19a483c01a5242f7d9bd210c51c963ccdcddac51 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 2 Oct 2023 23:15:41 +0100 Subject: [PATCH 10/18] Remove fixed range from standard unit preset class --- src/Aeon.Environment/CreateRoomLightMessage.cs | 5 ++++- src/Aeon.Environment/CreateRoomLightPreset.cs | 6 ------ src/Aeon.Environment/RoomLightMessage.cs | 3 --- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Aeon.Environment/CreateRoomLightMessage.cs b/src/Aeon.Environment/CreateRoomLightMessage.cs index 1ad6e7e..09a8ddf 100644 --- a/src/Aeon.Environment/CreateRoomLightMessage.cs +++ b/src/Aeon.Environment/CreateRoomLightMessage.cs @@ -8,10 +8,13 @@ namespace Aeon.Environment [Description("Creates a room light controller message for the specified light.")] public class CreateRoomLightMessage : Source { + const int NoChange = -1; + const int MaxLightValue = 254; + [Description("The unique ID of the channel for this light.")] public int Channel { get; set; } - [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] + [Range(NoChange, MaxLightValue)] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] [Description("The intensity to set on the channel for the specified light.")] public int Value { get; set; } diff --git a/src/Aeon.Environment/CreateRoomLightPreset.cs b/src/Aeon.Environment/CreateRoomLightPreset.cs index e95d395..337ec13 100644 --- a/src/Aeon.Environment/CreateRoomLightPreset.cs +++ b/src/Aeon.Environment/CreateRoomLightPreset.cs @@ -9,18 +9,12 @@ namespace Aeon.Environment [Description("Creates a light controller preset.")] public class CreateRoomLightPreset : Source { - [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] - [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] [Description("The normalized light level to set on the cold-white channels.")] public float ColdWhite { get; set; } - [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] - [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] [Description("The normalized light level to set on the warm-white channels.")] public float WarmWhite { get; set; } - [Range(RoomLightMessage.NoChange, RoomLightMessage.MaxLightValue)] - [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] [Description("The normalized light level to set on the red light channels.")] public float Red { get; set; } diff --git a/src/Aeon.Environment/RoomLightMessage.cs b/src/Aeon.Environment/RoomLightMessage.cs index 989a9c0..381be20 100644 --- a/src/Aeon.Environment/RoomLightMessage.cs +++ b/src/Aeon.Environment/RoomLightMessage.cs @@ -2,9 +2,6 @@ { public struct RoomLightMessage { - internal const int NoChange = -1; - internal const int MaxLightValue = 254; - public int Channel; public int Value; From e037eacb9f662ec19d8e53099f44bdf3de673799 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 10 Oct 2023 13:30:44 +0100 Subject: [PATCH 11/18] Add schema for room light channel map data classes --- .config/dotnet-tools.json | 12 + README.md | 16 + .../Aeon.Environment.Generated.cs | 295 ++++++++++++++++++ src/Aeon.Environment/ChannelMap.json | 49 +++ 4 files changed, 372 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 src/Aeon.Environment/Aeon.Environment.Generated.cs create mode 100644 src/Aeon.Environment/ChannelMap.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..b98e20a --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "bonsai.sgen": { + "version": "0.1.0", + "commands": [ + "bonsai.sgen" + ] + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index a379043..78f159f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,22 @@ The Project Aeon acquisition repository contains the set of standardized data acquisition systems, protocols, operation instructions and metadata necessary for reproducible task control and acquisition on the foraging arena assay. The scripts contained in this repository should always represent as accurately as possible the automation routines and operational instructions used to log the experimental raw data for Project Aeon. Each acquired dataset should have a reference to the specific hash or release from this repository which was used in the experiment. +## Build Instructions + +### Schema classes + +1. Install [`bonsai.sgen`](https://www.nuget.org/packages/Bonsai.Sgen) by running the `restore` command: + + ``` + dotnet tool restore + ``` + +2. Run the command to regenerate each schema class, e.g. for `ChannelMap.json`: + + ``` + dotnet bonsai.sgen --namespace Aeon.Environment --schema ChannelMap.json + ``` + ## Deployment Instructions The Project Aeon acquisition framework runs on the [Bonsai](https://bonsai-rx.org/) visual programming language. This repository includes installation scripts which will automatically download and configure a reproducible, self-contained, Bonsai environment to run all acquisition systems on the foraging arena. It is necessary, however, to install a few system dependencies and device drivers which need to be installed separately, before runnning the environment configuration script. diff --git a/src/Aeon.Environment/Aeon.Environment.Generated.cs b/src/Aeon.Environment/Aeon.Environment.Generated.cs new file mode 100644 index 0000000..4ac5715 --- /dev/null +++ b/src/Aeon.Environment/Aeon.Environment.Generated.cs @@ -0,0 +1,295 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.9.0.0 (Newtonsoft.Json v9.0.0.0) (http://NJsonSchema.org) +// +//---------------------- + + +namespace Aeon.Environment +{ + #pragma warning disable // Disable all warnings + + /// + /// Specifies the channel map for every light fixture in the room. + /// + [System.ComponentModel.DescriptionAttribute("Specifies the channel map for every light fixture in the room.")] + [Bonsai.CombinatorAttribute()] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + public partial class RoomFixtures + { + + private Fixture _coldWhite; + + private Fixture _warmWhite; + + private Fixture _red; + + [System.Xml.Serialization.XmlIgnoreAttribute()] + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="coldWhite")] + public Fixture ColdWhite + { + get + { + return _coldWhite; + } + set + { + _coldWhite = value; + } + } + + [System.Xml.Serialization.XmlIgnoreAttribute()] + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="warmWhite")] + public Fixture WarmWhite + { + get + { + return _warmWhite; + } + set + { + _warmWhite = value; + } + } + + [System.Xml.Serialization.XmlIgnoreAttribute()] + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="red")] + public Fixture Red + { + get + { + return _red; + } + set + { + _red = value; + } + } + + public System.IObservable Process() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return( + new RoomFixtures + { + ColdWhite = _coldWhite, + WarmWhite = _warmWhite, + Red = _red + })); + } + } + + + [Bonsai.CombinatorAttribute()] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + public partial class Fixture + { + + private System.Collections.Generic.List _channels = new System.Collections.Generic.List(); + + private InterpolationMethod _interpolationMethod; + + private string _calibrationFile; + + /// + /// Specifies the collection of channels assigned to the fixture. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="channels")] + [System.ComponentModel.DescriptionAttribute("Specifies the collection of channels assigned to the fixture.")] + public System.Collections.Generic.List Channels + { + get + { + return _channels; + } + set + { + _channels = value; + } + } + + /// + /// Specifies the method used to interpolate light values for a fixture. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="interpolationMethod")] + [System.ComponentModel.DescriptionAttribute("Specifies the method used to interpolate light values for a fixture.")] + public InterpolationMethod InterpolationMethod + { + get + { + return _interpolationMethod; + } + set + { + _interpolationMethod = value; + } + } + + /// + /// Specifies the path to the calibration file for this fixture. + /// + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="calibrationFile")] + [System.ComponentModel.DescriptionAttribute("Specifies the path to the calibration file for this fixture.")] + public string CalibrationFile + { + get + { + return _calibrationFile; + } + set + { + _calibrationFile = value; + } + } + + public System.IObservable Process() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return( + new Fixture + { + Channels = _channels, + InterpolationMethod = _interpolationMethod, + CalibrationFile = _calibrationFile + })); + } + } + + + /// + /// Specifies the method used to interpolate light values for a fixture. + /// + public enum InterpolationMethod + { + + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="None")] + None = 0, + + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="Zero")] + Zero = 1, + + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="Linear")] + Linear = 2, + } + + + /// + /// Represents channel map configuration used by the light controller. + /// + [System.ComponentModel.DescriptionAttribute("Represents channel map configuration used by the light controller.")] + [Bonsai.CombinatorAttribute()] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + public partial class ChannelMap + { + + private System.Collections.Generic.IDictionary _rooms; + + /// + /// Specifies the collection of light channel maps for all rooms. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [YamlDotNet.Serialization.YamlMemberAttribute(Alias="rooms")] + [System.ComponentModel.DescriptionAttribute("Specifies the collection of light channel maps for all rooms.")] + public System.Collections.Generic.IDictionary Rooms + { + get + { + return _rooms; + } + set + { + _rooms = value; + } + } + + public System.IObservable Process() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return( + new ChannelMap + { + Rooms = _rooms + })); + } + } + + + /// + /// Serializes a sequence of data model objects into YAML strings. + /// + [Bonsai.CombinatorAttribute()] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Transform)] + [System.ComponentModel.DescriptionAttribute("Serializes a sequence of data model objects into YAML strings.")] + public partial class SerializeToYaml + { + + private System.IObservable Process(System.IObservable source) + { + return System.Reactive.Linq.Observable.Defer(() => + { + var serializer = new YamlDotNet.Serialization.SerializerBuilder().Build(); + return System.Reactive.Linq.Observable.Select(source, value => serializer.Serialize(value)); + }); + } + + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + } + + + /// + /// Deserializes a sequence of YAML strings into data model objects. + /// + [System.ComponentModel.DefaultPropertyAttribute("Type")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Transform)] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + [System.ComponentModel.DescriptionAttribute("Deserializes a sequence of YAML strings into data model objects.")] + public partial class DeserializeFromYaml : Bonsai.Expressions.SingleArgumentExpressionBuilder + { + + public DeserializeFromYaml() + { + Type = new Bonsai.Expressions.TypeMapping(); + } + + public Bonsai.Expressions.TypeMapping Type { get; set; } + + public override System.Linq.Expressions.Expression Build(System.Collections.Generic.IEnumerable arguments) + { + var typeMapping = (Bonsai.Expressions.TypeMapping)Type; + var returnType = typeMapping.GetType().GetGenericArguments()[0]; + return System.Linq.Expressions.Expression.Call( + typeof(DeserializeFromYaml), + "Process", + new System.Type[] { returnType }, + System.Linq.Enumerable.Single(arguments)); + } + + private static System.IObservable Process(System.IObservable source) + { + return System.Reactive.Linq.Observable.Defer(() => + { + var serializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + return System.Reactive.Linq.Observable.Select(source, value => + { + var reader = new System.IO.StringReader(value); + var parser = new YamlDotNet.Core.MergingParser(new YamlDotNet.Core.Parser(reader)); + return serializer.Deserialize(parser); + }); + }); + } + } +} \ No newline at end of file diff --git a/src/Aeon.Environment/ChannelMap.json b/src/Aeon.Environment/ChannelMap.json new file mode 100644 index 0000000..9a225e5 --- /dev/null +++ b/src/Aeon.Environment/ChannelMap.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://sainsburywellcome.org/aeon/2023-10/channelmap", + "description": "Represents channel map configuration used by the light controller.", + "title": "ChannelMap", + "type": "object", + "properties": { + "rooms": { + "type": "object", + "description": "Specifies the collection of light channel maps for all rooms.", + "additionalProperties": { "$ref": "#/definitions/roomFixtures" } + } + }, + "definitions": { + "roomFixtures": { + "type": "object", + "description": "Specifies the channel map for every light fixture in the room.", + "properties": { + "coldWhite": { "$ref": "#/definitions/fixture" }, + "warmWhite": { "$ref": "#/definitions/fixture" }, + "red": { "$ref": "#/definitions/fixture" } + } + }, + "fixture": { + "type": "object", + "properties": { + "channels": { + "type": "array", + "description": "Specifies the collection of channels assigned to the fixture.", + "items": { "type": "integer" } + }, + "interpolationMethod": { + "description": "Specifies the method used to interpolate light values for a fixture.", + "$ref": "#/definitions/interpolationMethod" + }, + "calibrationFile": { + "type": "string", + "description": "Specifies the path to the calibration file for this fixture." + } + }, + "required": [ "channels", "interpolationMethod" ] + }, + "interpolationMethod": { + "type": "string", + "description": "Specifies the method used to interpolate light values for a fixture.", + "enum": ["None", "Zero", "Linear"] + } + } +} \ No newline at end of file From f4bab1fc23c17ddea5566b3a6dfee43384f42f82 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 10 Oct 2023 13:31:06 +0100 Subject: [PATCH 12/18] Add interpolation operator for room light presets --- src/Aeon.Acquisition/Aeon.Acquisition.csproj | 2 +- .../InterpolateRoomLightPreset.cs | 118 ++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/Aeon.Environment/InterpolateRoomLightPreset.cs diff --git a/src/Aeon.Acquisition/Aeon.Acquisition.csproj b/src/Aeon.Acquisition/Aeon.Acquisition.csproj index 7fad82f..44a523b 100644 --- a/src/Aeon.Acquisition/Aeon.Acquisition.csproj +++ b/src/Aeon.Acquisition/Aeon.Acquisition.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/Aeon.Environment/InterpolateRoomLightPreset.cs b/src/Aeon.Environment/InterpolateRoomLightPreset.cs new file mode 100644 index 0000000..39d98a6 --- /dev/null +++ b/src/Aeon.Environment/InterpolateRoomLightPreset.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Reactive; +using System.Reactive.Linq; +using System.Xml.Serialization; +using Bonsai; +using MathNet.Numerics; +using MathNet.Numerics.Interpolation; + +namespace Aeon.Environment +{ + [Description("Maps room light presets to a sequence of channel-value messages to the room light controller.")] + public class InterpolateRoomLightPreset : Combinator + { + [XmlIgnore] + [Description("Specifies the channel map for the fixtures in the room.")] + public RoomFixtures Fixtures { get; set; } + + static IInterpolation CreateFixtureInterpolation(InterpolationMethod method, string calibrationFile) + { + switch (method) + { + case InterpolationMethod.None: return new AnonymousInterpolation(t => t); + case InterpolationMethod.Zero: return new AnonymousInterpolation(t => 0); + case InterpolationMethod.Linear: + var calibrationContents = File.ReadAllLines( + calibrationFile ?? + throw new ArgumentNullException(nameof(calibrationFile))); + var points = new List(); + var samples = new List(); + Array.ForEach(calibrationContents, row => + { + var values = row.Split(','); + if (values.Length != 2 || + !double.TryParse(values[0], out double level) || + !double.TryParse(values[1], out double lux)) + { + throw new ArgumentException( + "Calibration file should be in 2-column comma-separated text format.", + nameof(calibrationFile)); + } + + points.Add(lux); + samples.Add(level); + }); + return Interpolate.Linear(points, samples); + default: throw new ArgumentException("Unsupported interpolation method.", nameof(method)); + } + } + + static void OnNextPreset( + float preset, + Fixture fixture, + IInterpolation interpolation, + IObserver observer) + { + if (fixture == null) throw new ArgumentNullException(nameof(fixture)); + if (fixture.Channels != null) + { + var value = (int)interpolation.Interpolate(preset); + for (int i = 0; i < fixture.Channels.Count; i++) + { + observer.OnNext(new RoomLightMessage( + channel: fixture.Channels[i], + value: value)); + } + } + } + + public override IObservable Process(IObservable source) + { + var fixtures = Fixtures ?? throw new InvalidOperationException("No fixtures have been specified."); + return Observable.Create(observer => + { + var coldWhiteLookup = CreateFixtureInterpolation( + fixtures.ColdWhite.InterpolationMethod, + fixtures.ColdWhite.CalibrationFile); + var warmWhiteLookup = CreateFixtureInterpolation( + fixtures.WarmWhite.InterpolationMethod, + fixtures.WarmWhite.CalibrationFile); + var redLookup = CreateFixtureInterpolation( + fixtures.Red.InterpolationMethod, + fixtures.Red.CalibrationFile); + + var presetObserver = Observer.Create( + value => + { + OnNextPreset(value.ColdWhite, fixtures.ColdWhite, coldWhiteLookup, observer); + OnNextPreset(value.WarmWhite, fixtures.WarmWhite, warmWhiteLookup, observer); + OnNextPreset(value.Red, fixtures.Red, redLookup, observer); + }, + observer.OnError, + observer.OnCompleted); + return source.SubscribeSafe(presetObserver); + }); + } + + class AnonymousInterpolation : IInterpolation + { + readonly Func interpolate; + + public AnonymousInterpolation(Func interpolator) + { + interpolate = interpolator; + } + + public double Interpolate(double t) => interpolate(t); + public bool SupportsDifferentiation => false; + public bool SupportsIntegration => false; + public double Differentiate(double t) => throw new NotSupportedException(); + public double Differentiate2(double t) => throw new NotSupportedException(); + public double Integrate(double t) => throw new NotSupportedException(); + public double Integrate(double a, double b) => throw new NotSupportedException(); + } + } +} From dad5688dbbb26decd15e723c529560840e1e5ab9 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 10 Oct 2023 13:35:07 +0100 Subject: [PATCH 13/18] Add operator to handle controller light presets --- .../LightPresetHandler.bonsai | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/Aeon.Environment/LightPresetHandler.bonsai diff --git a/src/Aeon.Environment/LightPresetHandler.bonsai b/src/Aeon.Environment/LightPresetHandler.bonsai new file mode 100644 index 0000000..78a1686 --- /dev/null +++ b/src/Aeon.Environment/LightPresetHandler.bonsai @@ -0,0 +1,101 @@ + + + Converts a sequence of light preset commands to a sequence of room light controller messages using the specified channel map. + + + + Source1 + + + + + + + + + Source1 + + + Group + + + Group + + + Key + + + Group + + + + + + + + + ChannelMap + + + Rooms + + + Group + + + Key + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Item1 + Item2 + + + + + + + + + + + \ No newline at end of file From 7afd3ba59fbd26bfc25c63b7caa574f2d0cbe5fc Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 10 Oct 2023 23:13:05 +0100 Subject: [PATCH 14/18] Remove obsolete property reference --- src/Aeon.Environment/LightCycle.bonsai | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Aeon.Environment/LightCycle.bonsai b/src/Aeon.Environment/LightCycle.bonsai index 19867af..be345c6 100644 --- a/src/Aeon.Environment/LightCycle.bonsai +++ b/src/Aeon.Environment/LightCycle.bonsai @@ -48,9 +48,6 @@ Item4 as WarmWhite) - - - 0 @@ -70,9 +67,8 @@ Item4 as WarmWhite) - - - + + \ No newline at end of file From e0ccb88814badd65ade4bda20dbe8bc582180169 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 11 Oct 2023 00:15:20 +0100 Subject: [PATCH 15/18] Rename network light client operator for clarity --- .../{LightController.bonsai => LightClient.bonsai} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Aeon.Environment/{LightController.bonsai => LightClient.bonsai} (97%) diff --git a/src/Aeon.Environment/LightController.bonsai b/src/Aeon.Environment/LightClient.bonsai similarity index 97% rename from src/Aeon.Environment/LightController.bonsai rename to src/Aeon.Environment/LightClient.bonsai index 7ffb80f..7f983e2 100644 --- a/src/Aeon.Environment/LightController.bonsai +++ b/src/Aeon.Environment/LightClient.bonsai @@ -7,7 +7,7 @@ xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns="https://bonsai-rx.org/2018/workflow"> - Provides control and acquisition functionality for automated room lighting. + Provides a network client for automated room light control. From 1a9b32c742f132dc51fae361e151973c633cbec1 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 11 Oct 2023 00:16:12 +0100 Subject: [PATCH 16/18] Ensure all responses are paired to the right topic --- .../LightPresetHandler.bonsai | 154 ++++++++++++------ .../RoomLightController.bonsai | 49 ++++++ 2 files changed, 151 insertions(+), 52 deletions(-) create mode 100644 src/Aeon.Environment/RoomLightController.bonsai diff --git a/src/Aeon.Environment/LightPresetHandler.bonsai b/src/Aeon.Environment/LightPresetHandler.bonsai index 78a1686..8850039 100644 --- a/src/Aeon.Environment/LightPresetHandler.bonsai +++ b/src/Aeon.Environment/LightPresetHandler.bonsai @@ -2,100 +2,150 @@ - Converts a sequence of light preset commands to a sequence of room light controller messages using the specified channel map. + Provides a default handler for light commands using the specified channel map config file. Source1 - + - + Source1 - - Group + + LightPresets + - - Group - - - Key - - - Group + + - - - - + - - ChannelMap + + Rooms - - Group - - - Key - - - - - - - - - - - + - - + + + + + + Source1 + + + Room + + + LightPresets + + + Room + + + + + + RoomPresets + + + + Source1 + + + Item1.Key,Item2.Key + + + + + + + + + + + + + Item1 + + + + + + Room + + + Value + + + + + + + + + + + Room + + + Key + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - - - Item1 - Item2 - - \ No newline at end of file diff --git a/src/Aeon.Environment/RoomLightController.bonsai b/src/Aeon.Environment/RoomLightController.bonsai new file mode 100644 index 0000000..49d8190 --- /dev/null +++ b/src/Aeon.Environment/RoomLightController.bonsai @@ -0,0 +1,49 @@ + + + Provides a centralized controller for automating grouped room light changes. + + + + Source1 + + + Item1 + + + + + + + + + PT0S + + + + Item2 + + + + + + Item2 + Item1 + + + + + + + + + + + + + + + \ No newline at end of file From 75440a6a00ad262748ece12835f3a97a87902e9e Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 11 Oct 2023 00:33:03 +0100 Subject: [PATCH 17/18] Rename externalized property for clarity --- src/Aeon.Acquisition/Aeon.Acquisition.csproj | 2 +- src/Aeon.Environment/Aeon.Environment.csproj | 2 +- src/Aeon.Environment/LightClient.bonsai | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Aeon.Acquisition/Aeon.Acquisition.csproj b/src/Aeon.Acquisition/Aeon.Acquisition.csproj index 44a523b..9deee9d 100644 --- a/src/Aeon.Acquisition/Aeon.Acquisition.csproj +++ b/src/Aeon.Acquisition/Aeon.Acquisition.csproj @@ -6,7 +6,7 @@ Bonsai Rx Project Aeon Acquisition net472 0.5.0 - build230924 + build231005 diff --git a/src/Aeon.Environment/Aeon.Environment.csproj b/src/Aeon.Environment/Aeon.Environment.csproj index 2eb4217..d59592b 100644 --- a/src/Aeon.Environment/Aeon.Environment.csproj +++ b/src/Aeon.Environment/Aeon.Environment.csproj @@ -7,7 +7,7 @@ Bonsai Rx Project Aeon Environment net472 0.1.0 - build230927 + build231005 diff --git a/src/Aeon.Environment/LightClient.bonsai b/src/Aeon.Environment/LightClient.bonsai index 7f983e2..6028246 100644 --- a/src/Aeon.Environment/LightClient.bonsai +++ b/src/Aeon.Environment/LightClient.bonsai @@ -14,7 +14,7 @@ - + From ad0c1ccc562cf636391d7c868bd4da1c1b267216 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 11 Oct 2023 00:37:54 +0100 Subject: [PATCH 18/18] Skip header row when loading calibration file --- src/Aeon.Environment/InterpolateRoomLightPreset.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Aeon.Environment/InterpolateRoomLightPreset.cs b/src/Aeon.Environment/InterpolateRoomLightPreset.cs index 39d98a6..d2584b1 100644 --- a/src/Aeon.Environment/InterpolateRoomLightPreset.cs +++ b/src/Aeon.Environment/InterpolateRoomLightPreset.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Reactive; using System.Reactive.Linq; using System.Xml.Serialization; @@ -30,7 +31,7 @@ static IInterpolation CreateFixtureInterpolation(InterpolationMethod method, str throw new ArgumentNullException(nameof(calibrationFile))); var points = new List(); var samples = new List(); - Array.ForEach(calibrationContents, row => + foreach (var row in calibrationContents.Skip(1)) { var values = row.Split(','); if (values.Length != 2 || @@ -44,7 +45,7 @@ static IInterpolation CreateFixtureInterpolation(InterpolationMethod method, str points.Add(lux); samples.Add(level); - }); + } return Interpolate.Linear(points, samples); default: throw new ArgumentException("Unsupported interpolation method.", nameof(method)); }