From bd31d76e8b454b02af4f1361094cb40fdb4fc9f6 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 24 Aug 2018 18:08:25 -0600 Subject: [PATCH] New Room on to default source feature working using scheduled events and built in scheduler based on config values --- PepperDashEssentials/Factory/DeviceFactory.cs | 191 +++++---- .../PepperDashEssentials.csproj | 1 + .../RoomOnToDefaultSourceWhenOccupied.cs | 398 ++++++++++++++++++ .../Room/Config/EssentialsRoomConfig.cs | 30 -- .../Room/Types/EssentialsRoomBase.cs | 32 +- essentials-framework | 2 +- 6 files changed, 503 insertions(+), 151 deletions(-) create mode 100644 PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs diff --git a/PepperDashEssentials/Factory/DeviceFactory.cs b/PepperDashEssentials/Factory/DeviceFactory.cs index 16afc536..432802d0 100644 --- a/PepperDashEssentials/Factory/DeviceFactory.cs +++ b/PepperDashEssentials/Factory/DeviceFactory.cs @@ -1,92 +1,99 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials -{ - public class DeviceFactory - { - public static IKeyed GetDevice(DeviceConfig dc) - { - var key = dc.Key; - var name = dc.Name; - var type = dc.Type; - var properties = dc.Properties; - - var typeName = dc.Type.ToLower(); - - if (typeName == "amplifier") - { - return new Amplifier(dc.Key, dc.Name); - } - else if (dc.Group.ToLower() == "touchpanel") // typeName.StartsWith("tsw")) - { - return UiDeviceFactory.GetUiDevice(dc); - } - - else if (typeName == "mockdisplay") - { - return new MockDisplay(key, name); - } - - else if (typeName == "generic") - { - return new Device(key, name); - } - - // MOVE into something else??? - else if (typeName == "basicirdisplay") - { - var ir = IRPortHelper.GetIrPort(properties); - if (ir != null) - return new BasicIrDisplay(key, name, ir.Port, ir.FileName); - } - - else if (typeName == "commmock") - { - var comm = CommFactory.CreateCommForDevice(dc); - var props = JsonConvert.DeserializeObject( - properties.ToString()); - return new ConsoleCommMockDevice(key, name, props, comm); - } - - else if (typeName == "appserver") - { - var props = JsonConvert.DeserializeObject(properties.ToString()); - return new CotijaSystemController(key, name, props); - } - - else if (typeName == "mobilecontrolbridge-ddvc01") - { - var comm = CommFactory.GetControlPropertiesConfig(dc); - - var bridge = new PepperDash.Essentials.Room.Cotija.CotijaDdvc01RoomBridge(key, name, comm.IpIdInt); - bridge.AddPreActivationAction(() => - { - var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; - if (parent == null) - { - Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); - } - Debug.Console(0, bridge, "Linking to parent controller"); - bridge.AddParent(parent); - parent.AddBridge(bridge); - }); - - return bridge; - } - - return null; - } - } - -} +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharpPro; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials +{ + public class DeviceFactory + { + public static IKeyed GetDevice(DeviceConfig dc) + { + var key = dc.Key; + var name = dc.Name; + var type = dc.Type; + var properties = dc.Properties; + + var typeName = dc.Type.ToLower(); + + if (typeName == "amplifier") + { + return new Amplifier(dc.Key, dc.Name); + } + else if (dc.Group.ToLower() == "touchpanel") // typeName.StartsWith("tsw")) + { + return UiDeviceFactory.GetUiDevice(dc); + } + + else if (typeName == "mockdisplay") + { + return new MockDisplay(key, name); + } + + else if (typeName == "generic") + { + return new Device(key, name); + } + + // MOVE into something else??? + else if (typeName == "basicirdisplay") + { + var ir = IRPortHelper.GetIrPort(properties); + if (ir != null) + return new BasicIrDisplay(key, name, ir.Port, ir.FileName); + } + + else if (typeName == "commmock") + { + var comm = CommFactory.CreateCommForDevice(dc); + var props = JsonConvert.DeserializeObject( + properties.ToString()); + return new ConsoleCommMockDevice(key, name, props, comm); + } + + else if (typeName == "appserver") + { + var props = JsonConvert.DeserializeObject(properties.ToString()); + return new CotijaSystemController(key, name, props); + } + + else if (typeName == "mobilecontrolbridge-ddvc01") + { + var comm = CommFactory.GetControlPropertiesConfig(dc); + + var bridge = new PepperDash.Essentials.Room.Cotija.CotijaDdvc01RoomBridge(key, name, comm.IpIdInt); + bridge.AddPreActivationAction(() => + { + var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; + if (parent == null) + { + Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); + } + Debug.Console(0, bridge, "Linking to parent controller"); + bridge.AddParent(parent); + parent.AddBridge(bridge); + }); + + return bridge; + } + + else if (typeName == "roomonwhenoccupancydetectedfeature") + { + var props = JsonConvert.DeserializeObject(properties.ToString()); + + return new Room.Behaviours.RoomOnToDefaultSourceWhenOccupied(key, props); + } + + return null; + } + } + +} diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj index 4442a2fc..8d96ad67 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -142,6 +142,7 @@ + diff --git a/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs new file mode 100644 index 00000000..2e0e15a6 --- /dev/null +++ b/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs @@ -0,0 +1,398 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Crestron.SimplSharp; +using Crestron.SimplSharp.Scheduler; +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.Occupancy; + +namespace PepperDash.Essentials.Room.Behaviours +{ + /// + /// A device that when linked to a room can power the room on when enabled during scheduled hours. + /// + public class RoomOnToDefaultSourceWhenOccupied : Device + { + RoomOnToDefaultSourceWhenOccupiedConfig Config; + + public bool FeatureEnabled { get; private set; } + + public DateTime FeatureEnabledTime { get; private set; } + + ScheduledEvent FeatureEnableEvent; + + const string FeatureEnableEventName = "EnableRoomOnToDefaultSourceWhenOccupied"; + + public DateTime FeatureDisabledTime { get; private set; } + + ScheduledEvent FeatureDisableEvent; + + const string FeatureDisableEventName = "DisableRoomOnToDefaultSourceWhenOccupied"; + + ScheduledEventGroup FeatureEventGroup; + + public EssentialsRoomBase Room { get; private set; } + + private Fusion.EssentialsHuddleSpaceFusionSystemControllerBase FusionRoom; + + public RoomOnToDefaultSourceWhenOccupied(string key, RoomOnToDefaultSourceWhenOccupiedConfig config) + : base(key) + { + + CrestronConsole.AddNewConsoleCommand((o) => FeatureEventGroup.ClearAllEvents(), "ClearAllEvents", "Clears all scheduled events for this group", ConsoleAccessLevelEnum.AccessAdministrator); + + Config = config; + + FeatureEventGroup = new ScheduledEventGroup(this.Key); + + FeatureEventGroup.RetrieveAllEvents(); + + AddPostActivationAction(() => + { + if (Room.RoomOccupancy != null) + Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler(RoomIsOccupiedFeedback_OutputChange); + else + Debug.Console(1, this, "Room has no RoomOccupancy object set"); + + var fusionRoomKey = Config.RoomKey + "-fusion"; + + FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Fusion.EssentialsHuddleSpaceFusionSystemControllerBase; + + if (FusionRoom == null) + Debug.Console(1, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey); + }); + } + + public override bool CustomActivate() + { + Room = DeviceManager.GetDeviceForKey(Config.RoomKey) as EssentialsRoomBase; + + if (Room != null) + { + + + // Creates the event group and adds it to the Global Scheduler list + //Global.Scheduler.AddEventGroup(FeatureEventGroup); + + try + { + FeatureEnabledTime = DateTime.Parse(Config.OccupancyStartTime); + + if (FeatureEnabledTime != null) + { + Debug.Console(1, this, "Enabled Time: {0}", FeatureEnabledTime.ToString()); + } + else + Debug.Console(1, this, "Unable to parse {0} to DateTime", Config.OccupancyStartTime); + } + catch (Exception e) + { + Debug.Console(1, this, "Unable to parse OccupancyStartTime property: {0} \n Error: {1}", Config.OccupancyStartTime, e); + } + + try + { + FeatureDisabledTime = DateTime.Parse(Config.OccupancyEndTime); + + if (FeatureDisabledTime != null) + { + Debug.Console(1, this, "Disabled Time: {0}", FeatureDisabledTime.ToString()); + } + else + Debug.Console(1, this, "Unable to parse {0} to DateTime", Config.OccupancyEndTime); + } + catch (Exception e) + { + Debug.Console(1, this, "Unable to parse a DateTime config value \n Error: {1}", e); + } + + AddEnableEventToGroup(); + + AddDisableEventToGroup(); + + FeatureEnabled = CheckIfFeatureShouldBeEnabled(); + + FeatureEventGroup.UserGroupCallBack += new ScheduledEventGroup.UserEventGroupCallBack(FeatureEventGroup_UserGroupCallBack); + + FeatureEventGroup.EnableAllEvents(); + } + else + Debug.Console(1, this, "Unable to get room from Device Manager with key: {0}", Config.RoomKey); + + return base.CustomActivate(); + } + + void FeatureEventGroup_UserGroupCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + if (SchEvent.Name == FeatureEnableEventName) + { + if (Config.EnableRoomOnWhenOccupied) + FeatureEnabled = true; + + Debug.Console(1, this, "*****Feature Enabled.*****"); + } + else if (SchEvent.Name == FeatureDisableEventName) + { + FeatureEnabled = false; + + Debug.Console(1, this, "*****Feature Disabled.*****"); + } + } + } + + /// + /// Checks if the feature should be currently enabled. Used on startup if processor starts after start time but before end time + /// + /// + bool CheckIfFeatureShouldBeEnabled() + { + bool enabled = false; + + if(Config.EnableRoomOnWhenOccupied) + { + if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0 && FeatureDisabledTime.CompareTo(DateTime.Now) >= 0) + { + enabled = true; + } + } + return enabled; + } + + /// + /// Respond to Occupancy status event + /// + /// + /// + void RoomIsOccupiedFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + if(e.BoolValue) + { + // Occupancy detected + + if (FeatureEnabled) + { + // Check room power state first + if(!Room.OnFeedback.BoolValue) + Room.PowerOnToDefaultOrLastSource(); + } + } + } + + void CreateEvent(ScheduledEvent schEvent, string name) + { + Debug.Console(1, this, "Adding Event: '{0}'", name); + // Create the event + if (schEvent == null) + schEvent = new ScheduledEvent(name, FeatureEventGroup); + + // Set up its initial properties + + schEvent.Acknowledgeable = true; + + schEvent.Persistent = true; + + schEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); + + // Set config driven properties + + if (schEvent.Name == FeatureEnableEventName) + { + schEvent.Description = "Enables the RoomOnToDefaultSourceWhenOccupiedFeature"; + + // Check to make sure the date for this event is in the future + if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0) + schEvent.DateAndTime.SetAbsoluteEventTime(FeatureEnabledTime.AddDays(1)); + else + schEvent.DateAndTime.SetAbsoluteEventTime(FeatureEnabledTime); + + Debug.Console(1, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); + + CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureEnabledTime, FeatureDisabledTime); + + schEvent.Recurrence.Weekly((ScheduledEventCommon.eWeekDays)CalculateDaysEnum()); + } + else if (schEvent.Name == FeatureDisableEventName) + { + schEvent.Description = "Disables the RoomOnToDefaultSourceWhenOccupiedFeature"; + + // Check to make sure the date for this event is in the future + if (DateTime.Now.CompareTo(FeatureDisabledTime) > 0) + schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime.AddDays(1)); + else + schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime); + + Debug.Console(1, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); + + CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureDisabledTime, FeatureEnabledTime); + + schEvent.Recurrence.Daily(); + } + } + + void CalculateAndSetAcknowledgeExpirationTimeout(ScheduledEvent schEvent, DateTime time1, DateTime time2) + { + Debug.Console(1, this, "time1.Hour = {0}", time1.Hour); + Debug.Console(1, this, "time2.Hour = {0}", time2.Hour); + Debug.Console(1, this, "time1.Minute = {0}", time1.Minute); + Debug.Console(1, this, "time2.Minute = {0}", time2.Minute); + + // Calculate the Acknowledge Expiration timer to be the time between the enable and dispable events, less one minute + var ackHours = time2.Hour - time1.Hour; + if(ackHours < 0) + ackHours = ackHours + 24; + var ackMinutes = time2.Minute - time1.Minute; + + Debug.Console(1, this, "ackHours = {0}, ackMinutes = {1}", ackHours, ackMinutes); + + var ackTotalMinutes = ((ackHours * 60) + ackMinutes) - 1; + + var ackExpHour = ackTotalMinutes / 60; + var ackExpMinutes = ackTotalMinutes % 60; + + Debug.Console(1, this, "Acknowledge Expiration Timeout: {0} hours, {1} minutes", ackExpHour, ackExpMinutes); + + schEvent.AcknowledgeExpirationTimeout.Hour = (ushort)(ackHours); + schEvent.AcknowledgeExpirationTimeout.Minute = (ushort)(ackExpMinutes); + } + + /// + /// Adds the Enable event to the local event group and sets its properties based on config + /// + void AddEnableEventToGroup() + { + if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureEnableEventName)) + { + CreateEvent(FeatureEnableEvent, FeatureEnableEventName); + } + else + { + FeatureEnableEvent = FeatureEventGroup.ScheduledEvents[FeatureEnableEventName]; + Debug.Console(1, this, "Enable event already found in group"); + + // TODO: Check config times and days against DateAndTime of existing event. If different, may need to remove event and recreate? + + } + + } + + /// + /// Adds the Enable event to the local event group and sets its properties based on config + /// + void AddDisableEventToGroup() + { + if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureDisableEventName)) + { + CreateEvent(FeatureDisableEvent, FeatureDisableEventName); + } + else + { + FeatureDisableEvent = FeatureEventGroup.ScheduledEvents[FeatureDisableEventName]; + Debug.Console(1, this, "Disable event already found in group"); + + // TODO: Check config times and days against DateAndTime of existing event. If different, may need to remove event and recreate? + } + + } + + + /// + /// Calculates the correct enum value for the event recurrence based on the config values + /// + /// + ScheduledEventCommon.eWeekDays CalculateDaysEnum() + { + ScheduledEventCommon.eWeekDays value = new ScheduledEventCommon.eWeekDays(); + + if (Config.EnableSunday) + value = value + (int)ScheduledEventCommon.eWeekDays.Sunday; + if (Config.EnableMonday) + value = value + (int)ScheduledEventCommon.eWeekDays.Monday; + if (Config.EnableTuesday) + value = value + (int)ScheduledEventCommon.eWeekDays.Tuesday; + if (Config.EnableWednesday) + value = value + (int)ScheduledEventCommon.eWeekDays.Wednesday; + if (Config.EnableThursday) + value = value + (int)ScheduledEventCommon.eWeekDays.Thursday; + if (Config.EnableFriday) + value = value + (int)ScheduledEventCommon.eWeekDays.Friday; + if (Config.EnableSaturday) + value = value + (int)ScheduledEventCommon.eWeekDays.Saturday; + + return value; + } + + /// + /// Callback for event that enables feature + /// + /// + /// + void FeatureEnableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + if(Config.EnableRoomOnWhenOccupied) + FeatureEnabled = true; + + Debug.Console(1, this, "RoomOnToDefaultSourceWhenOccupied Feature Enabled."); + } + } + + /// + /// Callback for event that enables feature + /// + /// + /// + void FeatureDisableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + FeatureEnabled = false; + + Debug.Console(1, this, "RoomOnToDefaultSourceWhenOccupied Feature Disabled."); + } + } + } + + public class RoomOnToDefaultSourceWhenOccupiedConfig + { + [JsonProperty("roomKey")] + public string RoomKey { get; set; } + + [JsonProperty("enableRoomOnWhenOccupied")] + public bool EnableRoomOnWhenOccupied { get; set; } + + [JsonProperty("occupancyStartTime")] + public string OccupancyStartTime { get; set; } + + [JsonProperty("occupancyEndTime")] + public string OccupancyEndTime { get; set; } + + [JsonProperty("enableSunday")] + public bool EnableSunday { get; set; } + + [JsonProperty("enableMonday")] + public bool EnableMonday { get; set; } + + [JsonProperty("enableTuesday")] + public bool EnableTuesday { get; set; } + + [JsonProperty("enableWednesday")] + public bool EnableWednesday { get; set; } + + [JsonProperty("enableThursday")] + public bool EnableThursday { get; set; } + + [JsonProperty("enableFriday")] + public bool EnableFriday { get; set; } + + [JsonProperty("enableSaturday")] + public bool EnableSaturday { get; set; } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs index 5912c6d1..35a23de0 100644 --- a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs @@ -324,36 +324,6 @@ namespace PepperDash.Essentials.Room.Config [JsonProperty("timoutMinutes")] public int TimoutMinutes { get; set; } - - [JsonProperty("enableRoomOnWhenOccupied")] - public bool EnableRoomOnWhenOccupied { get; set; } - - [JsonProperty("occupancyStartTime")] - public string OccupancyStartTime { get; set; } - - [JsonProperty("occupancyEndTime")] - public string OccupancyEndTime { get; set; } - - [JsonProperty("enableSunday")] - public bool EnableSunday { get; set; } - - [JsonProperty("enableMonday")] - public bool EnableMonday { get; set; } - - [JsonProperty("enableTuesday")] - public bool EnableTuesday { get; set; } - - [JsonProperty("enableWednesday")] - public bool EnableWednesday { get; set; } - - [JsonProperty("enableThursday")] - public bool EnableThursday { get; set; } - - [JsonProperty("enableFriday")] - public bool EnableFriday { get; set; } - - [JsonProperty("enableSaturday")] - public bool EnableSaturday { get; set; } } public class EssentialsRoomTechConfig diff --git a/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs b/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs index 736dcefe..42876694 100644 --- a/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs +++ b/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Crestron.SimplSharp; +using Crestron.SimplSharp; +using Crestron.SimplSharp.Scheduler; + using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Devices.Common.Occupancy; @@ -60,22 +62,6 @@ namespace PepperDash.Essentials /// Seconds after vacancy detected until prompt is displayed /// protected int RoomVacancyShutdownPromptSeconds; - - /// - /// The time of day at which the room occupancy power on feature should be enabled - /// - protected DateTime RoomOccpuancyPowerOnStart; - - /// - /// The time of day at which the room occupancy power on feature should be disabled - /// - protected DateTime RoomOccupancyPowerOnEnd; - - /// - /// Should the room power on to the default source if occupied between the start and end times - /// - protected bool RoomOccupancyPowerOnIsEnabled; - /// /// @@ -239,16 +225,6 @@ namespace PepperDash.Essentials Debug.Console(1, this, "Notice: Occupancy Detected"); // Reset the timer when the room is occupied RoomVacancyShutdownTimer.Cancel(); - - if(RoomOccupancyPowerOnIsEnabled) - { - var currentTime = DateTime.Now.TimeOfDay; - - if (currentTime.CompareTo(RoomOccpuancyPowerOnStart.TimeOfDay) > 0 && RoomOccupancyPowerOnEnd.TimeOfDay.CompareTo(currentTime) > 0) - { - PowerOnToDefaultOrLastSource(); - } - } } } @@ -257,7 +233,7 @@ namespace PepperDash.Essentials /// void SetUpOccupancyRoomOnEventsInScheduler() { - + } /// diff --git a/essentials-framework b/essentials-framework index c9244a5d..25695dc0 160000 --- a/essentials-framework +++ b/essentials-framework @@ -1 +1 @@ -Subproject commit c9244a5d64b7ae915cba9536e2ef64f9abba2520 +Subproject commit 25695dc0d26fdf36b94df67406f62e14f55dc414