diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 8e688858..81f3774a 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -327,6 +327,7 @@ + @@ -337,6 +338,7 @@ + diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Timers/RetriggerableTimer.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Timers/RetriggerableTimer.cs new file mode 100644 index 00000000..b31c9da0 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Timers/RetriggerableTimer.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +using Newtonsoft.Json; + + +namespace PepperDash.Essentials.Core.Timers +{ + /// + /// A device that runs a retriggerable timer and can execute actions specified in config + /// + [Description("A retriggerable timer device")] + public class RetriggerableTimer : EssentialsDevice + { + private RetriggerableTimerPropertiesConfig _propertiesConfig; + + private CTimer _timer; + private long _timerIntervalMs; + + public RetriggerableTimer(string key, DeviceConfig config) + : base(key, config.Name) + { + var props = config.Properties.ToObject(); + _propertiesConfig = props; + + if (_propertiesConfig != null) + { + _timerIntervalMs = _propertiesConfig.TimerIntervalMs; + } + } + + public override bool CustomActivate() + { + if (_propertiesConfig.StartTimerOnActivation) + { + StartTimer(); + } + + return base.CustomActivate(); + } + + private void CleanUpTimer() + { + if (_timer != null) + { + _timer.Stop(); + _timer.Dispose(); + } + + _timer = null; + } + + public void StartTimer() + { + CleanUpTimer(); + + _timer = new CTimer(TimerElapsedCallback, GetActionFromConfig(eRetriggerableTimerEvents.Elapsed), 0, _timerIntervalMs); + } + + public void StopTimer() + { + _timer.Stop(); + + ExecuteAction(GetActionFromConfig(eRetriggerableTimerEvents.Stopped)); + } + + private DeviceActionWrapper GetActionFromConfig(eRetriggerableTimerEvents eventType) + { + var action = _propertiesConfig.Events[eRetriggerableTimerEvents.Elapsed]; + + if (action != null) + return action; + else return null; + } + + /// + /// Executes the Elapsed action from confing when the timer elapses + /// + /// + private void TimerElapsedCallback(object action) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Timer Elapsed. Executing Action"); + + if (action == null) + { + Debug.Console(1, this, "Timer elapsed but unable to execute action. Action is null."); + return; + } + + var devAction = action as DeviceActionWrapper; + if (devAction != null) + ExecuteAction(devAction); + else + { + Debug.Console(2, this, "Unable to cast action as DeviceActionWrapper. Cannot Execute"); + } + + } + + private void ExecuteAction(DeviceActionWrapper action) + { + if (action == null) + return; + + try + { + DeviceJsonApi.DoDeviceAction(action); + } + catch (Exception e) + { + Debug.Console(2, this, "Error Executing Action: {0}", e); + } + //finally // Not sure this is needed + //{ + // _Timer.Reset(0, _TimerIntervalMs); + //} + } + } + + /// + /// Configuration Properties for RetriggerableTimer + /// + public class RetriggerableTimerPropertiesConfig + { + [JsonProperty("startTimerOnActivation")] + public bool StartTimerOnActivation { get; set; } + + [JsonProperty("timerIntervalMs")] + public long TimerIntervalMs { get; set; } + + [JsonProperty("events")] + public Dictionary Events { get; set; } + + public RetriggerableTimerPropertiesConfig() + { + Events = new Dictionary(); + } + } + + /// + /// The set of values describing events on the timer + /// + public enum eRetriggerableTimerEvents + { + Elapsed, + Stopped, + } + + /// + /// Factory class + /// + public class RetriggerableTimerFactory : EssentialsDeviceFactory + { + public RetriggerableTimerFactory() + { + TypeNames = new List() { "retriggerabletimer" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new RetriggerableTimer Device"); + + return new RetriggerableTimer(dc.Key, dc); + } + } + + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Utilities/ActionSequence.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Utilities/ActionSequence.cs new file mode 100644 index 00000000..c62b6e47 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Utilities/ActionSequence.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.CrestronThread; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Core.Utilities +{ + /// + /// A device that executes a sequence of actions with optional delays between actions + /// + [Description("A device that exectues a sequence of actions with optional delays between actions")] + public class ActionSequence : EssentialsDevice + { + private ActionSequencePropertiesConfig _propertiesConfig; + + private CrestronQueue _actionQueue; + + private Thread _worker; + + private bool _allowActionsToExecute; + + public ActionSequence(string key, DeviceConfig config) + : base(key, config.Name) + { + var props = config.Properties.ToObject(); + _propertiesConfig = props; + + if (_propertiesConfig != null) + { + if (_propertiesConfig.ActionSequence.Count > 0) + { + _actionQueue = new CrestronQueue(_propertiesConfig.ActionSequence.Count); + } + } + } + + /// + /// Starts executing the sequenced actions + /// + public void StartSequence() + { + Debug.Console(1, this, "Starting Action Sequence"); + _allowActionsToExecute = true; + AddActionsToQueue(); + _worker = new Thread(ProcessActions, null, Thread.eThreadStartOptions.Running); + } + + /// + /// Stops executing the sequenced actions + /// + public void StopSequence() + { + Debug.Console(1, this, "Stopping Action Sequence"); + _allowActionsToExecute = false; + _worker.Abort(); + } + + /// + /// Populates the queue from the configuration information + /// + private void AddActionsToQueue() + { + Debug.Console(1, this, "Adding {0} actions to queue", _propertiesConfig.ActionSequence.Count); + + for (int i = 0; i < _propertiesConfig.ActionSequence.Count; i++) + { + _actionQueue.Enqueue(_propertiesConfig.ActionSequence[i]); + } + } + + private object ProcessActions(object obj) + { + while (_allowActionsToExecute && _actionQueue.Count > 0) + { + SequencedDeviceActionWrapper action = null; + + action = _actionQueue.Dequeue(); + if (action == null) + break; + + // Delay before executing + if (action.DelayMs > 0) + Thread.Sleep(action.DelayMs); + + ExecuteAction(action); + } + + return null; + } + + private void ExecuteAction(DeviceActionWrapper action) + { + if (action == null) + return; + + try + { + DeviceJsonApi.DoDeviceAction(action); + } + catch (Exception e) + { + Debug.Console(2, this, "Error Executing Action: {0}", e); + } + } + } + + /// + /// Configuration Properties for ActionSequence + /// + public class ActionSequencePropertiesConfig + { + [JsonProperty("actionSequence")] + public List ActionSequence { get; set; } + + public ActionSequencePropertiesConfig() + { + ActionSequence = new List(); + } + } + + public class SequencedDeviceActionWrapper : DeviceActionWrapper + { + [JsonProperty("delayMs")] + public int DelayMs { get; set; } + } + + /// + /// Factory class + /// + public class ActionSequenceFactory : EssentialsDeviceFactory + { + public ActionSequenceFactory() + { + TypeNames = new List() { "actionsequence" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new ActionSequence Device"); + + return new ActionSequence(dc.Key, dc); + } + } + +} \ No newline at end of file