From 6d282db6e82365109074ffe8cbaeb8ef174bbe5c Mon Sep 17 00:00:00 2001 From: Pavel Yadlouski <46033323+x00Pavel@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:21:46 +0100 Subject: [PATCH] Merge pull request #38 * Init automation right in the config of the room * Update docs --- Docs/Advanced/room-service.md | 3 + Docs/how-to-use.md | 105 ++++++++++++------ src/Core/Automations/AutomationBase.cs | 72 ++++++------ src/Core/Automations/BlindAutomationBase.cs | 46 ++++---- src/Core/Automations/LightAutomationBase.cs | 51 ++++----- .../Automations/MainLightAutomationBase.cs | 36 +++--- src/Core/Configs/AutomationConfig.cs | 34 +++--- src/Core/Fsm/BlindsFsm.cs | 2 +- src/Core/Fsm/IFsmBase.cs | 3 +- src/Core/Fsm/LightFsmBase.cs | 2 +- src/Core/Fsm/MainLightFsmBase.cs | 2 +- .../{Configs => RoomManager}/IRoomConfig.cs | 9 +- src/Core/RoomManager/Room.cs | 25 ++--- src/Core/RoomManager/RoomManager.cs | 20 ++-- 14 files changed, 200 insertions(+), 210 deletions(-) create mode 100644 Docs/Advanced/room-service.md rename src/Core/{Configs => RoomManager}/IRoomConfig.cs (79%) diff --git a/Docs/Advanced/room-service.md b/Docs/Advanced/room-service.md new file mode 100644 index 0000000..eae49ca --- /dev/null +++ b/Docs/Advanced/room-service.md @@ -0,0 +1,3 @@ +# Room Controls + +The NetEntityAutomation creates several services for controlling automation in individual rooms. \ No newline at end of file diff --git a/Docs/how-to-use.md b/Docs/how-to-use.md index 23d3943..2bf6d24 100644 --- a/Docs/how-to-use.md +++ b/Docs/how-to-use.md @@ -21,13 +21,16 @@ Configuration class is used to define rooms and entities that are present in spe Following example illustrates how to define a room configuration: ```csharp +using NetEntityAutomation.Core.Automations; +using NetEntityAutomation.Core.RoomManager; + namespace nd_app.apps.HomeAutomation.Configs; -public class LivingRoomConfig: IRoomConfig +public class LivingRoomConfigV1: IRoomConfig { public string Name => "Living room"; public ILogger Logger { get; set; } - public IEnumerable Entities { get; set; } + public IEnumerable Entities { get; set; } public NightModeConfig? NightMode { get; set; } public LivingRoomConfigV1( @@ -41,54 +44,82 @@ public class LivingRoomConfig: IRoomConfig ) { Logger = logger; - var secondaryLight = new AutomationConfig - { - AutomationType = AutomationType.SecondaryLight, - ServiceAccountId = personEntities.Netdaemon.Attributes?.UserId ?? "", - Entities = new[] - { - lights.LivingroomBulb, - lights.LedLivingRoomSonoffLed, - }, - Triggers = new [] + var mainLight = ; + var secondaryLight = ; + Entities = new List + { + new MainLightAutomationBase { - new MotionSensor( - new [] + EntitiesList = new List + { + lights.LivingroomBulbLight + }, + Triggers = new [] + { + new MotionSensor(new [] { - sensors.MotionSensorLivingRoomMotion, - sensors.MotionSensorLivingRoom2Motion + sensors.LivingRoomMotionSensorMotion, + sensors.LivingRoomMotionSensor1Motion }, context) + }, + WaitTime = TimeSpan.FromHours(1) }, - NightMode = new NightModeConfig - { - IsEnabled = true, - } - }; - var blinds = new AutomationConfig - { - AutomationType = AutomationType.Blinds, - Entities = new Entity[] + new LightAutomationBase + { + EntitiesList = new List + { + lights.LivingroomBulb, + lights.LedLivingRoomSonoffLed, + }, + Triggers = new [] + { + new MotionSensor( + new[] + { + sensors.LivingRoomMotionSensor1Motion, + sensors.LivingRoomMotionSensorMotion + }, context) + }, + ServiceAccountId = personEntities.Netdaemon.Attributes?.UserId ?? "", + WaitTime = TimeSpan.FromMinutes(20), + SwitchTimer = TimeSpan.FromHours(1), + StopAtTimeFunc = () => DateTime.Parse(sun.Sun.Attributes?.NextDawn ?? "06:00:00").TimeOfDay, + StartAtTimeFunc = () => + DateTime.Parse(sun.Sun.Attributes?.NextDusk ?? "20:00:00").TimeOfDay - + TimeSpan.FromMinutes(30), + NightMode = new NightModeConfig + { + IsEnabled = true, + Devices = new List + { + lights.LivingroomBulb, + }, + StartAtTimeFunc = () => DateTime.Parse("23:00:00").TimeOfDay + } + }, + new BlindAutomationBase { - covers.LumiLumiCurtainAcn002Cover, - sun.Sun + Sun = sun.Sun, + EntitiesList = new List + { + covers.LivingRoomBlinds1Cover, + }, + StartAtTimeFunc = null, + StopAtTimeFunc = null } }; - Entities = new List - { - secondaryLight, - blinds - }; } } ``` In the example above, `LivingRoomConfig` class is used to define a living room. -Abstracting from the details, it contains a list of entities that are grouped for a specific automation scenario: secondary light and blinds. -Each entity is represented by an instance of `AutomationConfig` class. -`AutomationConfig` class contains information about the entity, its triggers, and other settings that are specific to the automation scenario. -There is more options available in `AutomationConfig` class, but the example above shows the most common ones. +Abstracting from the details, it contains a list of entities that are grouped for a specific automation scenario: main light, secondary light and blinds. +Each entity is represented by an instance of `AutomationBase` class. +`AutomationBase` class contains information about the entity, its triggers, and other settings that are specific to the automation scenario. +There is more options available in `AutomationBase` class, but the example above shows the most common ones. +This class should be inherited in order to define a custom automation scenario. > This configuration might not be ideal and might change in the future. > It is recommended to check the latest version of the library to see the most up-to-date configuration. -More datailed information about configuration classes and their properties can be found in the API documentation. +More detailed information about configuration classes and their properties can be found in the API documentation. diff --git a/src/Core/Automations/AutomationBase.cs b/src/Core/Automations/AutomationBase.cs index 1028cab..ffed074 100644 --- a/src/Core/Automations/AutomationBase.cs +++ b/src/Core/Automations/AutomationBase.cs @@ -2,30 +2,32 @@ using Microsoft.Extensions.Logging; using NetDaemon.HassModel; using NetDaemon.HassModel.Entities; +using NetEntityAutomation.Core.Conditions; using NetEntityAutomation.Core.Configs; +using NetEntityAutomation.Core.Triggers; using NetEntityAutomation.Extensions.Events; using NetEntityAutomation.Extensions.ExtensionMethods; namespace NetEntityAutomation.Core.Automations; -internal enum ServiceAction -{ - Disable, - Enable, - Toggle, -} - -internal record ServiceData -{ - public string? action { get; init; } - public string? value { get; init; } -} -public abstract class IAutomationBase +public abstract class AutomationBase: IAutomationConfig { private bool _enabled = true; - public IObservable IsEnabledObserver; + public readonly IObservable IsEnabledObserver; public event EventHandler? IsEnabledChanged; + public IEnumerable Triggers { get; set; } + public IEnumerable Conditions { get; set; } = new []{new DefaultCondition()}; + public string ServiceAccountId { get; set; } + public TimeSpan WaitTime { get; set; } + public TimeSpan SwitchTimer { get; set; } + public Func? StopAtTimeFunc { get; set; } + public Func? StartAtTimeFunc { get; set; } + public NightModeConfig NightMode { get; set; } + public IHaContext Context { get; set; } + public ILogger Logger { get; set; } + public abstract void ConfigureAutomation(); + public bool IsEnabled { get => _enabled; @@ -36,6 +38,13 @@ public bool IsEnabled IsEnabledChanged?.Invoke(this, value); } } + protected AutomationBase() + { + IsEnabledObserver = Observable.FromEventPattern( + handler => IsEnabledChanged += handler, + handler => IsEnabledChanged -= handler + ).Select(pattern => pattern.EventArgs); + } } /// @@ -44,30 +53,11 @@ public bool IsEnabled /// /// Type of entities an automation will work with /// Type of Finite State Machine that will be used for storing and representing the state of TEntity -public abstract class AutomationBase: IAutomationBase +public abstract class AutomationBase: AutomationBase { - protected IHaContext Context { get; set; } - protected ILogger Logger { get; set; } - protected AutomationConfig Config { get; set; } - protected List EntitiesList { get; set; } - protected List FsmList; + public List EntitiesList { get; set; } + protected List FsmList { get; } = []; - protected AutomationBase(IHaContext context, AutomationConfig config, ILogger logger) - { - Context = context; - Logger = logger; - Config = config; - IsEnabledObserver = Observable.FromEventPattern( - handler => IsEnabledChanged += handler, - handler => IsEnabledChanged -= handler - ).Select(pattern => pattern.EventArgs); - FsmList = new List(); - EntitiesList = Config.Entities.OfType().ToList(); - if (Config is { StartAtTimeFunc: not null, StopAtTimeFunc: not null }) - Logger.LogDebug("Working hours from {Start} - {End}", Config.StartAtTimeFunc() , Config.StopAtTimeFunc()); - Logger.LogDebug("Night mode from {Start} - {End}", Config.NightMode.StartAtTimeFunc(), Config.NightMode.StopAtTimeFunc()); - } - /// /// Helper method to trigger an event at a specific time of the day. /// It uses Observable.Timer to trigger the event. @@ -122,7 +112,7 @@ protected void CreateFsm() /// /// protected IObservable UserEvent(string id) => EntityEvent(id) - .Where(e => !e.IsAutomationInitiated(Config.ServiceAccountId)); + .Where(e => !e.IsAutomationInitiated(ServiceAccountId)); /// /// Observable for all state changes of a specific entity initiated by the automation. @@ -131,7 +121,7 @@ protected IObservable UserEvent(string id) => EntityEvent(id) /// Account ID which owns the token for NetDaemon /// protected IObservable AutomationEvent(string id) => EntityEvent(id) - .Where(e => e.IsAutomationInitiated(Config.ServiceAccountId)); + .Where(e => e.IsAutomationInitiated(ServiceAccountId)); /// /// Helper function to choose between two actions based on a condition. @@ -154,8 +144,8 @@ protected IObservable AutomationEvent(string id) => EntityEvent(id) protected bool IsWorkingHours() { - if (Config is { StartAtTimeFunc: not null, StopAtTimeFunc: not null }) - return UtilsMethods.NowInTimeRange(Config.StartAtTimeFunc(), Config.StopAtTimeFunc()); + if (NightMode is { StartAtTimeFunc: not null, StopAtTimeFunc: not null }) + return UtilsMethods.NowInTimeRange(StartAtTimeFunc(), StopAtTimeFunc()); Logger.LogWarning("Can't determine working hours. StartAtTimeFunc or StopAtTimeFunc is not set"); return true; } @@ -172,4 +162,4 @@ protected bool IsWorkingHours() action(); return null; } -} \ No newline at end of file +} diff --git a/src/Core/Automations/BlindAutomationBase.cs b/src/Core/Automations/BlindAutomationBase.cs index 02b61f5..89c155f 100644 --- a/src/Core/Automations/BlindAutomationBase.cs +++ b/src/Core/Automations/BlindAutomationBase.cs @@ -1,10 +1,9 @@ using System.Reactive.Linq; using Microsoft.Extensions.Logging; -using NetDaemon.HassModel; using NetDaemon.HassModel.Entities; -using NetEntityAutomation.Extensions.ExtensionMethods; -using NetEntityAutomation.Core.Configs; using NetEntityAutomation.Core.Fsm; +using NetEntityAutomation.Extensions.ExtensionMethods; + namespace NetEntityAutomation.Core.Automations; /// @@ -13,24 +12,18 @@ namespace NetEntityAutomation.Core.Automations; /// public class BlindAutomationBase : AutomationBase { - private readonly ISunEntityCore _sun; + public required ISunEntityCore Sun { get; set; } private int OpenBlinds => EntitiesList.Count(b => Context.GetState(b.EntityId)?.State == "open"); - - public BlindAutomationBase(IHaContext haContext, AutomationConfig automation, ILogger roomConfigLogger): base(haContext, automation, roomConfigLogger) - { - _sun = Config.Entities.OfType().First(); - CreateFsm(); - ConfigureAutomation(); - } + private bool IsOpenTime() { - return (Config.StartAtTimeFunc == null, Config.StopAtTimeFunc == null) switch + return (StartAtTimeFunc == null, StopAtTimeFunc == null) switch { - (true, true) => _sun.IsAboveHorizon(), - (false, false) => UtilsMethods.NowInTimeRange(Config.StartAtTimeFunc!(), Config.StopAtTimeFunc!()), - (true, false) => _sun.IsAboveHorizon() && UtilsMethods.NowAfterTime(Config.StopAtTimeFunc!()), - (false, true) => UtilsMethods.NowBeforeTime(Config.StartAtTimeFunc!()) && _sun.IsAboveHorizon(), + (true, true) => Sun.IsAboveHorizon(), + (false, false) => UtilsMethods.NowInTimeRange(StartAtTimeFunc!(), StopAtTimeFunc!()), + (true, false) => Sun.IsAboveHorizon() && UtilsMethods.NowAfterTime(StopAtTimeFunc!()), + (false, true) => UtilsMethods.NowBeforeTime(StartAtTimeFunc!()) && Sun.IsAboveHorizon(), }; } @@ -47,7 +40,7 @@ private BlindsStateActivateAction BlindsActivateActions(ICoverEntityCore blind) protected override BlindsFsm ConfigureFsm(ICoverEntityCore blind) { - var fsm = new BlindsFsm(Config, Logger, blind).Configure(BlindsActivateActions(blind)); + var fsm = new BlindsFsm(Logger, blind).Configure(BlindsActivateActions(blind)); UserClose(blind.EntityId).Subscribe(_ => ChooseAction(OpenBlinds > 0, fsm.FireManualClose, FsmList.FireAllOff)); UserOpen(blind.EntityId).Subscribe(_ => fsm.FireManualOpen()); AutomationClose(blind.EntityId).Subscribe(_ =>ChooseAction(OpenBlinds > 0, fsm.FireAutomationClose, fsm.FireAutomationClose)); @@ -55,30 +48,31 @@ protected override BlindsFsm ConfigureFsm(ICoverEntityCore blind) return fsm; } - private void ConfigureAutomation() - { + public override void ConfigureAutomation() + { + CreateFsm(); Logger.LogDebug("Configuring blind automation"); - if (Config.StartAtTimeFunc == null) + if (StartAtTimeFunc == null) { - _sun.AboveHorizon().Subscribe(_ => EntitiesList.OpenCover()); + Sun.AboveHorizon().Subscribe(_ => EntitiesList.OpenCover()); Logger.LogDebug("Subscribed to sun above horizon event to open blinds"); } else { - var time = Config.StartAtTimeFunc.Invoke(); + var time = StartAtTimeFunc.Invoke(); DailyEventAtTime(time, EntitiesList.OpenCover); Logger.LogDebug("Subscribed to daily event at {Time} to open blinds", time); } - if (Config.StopAtTimeFunc == null) + if (StopAtTimeFunc == null) { - _sun.BelowHorizon().Subscribe(_ => EntitiesList.CloseCover()); + Sun.BelowHorizon().Subscribe(_ => EntitiesList.CloseCover()); Logger.LogDebug("Subscribed to sun below horizon event to close blinds"); } else { - DailyEventAtTime(Config.StopAtTimeFunc.Invoke(), EntitiesList.CloseCover); - Logger.LogDebug("Subscribed to daily event at {Time} to close blinds", Config.StopAtTimeFunc.Invoke()); + DailyEventAtTime(StopAtTimeFunc.Invoke(), EntitiesList.CloseCover); + Logger.LogDebug("Subscribed to daily event at {Time} to close blinds", StopAtTimeFunc.Invoke()); } } diff --git a/src/Core/Automations/LightAutomationBase.cs b/src/Core/Automations/LightAutomationBase.cs index f215027..c3ca7a1 100644 --- a/src/Core/Automations/LightAutomationBase.cs +++ b/src/Core/Automations/LightAutomationBase.cs @@ -1,8 +1,6 @@ using System.Reactive.Linq; using Microsoft.Extensions.Logging; -using NetDaemon.HassModel; using NetDaemon.HassModel.Entities; -using NetEntityAutomation.Core.Configs; using NetEntityAutomation.Core.Fsm; using NetEntityAutomation.Core.Triggers; using NetEntityAutomation.Extensions.ExtensionMethods; @@ -23,21 +21,14 @@ namespace NetEntityAutomation.Core.Automations; /// public class LightAutomationBase : AutomationBase { - private readonly Dictionary _lightParameters = new (); - - private static LightParameters DefaultLightParameters => new LightParameters {Brightness = 255}; - - public LightAutomationBase(IHaContext context, AutomationConfig config, ILogger logger): base(context, config, logger) - { - CreateFsm(); - ConfigureAutomation(); - } + private static LightParameters DefaultLightParameters => new() {Brightness = 255}; - private void ConfigureAutomation() + public override void ConfigureAutomation() { + CreateFsm(); Logger.LogDebug("Subscribing to motion sensor events"); - foreach (var sensor in Config.Triggers) + foreach (var sensor in Triggers) { sensor.On.Subscribe(e => { if (IsEnabled) TurnOnByAutomation(e.Entity); }); } @@ -54,13 +45,17 @@ private void ConfigureAutomation() AutomationEnabled.Subscribe(e => { Logger.LogDebug("Enabling automation {Automation}", nameof(LightAutomationBase)); - if (Config.Triggers.IsAllOn()) - EntitiesList.ForEach(l => TurnOnByAutomation(l)); + if (Triggers.IsAllOn()) + EntitiesList.ForEach(TurnOnByAutomation); }); } - private void TurnOnByAutomation(IEntityCore entityCore) + private void TurnOnByAutomation(IEntityCore? entityCore) { + if (entityCore == null) + { + throw new NullReferenceException("Light entity is null. Cannot turn on lights by motion sensor."); + } if (!IsWorkingHours()) { Logger.LogDebug("Turning on lights by motion sensor {Sensor} is not allowed because it's not working hours", @@ -68,7 +63,7 @@ private void TurnOnByAutomation(IEntityCore entityCore) return; } - if (!Config.Conditions.All(c => c.IsTrue())) + if (!Conditions.All(c => c.IsTrue())) { Logger.LogDebug("Not all conditions are met to turn on lights by motion sensor {Sensor}", entityCore.EntityId); @@ -76,14 +71,14 @@ private void TurnOnByAutomation(IEntityCore entityCore) } Logger.LogDebug("Turning on lights by motion sensor {Sensor}", entityCore.EntityId); - switch (Config.NightMode) + switch (NightMode) { case { IsEnabled: true, IsWorkingHours: true }: Logger.LogDebug("Time of Night Mode {Time}", DateTime.Now.TimeOfDay); foreach (var light in LightsOffByAutomation.Select(fsm => fsm.Entity)) { - if (Config.NightMode.Devices?.Contains(light) ?? false) - light.TurnOn(Config.NightMode.LightParameters); + if (NightMode.Devices?.Contains(light) ?? false) + light.TurnOn(NightMode.LightParameters); } break; case { IsEnabled: true, IsWorkingHours: false }: @@ -105,7 +100,7 @@ private void TurnOnByAutomation(IEntityCore entityCore) } } - private bool OnConditionsMet() => IsWorkingHours() && Config.Triggers.Any(s => s.IsOn()) && Config.Conditions.All(c => c.IsTrue()); + private bool OnConditionsMet() => IsWorkingHours() && Triggers.Any(s => s.IsOn()) && Conditions.All(c => c.IsTrue()); private LightStateActivateAction ActionForLight(ILightEntityCore l) { @@ -122,7 +117,7 @@ private LightStateActivateAction ActionForLight(ILightEntityCore l) if (OnConditionsMet()) { l.TurnOn(); - ResetTimerOrAction(lightFsm.Timer, Config.WaitTime, lightFsm.Entity.TurnOff, OnConditionsMet); + ResetTimerOrAction(lightFsm.Timer, WaitTime, lightFsm.Entity.TurnOff, OnConditionsMet); } else { @@ -135,7 +130,7 @@ private LightStateActivateAction ActionForLight(ILightEntityCore l) if (OnConditionsMet()) { l.TurnOn(); - ResetTimerOrAction(lightFsm.Timer, Config.WaitTime, lightFsm.Entity.TurnOff, OnConditionsMet); + ResetTimerOrAction(lightFsm.Timer, WaitTime, lightFsm.Entity.TurnOff, OnConditionsMet); } else { @@ -153,7 +148,7 @@ private LightStateActivateAction ActionForLight(ILightEntityCore l) protected override LightFsmBase ConfigureFsm(ILightEntityCore l) { - var lightFsm = new LightFsmBase(l, Config, Logger).Configure(ActionForLight(l)); + var lightFsm = new LightFsmBase(l, Logger).Configure(ActionForLight(l)); AutomationOn(l.EntityId) .Subscribe(e => @@ -162,14 +157,14 @@ protected override LightFsmBase ConfigureFsm(ILightEntityCore l) e.New?.EntityId, e.New?.State, e.New?.Context?.UserId); lightFsm.FireMotionOn(); // This is needed to reset the timer if timeout is expired, but there is still a motion - ResetTimerOrAction(lightFsm.Timer, Config.WaitTime, lightFsm.Entity.TurnOff, OnConditionsMet); + ResetTimerOrAction(lightFsm.Timer, WaitTime, lightFsm.Entity.TurnOff, OnConditionsMet); }); AutomationOff(l.EntityId) .Subscribe(e => { Logger.LogDebug("Light Event: lights {Light} is in {State} without user by automation {User}", e.New?.EntityId, e.New?.State, e.New?.Context?.UserId); - if (!Config.NightMode.IsWorkingHours) + if (!NightMode.IsWorkingHours) { Logger.LogDebug("Storing light parameters for light {Light} : {LightParams}", l.EntityId, e.Old?.AttributesJson); lightFsm.LastParams = @@ -186,7 +181,7 @@ protected override LightFsmBase ConfigureFsm(ILightEntityCore l) e.New?.State, e.New?.Context?.UserId); lightFsm.FireOn(); - lightFsm.Timer.StartTimer(Config.SwitchTimer, lightFsm.Entity.TurnOff); + lightFsm.Timer.StartTimer(SwitchTimer, lightFsm.Entity.TurnOff); }); UserOff(l.EntityId) .Subscribe(e => @@ -195,7 +190,7 @@ protected override LightFsmBase ConfigureFsm(ILightEntityCore l) e.New?.EntityId, e.New?.State, e.New?.Context?.UserId); - if (!Config.NightMode.IsWorkingHours) + if (!NightMode.IsWorkingHours) { Logger.LogDebug("Storing light parameters for light {Light} : {LightParams}", l.EntityId, e.Old?.AttributesJson); lightFsm.LastParams = diff --git a/src/Core/Automations/MainLightAutomationBase.cs b/src/Core/Automations/MainLightAutomationBase.cs index 6ec8fea..a0ec873 100644 --- a/src/Core/Automations/MainLightAutomationBase.cs +++ b/src/Core/Automations/MainLightAutomationBase.cs @@ -1,8 +1,5 @@ using System.Reactive.Linq; -using Microsoft.Extensions.Logging; -using NetDaemon.HassModel; using NetDaemon.HassModel.Entities; -using NetEntityAutomation.Core.Configs; using NetEntityAutomation.Core.Fsm; using NetEntityAutomation.Core.Triggers; using NetEntityAutomation.Extensions.Events; @@ -12,33 +9,30 @@ namespace NetEntityAutomation.Core.Automations; public class MainLightAutomationBase: AutomationBase { - private readonly IEnumerable _lights; - private readonly IEnumerable _motionSensors; - private readonly CustomTimer _timer; - - public MainLightAutomationBase(IHaContext context, AutomationConfig config, ILogger logger) : base(context, config, logger) - { - _lights = config.Entities.OfType(); - _motionSensors = config.Triggers.OfType(); - _timer = new CustomTimer(Logger); - CreateFsm(); - ConfigureAutomation(); - } + private IEnumerable _lights; + private IEnumerable _motionSensors; + private CustomTimer _timer; /// /// The automation scenario is to turn off the main light in specific time frame specified by /// Config.WaitTime parameter. /// On the timeout, if there are no triggers in on state (usually, this is a motion sensor), the light will be turned off. /// - private void ConfigureAutomation() - { - foreach (var light in _lights) + public override void ConfigureAutomation() + { + if (EntitiesList == null || Triggers == null) + throw new ArgumentNullException(nameof(EntitiesList), "Entities and Triggers must be set before creating an automation"); + _motionSensors = Triggers.OfType(); + _timer = new CustomTimer(Logger); + + CreateFsm(); + foreach (var light in EntitiesList) { light.OnEvent().Subscribe(e => { Observable - .Timer(Config.WaitTime) - .Subscribe(_ => ResetTimerOrAction(_timer, Config.WaitTime, light.TurnOff, _motionSensors.IsAnyOn)); + .Timer(WaitTime) + .Subscribe(_ => ResetTimerOrAction(_timer, WaitTime, light.TurnOff, _motionSensors.IsAnyOn)); }); light.OffEvent() .Subscribe(e => _timer.Dispose()); @@ -55,7 +49,7 @@ private void ConfigureAutomation() /// protected override MainLightFsmBase ConfigureFsm(ILightEntityCore entity) { - var lightFsm = new MainLightFsmBase(entity, Config, Logger).Configure(ActionForLight()); + var lightFsm = new MainLightFsmBase(entity, Logger).Configure(ActionForLight()); entity.OnEvent().Subscribe(_ => lightFsm.FireOn()); entity.OffEvent().Subscribe(_ => lightFsm.FireOff()); return lightFsm; diff --git a/src/Core/Configs/AutomationConfig.cs b/src/Core/Configs/AutomationConfig.cs index 6469863..8d16655 100644 --- a/src/Core/Configs/AutomationConfig.cs +++ b/src/Core/Configs/AutomationConfig.cs @@ -1,6 +1,7 @@ -using NetDaemon.HassModel.Entities; -using NetEntityAutomation.Core.Triggers; +using Microsoft.Extensions.Logging; +using NetDaemon.HassModel; using NetEntityAutomation.Core.Conditions; +using NetEntityAutomation.Core.Triggers; namespace NetEntityAutomation.Core.Configs; @@ -8,25 +9,18 @@ namespace NetEntityAutomation.Core.Configs; /// Class represents a configuration for automation. /// It is used to configure the automation by providing entities, triggers, conditions and other settings. /// -public record AutomationConfig +public interface IAutomationConfig { /// /// Entities that are used by the automation (including secondary entities that acts as a trigger like a Sun entity). /// On automation configuration phase, this list will be filtered to the specific type of entities based on automation type. /// - public IEnumerable Entities { get; set; } - /// - /// Type of automation. - /// All types are presented at this doc. - /// - public AutomationType AutomationType { get; set; } - /// /// Triggers are used to trigger the automation. /// The automation is relies that trigger will return input_boolean meaning On/Off state. /// - public IEnumerable Triggers { get; set; } = new List(); + public IEnumerable Triggers { get; set; } //= new List(); /// /// A list of conditions when the automation should be triggered. @@ -34,7 +28,7 @@ public record AutomationConfig /// It is mostly used for custom conditions. /// E.g if someone is at home, if tracker device is in sleep mode, etc. /// - public IEnumerable Conditions { get; set; } = new List { new DefaultCondition() }; + public IEnumerable Conditions { get; set; } //= new List { new DefaultCondition() }; /// /// Account ID which owns the token for NetDaemon. @@ -46,13 +40,13 @@ public record AutomationConfig /// Time for waiting for the next trigger event. /// E.g. in light scenario, after light was turned on by automation (triggered by motion sensor), the automation will check for motion after this period of time. /// - public TimeSpan WaitTime { get; set; } = TimeSpan.FromSeconds(60); + public TimeSpan WaitTime { get; set; } // = TimeSpan.FromSeconds(60); /// /// Time after manual interaction with the automation to trigger the event. /// E.g. in light scenario, after light was turned on by switch (manualy, from HA), the automation will turn off the light if there is no motion after this period of time. /// - public TimeSpan SwitchTimer { get; set; } = TimeSpan.FromHours(2); + public TimeSpan SwitchTimer { get; set; } // = TimeSpan.FromHours(2); /// /// Callable that returns time when the automation should stop working. @@ -68,7 +62,7 @@ public record AutomationConfig /// /// /// - public Func? StopAtTimeFunc { get; set; } = () => DateTime.Parse("06:00").TimeOfDay; + public Func? StopAtTimeFunc { get; set; } //= () => DateTime.Parse("06:00").TimeOfDay; /// /// Callable that returns time when the automation should start working. @@ -84,10 +78,16 @@ public record AutomationConfig /// /// /// - public Func? StartAtTimeFunc { get; set; } = () => DateTime.Parse("18:00").TimeOfDay; + public Func? StartAtTimeFunc { get; set; } //= () => DateTime.Parse("18:00").TimeOfDay; /// /// Configuration for night mode. /// - public NightModeConfig NightMode { get; set; } = new(); + public NightModeConfig NightMode { get; set; } //= new(); + + public IHaContext Context { get; set; } + + public ILogger Logger { get; set; } + + public void ConfigureAutomation(); } \ No newline at end of file diff --git a/src/Core/Fsm/BlindsFsm.cs b/src/Core/Fsm/BlindsFsm.cs index 7de4e2f..4433928 100644 --- a/src/Core/Fsm/BlindsFsm.cs +++ b/src/Core/Fsm/BlindsFsm.cs @@ -34,7 +34,7 @@ public class BlindsFsm : FsmBase { // private new ICoverEntityCore Entity { get; set; } - public BlindsFsm(AutomationConfig config, ILogger logger, ICoverEntityCore blinds) : base(config, logger) + public BlindsFsm(ILogger logger, ICoverEntityCore blinds) : base(logger) { Logger = logger; DefaultState = BlindsState.Closed; diff --git a/src/Core/Fsm/IFsmBase.cs b/src/Core/Fsm/IFsmBase.cs index 90f85bd..062d13c 100644 --- a/src/Core/Fsm/IFsmBase.cs +++ b/src/Core/Fsm/IFsmBase.cs @@ -23,9 +23,8 @@ public static void FireAllOff(this IEnumerable state) } } -public abstract class FsmBase(AutomationConfig config, ILogger logger): IFsmBase +public abstract class FsmBase(ILogger logger): IFsmBase { - private AutomationConfig Config { get; set; } = config; protected StateMachine _fsm; protected ILogger Logger = logger; public CustomTimer Timer = new (logger); diff --git a/src/Core/Fsm/LightFsmBase.cs b/src/Core/Fsm/LightFsmBase.cs index e07d800..ce5796f 100644 --- a/src/Core/Fsm/LightFsmBase.cs +++ b/src/Core/Fsm/LightFsmBase.cs @@ -35,7 +35,7 @@ public class LightFsmBase : FsmBase public LightParameters? LastParams; public new ILightEntityCore Entity { get; init; } - public LightFsmBase(ILightEntityCore light, AutomationConfig config, ILogger logger) : base(config, logger) + public LightFsmBase(ILightEntityCore light, ILogger logger) : base(logger) { DefaultState = LightState.Off; Entity = light; diff --git a/src/Core/Fsm/MainLightFsmBase.cs b/src/Core/Fsm/MainLightFsmBase.cs index 441ab1c..179746c 100644 --- a/src/Core/Fsm/MainLightFsmBase.cs +++ b/src/Core/Fsm/MainLightFsmBase.cs @@ -28,7 +28,7 @@ public struct MainLightActivateAction public class MainLightFsmBase : FsmBase { public new ILightEntityCore Entity { get; init; } - public MainLightFsmBase(ILightEntityCore light, AutomationConfig config, ILogger logger) : base(config, logger) + public MainLightFsmBase(ILightEntityCore light, ILogger logger) : base(logger) { DefaultState = MainLightState.Off; Entity = light; diff --git a/src/Core/Configs/IRoomConfig.cs b/src/Core/RoomManager/IRoomConfig.cs similarity index 79% rename from src/Core/Configs/IRoomConfig.cs rename to src/Core/RoomManager/IRoomConfig.cs index 32c5173..1a2a3b5 100644 --- a/src/Core/Configs/IRoomConfig.cs +++ b/src/Core/RoomManager/IRoomConfig.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Logging; +using NetEntityAutomation.Core.Automations; +using NetEntityAutomation.Core.Configs; -namespace NetEntityAutomation.Core.Configs; +namespace NetEntityAutomation.Core.RoomManager; public interface IRoom; @@ -13,7 +15,6 @@ public interface IRoomConfig : IRoom /// Name of the room. /// public string Name => GetType().Name; - /// /// Logger to be used by the room. /// This is externally useful for debugging and logging. @@ -21,9 +22,9 @@ public interface IRoomConfig : IRoom public ILogger Logger { get; set; } /// /// A list of automation configurations that will be used by the room. - /// See for more details. + /// See for more details. /// - public IEnumerable Entities { get; set; } + public IEnumerable Entities { get; set; } /// /// Night mode for the room. diff --git a/src/Core/RoomManager/Room.cs b/src/Core/RoomManager/Room.cs index 6ff3a79..773d532 100644 --- a/src/Core/RoomManager/Room.cs +++ b/src/Core/RoomManager/Room.cs @@ -2,7 +2,6 @@ using NetDaemon.HassModel; using NetDaemon.HassModel.Integration; using NetEntityAutomation.Core.Automations; -using NetEntityAutomation.Core.Configs; namespace NetEntityAutomation.Core.RoomManager; @@ -13,7 +12,7 @@ public class Room { private readonly IHaContext _haContext; private readonly IRoomConfig _roomConfig; - private readonly List _automations = []; + private readonly List _automations = []; public Room(IRoomConfig roomConfig, IHaContext haContext) { @@ -44,22 +43,12 @@ private void InitAutomations() { _roomConfig.Logger.LogDebug("Creating automations"); foreach (var automation in _roomConfig.Entities) - { - _roomConfig.Logger.LogDebug("Created {AutomationType}", automation.AutomationType); - switch (automation.AutomationType) - { - case AutomationType.MainLight: - _automations.Add(new MainLightAutomationBase(_haContext, automation, _roomConfig.Logger)); - break; - case AutomationType.SecondaryLight: - _automations.Add(new LightAutomationBase(_haContext, automation, _roomConfig.Logger)); - break; - case AutomationType.Blinds: - _automations.Add(new BlindAutomationBase(_haContext, automation, _roomConfig.Logger)); - break; - default: - break; - } + { + _roomConfig.Logger.LogDebug("Creating {AutomationType}", automation.GetType().Name); + automation.Context = _haContext; + automation.Logger = _roomConfig.Logger; + automation.ConfigureAutomation(); + _automations.Add(automation); } _roomConfig.Logger.LogDebug("Number of automations: {AutomationCount}", _automations.Count); } diff --git a/src/Core/RoomManager/RoomManager.cs b/src/Core/RoomManager/RoomManager.cs index 709d66e..71af74d 100644 --- a/src/Core/RoomManager/RoomManager.cs +++ b/src/Core/RoomManager/RoomManager.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using NetDaemon.HassModel; -using NetEntityAutomation.Core.Configs; namespace NetEntityAutomation.Core.RoomManager; @@ -12,20 +11,15 @@ namespace NetEntityAutomation.Core.RoomManager; public class RoomManager : IRoomManager { private readonly List _rooms = []; + private readonly List configs; + private readonly IHaContext haContext; - public RoomManager(IHaContext haContext, ILogger logger, IEnumerable rooms) + public RoomManager(IHaContext context, ILogger logger, IEnumerable rooms) { logger.LogInformation("Initialising room manager"); - logger.LogInformation("Number of rooms: {RoomCount}", rooms.Count()); - if (haContext == null) - { - logger.LogError("HaContext is null"); - throw new ArgumentNullException(nameof(haContext)); - } - - foreach (var roomConfig in rooms) - { - _rooms.Add(new Room(roomConfig, haContext)); - } + haContext = context ?? throw new ArgumentNullException(nameof(context)); + configs = rooms.ToList(); + configs.ForEach(config => _rooms.Add(new Room(config, haContext))); + logger.LogInformation("Number of rooms: {RoomCount}", configs.Count); } } \ No newline at end of file