diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs index dd23d85a..287a9ba1 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class IChannelExtensions { - public static void LinkActions(this IChannel dev, CotijaSystemController controller) + public static void LinkActions(this IChannel dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -22,7 +22,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "exit", new PressAndHoldAction(dev.Exit)); } - public static void UnlinkActions(this IChannel dev, CotijaSystemController controller) + public static void UnlinkActions(this IChannel dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IColorExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IColorExtensions.cs index 21296550..9912433d 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IColorExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IColorExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class IColorExtensions { - public static void LinkActions(this IColor dev, CotijaSystemController controller) + public static void LinkActions(this IColor dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -20,7 +20,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "blue", new PressAndHoldAction(dev.Blue)); } - public static void UnlinkActions(this IColor dev, CotijaSystemController controller) + public static void UnlinkActions(this IColor dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDPadExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDPadExtensions.cs index 3652ba68..b094dfe8 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDPadExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDPadExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class IDPadExtensions { - public static void LinkActions(this IDPad dev, CotijaSystemController controller) + public static void LinkActions(this IDPad dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -23,7 +23,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "exit", new PressAndHoldAction(dev.Exit)); } - public static void UnlinkActions(this IDPad dev, CotijaSystemController controller) + public static void UnlinkActions(this IDPad dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDvrExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDvrExtensions.cs index 99aa1103..d4a9570b 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDvrExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDvrExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class IDvrExtensions { - public static void LinkActions(this IDvr dev, CotijaSystemController controller) + public static void LinkActions(this IDvr dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -18,7 +18,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "record", new PressAndHoldAction(dev.Record)); } - public static void UnlinkActions(this IDvr dev, CotijaSystemController controller) + public static void UnlinkActions(this IDvr dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs index 376ed57b..2b61d775 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class INumericExtensions { - public static void LinkActions(this INumericKeypad dev, CotijaSystemController controller) + public static void LinkActions(this INumericKeypad dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -29,7 +29,7 @@ namespace PepperDash.Essentials.Room.Cotija // Deal with the Accessory functions on the numpad later } - public static void UnlinkActions(this INumericKeypad dev, CotijaSystemController controller) + public static void UnlinkActions(this INumericKeypad dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs index 732d2740..515c2c7f 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class IPowerExtensions { - public static void LinkActions(this IPower dev, CotijaSystemController controller) + public static void LinkActions(this IPower dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -19,7 +19,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "powerToggle", new Action(dev.PowerToggle)); } - public static void UnlinkActions(this IPower dev, CotijaSystemController controller) + public static void UnlinkActions(this IPower dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs index 99198fa6..2ea60545 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class ISetTopBoxControlsExtensions { - public static void LinkActions(this ISetTopBoxControls dev, CotijaSystemController controller) + public static void LinkActions(this ISetTopBoxControls dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -18,7 +18,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "replay", new PressAndHoldAction(dev.Replay)); } - public static void UnlinkActions(this ISetTopBoxControls dev, CotijaSystemController controller) + public static void UnlinkActions(this ISetTopBoxControls dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs index 9463d95f..f34fb9e3 100644 --- a/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs @@ -6,11 +6,11 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; using PepperDash.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public static class ITransportExtensions { - public static void LinkActions(this ITransport dev, CotijaSystemController controller) + public static void LinkActions(this ITransport dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); @@ -24,7 +24,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "record", new PressAndHoldAction(dev.Record)); } - public static void UnlinkActions(this ITransport dev, CotijaSystemController controller) + public static void UnlinkActions(this ITransport dev, MobileControlSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); diff --git a/PepperDashEssentials/AppServer/Interfaces.cs b/PepperDashEssentials/AppServer/Interfaces.cs index df651ef8..8a1c20c8 100644 --- a/PepperDashEssentials/AppServer/Interfaces.cs +++ b/PepperDashEssentials/AppServer/Interfaces.cs @@ -6,7 +6,7 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { /// /// Represents a room whose configuration is derived from runtime data, @@ -19,42 +19,3 @@ namespace PepperDash.Essentials.Room.Cotija } } -namespace PepperDash.Essentials -{ - /// - /// For rooms with a single presentation source, change event - /// - public interface IHasCurrentSourceInfoChange - { - string CurrentSourceInfoKey { get; } - SourceListItem CurrentSourceInfo { get; } - event SourceInfoChangeHandler CurrentSingleSourceChange; - } - - - /// - /// For rooms with routing - /// - public interface IRunRouteAction - { - void RunRouteAction(string routeKey); - - void RunRouteAction(string routeKey, Action successCallback); - } - - /// - /// For rooms that default presentation only routing - /// - public interface IRunDefaultPresentRoute - { - bool RunDefaultPresentRoute(); - } - - /// - /// For rooms that have default presentation and calling routes - /// - public interface IRunDefaultCallRoute : IRunDefaultPresentRoute - { - bool RunDefaultCallRoute(); - } -} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Messengers/AudioCodecBaseMessenger.cs b/PepperDashEssentials/AppServer/Messengers/AudioCodecBaseMessenger.cs index b37b80da..3d74b132 100644 --- a/PepperDashEssentials/AppServer/Messengers/AudioCodecBaseMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/AudioCodecBaseMessenger.cs @@ -39,7 +39,7 @@ namespace PepperDash.Essentials.AppServer.Messengers } - protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + protected override void CustomRegisterWithAppServer(MobileControlSystemController appServerController) { appServerController.AddAction(MessagePath + "/fullStatus", new Action(SendAtcFullMessageObject)); appServerController.AddAction(MessagePath + "/dial", new Action(s => Codec.Dial(s))); diff --git a/PepperDashEssentials/AppServer/Messengers/ConfigMessenger.cs b/PepperDashEssentials/AppServer/Messengers/ConfigMessenger.cs index 1e3cfa12..ae05c118 100644 --- a/PepperDashEssentials/AppServer/Messengers/ConfigMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/ConfigMessenger.cs @@ -27,7 +27,7 @@ namespace PepperDash.Essentials.AppServer.Messengers PostUpdateStatus(e.UpdateStatus.ToString()); } - protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + protected override void CustomRegisterWithAppServer(MobileControlSystemController appServerController) { appServerController.AddAction(MessagePath + "/updateConfig", new Action(s => GetConfigFile(s))); } diff --git a/PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs b/PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs index 72e2f8d3..eee94350 100644 --- a/PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs @@ -127,7 +127,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + protected override void CustomRegisterWithAppServer(MobileControlSystemController appServerController) { //EISC.SetStringSigAction(SCurrentDialString, s => PostStatusMessage(new { currentDialString = s })); diff --git a/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs b/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs index 7fb53f96..e3de11d0 100644 --- a/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs @@ -253,7 +253,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + protected override void CustomRegisterWithAppServer(MobileControlSystemController appServerController) { var asc = appServerController; EISC.SetStringSigAction(SHookState, s => diff --git a/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs b/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs index f9833afb..d4967eee 100644 --- a/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs +++ b/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs @@ -24,7 +24,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public CotijaSystemController AppServerController { get; private set; } + public MobileControlSystemController AppServerController { get; private set; } public string MessagePath { get; private set; } @@ -47,7 +47,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// Registers this messenger with appserver controller /// /// - public void RegisterWithAppServer(CotijaSystemController appServerController) + public void RegisterWithAppServer(MobileControlSystemController appServerController) { if (appServerController == null) throw new ArgumentNullException("appServerController"); @@ -60,7 +60,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// Implemented in extending classes. Wire up API calls and feedback here /// /// - abstract protected void CustomRegisterWithAppServer(CotijaSystemController appServerController); + abstract protected void CustomRegisterWithAppServer(MobileControlSystemController appServerController); /// /// Helper for posting status message diff --git a/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs b/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs index 12107ff5..080fbd78 100644 --- a/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs @@ -86,7 +86,7 @@ namespace PepperDash.Essentials.AppServer.Messengers }); } - protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + protected override void CustomRegisterWithAppServer(MobileControlSystemController appServerController) { AppServerController.AddAction(MessagePath + "/fullStatus", new Action(SendFullStatusMessage)); } diff --git a/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs b/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs index 5c1c59b7..29c80085 100644 --- a/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs @@ -160,7 +160,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// Called from base's RegisterWithAppServer method /// /// - protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + protected override void CustomRegisterWithAppServer(MobileControlSystemController appServerController) { appServerController.AddAction("/device/videoCodec/isReady", new Action(SendIsReady)); appServerController.AddAction("/device/videoCodec/fullStatus", new Action(SendVtcFullMessageObject)); diff --git a/PepperDashEssentials/AppServer/CotijaConfig.cs b/PepperDashEssentials/AppServer/MobileControlConfig.cs similarity index 84% rename from PepperDashEssentials/AppServer/CotijaConfig.cs rename to PepperDashEssentials/AppServer/MobileControlConfig.cs index 22196956..6b775a0a 100644 --- a/PepperDashEssentials/AppServer/CotijaConfig.cs +++ b/PepperDashEssentials/AppServer/MobileControlConfig.cs @@ -13,7 +13,7 @@ namespace PepperDash.Essentials /// /// /// - public class CotijaConfig + public class MobileControlConfig { [JsonProperty("serverUrl")] public string ServerUrl { get; set; } @@ -25,7 +25,7 @@ namespace PepperDash.Essentials /// /// /// - public class CotijaDdvc01RoomBridgePropertiesConfig + public class MobileControlDdvc01RoomBridgePropertiesConfig { [JsonProperty("eiscId")] public string EiscId { get; set; } diff --git a/PepperDashEssentials/AppServer/CotijaDdvc01DeviceBridge.cs b/PepperDashEssentials/AppServer/MobileControlDdvc01DeviceBridge.cs similarity index 91% rename from PepperDashEssentials/AppServer/CotijaDdvc01DeviceBridge.cs rename to PepperDashEssentials/AppServer/MobileControlDdvc01DeviceBridge.cs index 51c0bb5b..49a75263 100644 --- a/PepperDashEssentials/AppServer/CotijaDdvc01DeviceBridge.cs +++ b/PepperDashEssentials/AppServer/MobileControlDdvc01DeviceBridge.cs @@ -8,19 +8,19 @@ using Crestron.SimplSharpPro.EthernetCommunication; using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { /// /// Represents a generic device connection through to and EISC for DDVC01 /// - public class CotijaDdvc01DeviceBridge : Device, IChannel, INumericKeypad + public class MobileControlDdvc01DeviceBridge : Device, IChannel, INumericKeypad { /// /// EISC used to talk to Simpl /// ThreeSeriesTcpIpEthernetIntersystemCommunications EISC; - public CotijaDdvc01DeviceBridge(string key, string name, ThreeSeriesTcpIpEthernetIntersystemCommunications eisc) + public MobileControlDdvc01DeviceBridge(string key, string name, ThreeSeriesTcpIpEthernetIntersystemCommunications eisc) : base(key, name) { EISC = eisc; diff --git a/PepperDashEssentials/AppServer/CotijaSystemController.cs b/PepperDashEssentials/AppServer/MobileControlSystemController.cs similarity index 95% rename from PepperDashEssentials/AppServer/CotijaSystemController.cs rename to PepperDashEssentials/AppServer/MobileControlSystemController.cs index 1e74efe0..08afb464 100644 --- a/PepperDashEssentials/AppServer/CotijaSystemController.cs +++ b/PepperDashEssentials/AppServer/MobileControlSystemController.cs @@ -1,864 +1,864 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharp.Reflection; -using Crestron.SimplSharpPro.CrestronThread; -using Crestron.SimplSharp.CrestronWebSocketClient; -using Crestron.SimplSharpPro; -using Crestron.SimplSharp.Net.Http; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Room.Cotija; -using PepperDash.Essentials.AppServer.Messengers; - -namespace PepperDash.Essentials -{ - public class CotijaSystemController : Device - { - WebSocketClient WSClient; - - //bool LinkUp; - - /// - /// Prevents post operations from stomping on each other and getting lost - /// - CEvent PostLockEvent = new CEvent(true, true); - - CEvent RegisterLockEvent = new CEvent(true, true); - - public CotijaConfig Config { get; private set; } - - Dictionary ActionDictionary = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - Dictionary PushedActions = new Dictionary(); - - public ConfigMessenger ConfigMessenger { get; private set; } - - CTimer ServerHeartbeatCheckTimer; - - long ServerHeartbeatInterval = 20000; - - CTimer ServerReconnectTimer; - - long ServerReconnectInterval = 5000; - - DateTime LastAckMessage; - - public string SystemUuid; - - List RoomBridges = new List(); - - long ButtonHeartbeatInterval = 1000; - - /// - /// Used for tracking HTTP debugging - /// - bool HttpDebugEnabled; - - /// - /// - /// - /// - /// - /// - public CotijaSystemController(string key, string name, CotijaConfig config) : base(key, name) - { - Config = config; - - SystemUuid = ConfigReader.ConfigObject.SystemUuid; - - Debug.Console(0, this, "Mobile UI controller initializing for server:{0}", config.ServerUrl); - - CrestronConsole.AddNewConsoleCommand(AuthorizeSystem, - "mobileauth", "Authorizes system to talk to cotija server", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => ShowInfo(), - "mobileinfo", "Shows information for current mobile control session", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => { - s = s.Trim(); - if(!string.IsNullOrEmpty(s)) - { - HttpDebugEnabled = (s.Trim() != "0"); - } - CrestronConsole.ConsoleCommandResponse("HTTP Debug {0}", HttpDebugEnabled ? "Enabled" : "Disabled"); - }, - "mobilehttpdebug", "1 enables more verbose HTTP response debugging", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(TestHttpRequest, - "mobilehttprequest", "Tests an HTTP get to URL given", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(PrintActionDictionaryPaths, "mobileshowactionpaths", - "Prints the paths in the Action Dictionary", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => ConnectWebsocketClient(), "mobileconnect", - "Forces connect of websocket", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CleanUpWebsocketClient(), "mobiledisco", - "Disconnects websocket", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => ParseStreamRx(s), "mobilesimulateaction", "Simulates a message from the server", ConsoleAccessLevelEnum.AccessOperator); - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); - - // Config Messenger - var cmKey = Key + "-config"; - ConfigMessenger = new ConfigMessenger(cmKey, "/config"); - ConfigMessenger.RegisterWithAppServer(this); - } - - /// - /// If config rooms is empty or null then go - /// - /// - public override bool CustomActivate() - { - if (ConfigReader.ConfigObject.Rooms == null || ConfigReader.ConfigObject.Rooms.Count == 0) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Config contains no rooms. Registering with Server."); - RegisterSystemToServer(); - } - - return base.CustomActivate(); - } - - /// - /// - /// - /// - void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs args) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Ethernet status change, port {0}: {1}", - args.EthernetAdapter, args.EthernetEventType); - - if (args.EthernetEventType == eEthernetEventType.LinkDown && WSClient != null && args.EthernetAdapter == WSClient.EthernetAdapter) - { - CleanUpWebsocketClient(); - } - } - - /// - /// Sends message to server to indicate the system is shutting down - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping - && WSClient != null - && WSClient.Connected) - { - CleanUpWebsocketClient(); - } - } - - public void PrintActionDictionaryPaths(object o) - { - Debug.Console(0, this, "ActionDictionary Contents:"); - - foreach (var item in ActionDictionary) - { - Debug.Console(0, this, "{0}", item.Key); - } - } - - /// - /// Adds an action to the dictionary - /// - /// The path of the API command - /// The action to be triggered by the commmand - public void AddAction(string key, object action) - { - if (!ActionDictionary.ContainsKey(key)) - { - ActionDictionary.Add(key, action); - } - else - { - Debug.Console(1, this, "Cannot add action with key '{0}' because key already exists in ActionDictionary.", key); - } - } - - /// - /// Removes an action from the dictionary - /// - /// - public void RemoveAction(string key) - { - if (ActionDictionary.ContainsKey(key)) - ActionDictionary.Remove(key); - } - - /// - /// - /// - /// - public void AddBridge(CotijaBridgeBase bridge) - { - RoomBridges.Add(bridge); - var b = bridge as IDelayedConfiguration; - if (b != null) - { - Debug.Console(0, this, "Adding room bridge with delayed configuration"); - b.ConfigurationIsReady += new EventHandler(bridge_ConfigurationIsReady); - } - else - { - Debug.Console(0, this, "Adding room bridge and sending configuration"); - //SystemUuid = ConfigReader.ConfigObject.SystemUuid; - RegisterSystemToServer(); - } - } - - /// - /// - /// - /// - /// - void bridge_ConfigurationIsReady(object sender, EventArgs e) - { - Debug.Console(1, this, "Bridge ready. Registering"); - //SystemUuid = ConfigReader.ConfigObject.SystemUuid; - // send the configuration object to the server - RegisterSystemToServer(); - } - - /// - /// - /// - /// - void ReconnectToServerTimerCallback(object o) - { - RegisterSystemToServer(); - } - - /// - /// Verifies system connection with servers - /// - /// - void AuthorizeSystem(string code) - { - //SystemUuid = ConfigReader.ConfigObject.SystemUuid; - - if (string.IsNullOrEmpty(SystemUuid)) - { - CrestronConsole.ConsoleCommandResponse("System does not have a UUID. Please ensure proper portal-format configuration is loaded and restart."); - return; - } - - if (string.IsNullOrEmpty(code)) - { - CrestronConsole.ConsoleCommandResponse("Please enter a user code to authorize a system"); - return; - } - - var req = new HttpClientRequest(); - string url = string.Format("http://{0}/api/system/grantcode/{1}/{2}", Config.ServerUrl, code, SystemUuid); - Debug.Console(0, this, "Authorizing to: {0}", url); - - if (string.IsNullOrEmpty(Config.ServerUrl)) - { - CrestronConsole.ConsoleCommandResponse("Config URL address is not set. Check portal configuration"); - return; - } - try - { - req.Url.Parse(url); - new HttpClient().DispatchAsync(req, (r, e) => - { - CheckHttpDebug(r, e); - if (e == HTTP_CALLBACK_ERROR.COMPLETED) - { - if (r.Code == 200) - { - Debug.Console(0, "System authorized, sending config."); -#warning This registration may need to wait for config ready. Maybe. - RegisterSystemToServer(); - } - else if (r.Code == 404) - { - if (r.ContentString.Contains("codeNotFound")) - { - Debug.Console(0, "Authorization failed, code not found for system UUID {0}", SystemUuid); - } - else if (r.ContentString.Contains("uuidNotFound")) - { - Debug.Console(0, "Authorization failed, uuid {0} not found. Check Essentials configuration is correct", - SystemUuid); - } - } - } - else - Debug.Console(0, this, "Error {0} in authorizing system", e); - }); - } - catch (Exception e) - { - Debug.Console(0, this, "Error in authorizing: {0}", e); - } - } - - /// - /// Dumps info in response to console command. - /// - void ShowInfo() - { - var url = Config != null ? Config.ServerUrl : "No config"; - string name; - string code; - if (RoomBridges != null && RoomBridges.Count > 0) - { - name = RoomBridges[0].RoomName; - code = RoomBridges[0].UserCode; - } - else - { - name = "No config"; - code = "Not available"; - } - var conn = WSClient == null ? "No client" : (WSClient.Connected ? "Yes" : "No"); - var secSinceLastAck = DateTime.Now - LastAckMessage; - - - CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: - Server address: {0} - System Name: {1} - System URL: {2} - System UUID: {3} - System User code: {4} - Connected?: {5} - Seconds Since Last Ack: {6}" - , url, name, ConfigReader.ConfigObject.SystemUrl, SystemUuid, - code, conn, secSinceLastAck.Seconds); - } - - /// - /// Registers the room with the server - /// - /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" - void RegisterSystemToServer() - { - ConnectWebsocketClient(); - } - - /// - /// Connects the Websocket Client - /// - /// - void ConnectWebsocketClient() - { - - Debug.Console(1, this, "Initializing Stream client to server."); - - if (WSClient != null) - { - Debug.Console(1, this, "Cleaning up previous socket"); - CleanUpWebsocketClient(); - } - - WSClient = new WebSocketClient(); - WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); - WSClient.ConnectionCallBack = Websocket_ConnectCallback; - WSClient.ConnectAsync(); - } - - /// - /// - /// - /// - /// - int Websocket_ConnectCallback(WebSocketClient.WEBSOCKET_RESULT_CODES code) - { - if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) - { - StopServerReconnectTimer(); - Debug.Console(1, this, "Websocket connected"); - WSClient.DisconnectCallBack = Websocket_DisconnectCallback; - WSClient.SendCallBack = Websocket_SendCallback; - WSClient.ReceiveCallBack = Websocket_ReceiveCallback; - WSClient.ReceiveAsync(); - SendMessageObjectToServer(new - { - type = "hello" - }); - } - else - { - if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_HTTP_HANDSHAKE_TOKEN_ERROR) - { - // This is the case when app server is running behind a websever and app server is down - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failed. Check that app server is running behind web server"); - } - else if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SOCKET_CONNECTION_FAILED) - { - // this will be the case when webserver is unreachable - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failed"); - } - else - { - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failure: {0}", code); - } - StartServerReconnectTimer(); - } - - return 0; - } - - /// - /// After a "hello" from the server, sends config and stuff - /// - void SendInitialMessage() - { - Debug.Console(1, this, "Sending initial join message"); - var confObject = ConfigReader.ConfigObject; - confObject.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; - var version = Assembly.GetExecutingAssembly().GetName().Version; - confObject.Info.RuntimeInfo.AssemblyVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); - - var msg = new - { - type = "join", - content = new - { - config = confObject - } - }; - SendMessageObjectToServer(msg); - } - - /// - /// Sends any object type to server - /// - /// - public void SendMessageObjectToServer(object o) - { - SendMessageToServer(JObject.FromObject(o)); - } - - /// - /// Sends a message to the server from a room - /// - /// room from which the message originates - /// object to be serialized and sent in post body - public void SendMessageToServer(JObject o) - { - if (WSClient != null && WSClient.Connected) - { - string message = JsonConvert.SerializeObject(o, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - - if (!message.Contains("/system/heartbeat")) - Debug.Console(1, this, "Message TX: {0}", message); - //else - // Debug.Console(1, this, "TX messages contains /system/heartbeat"); - - var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); - var result = WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - if (result != WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) - { - Debug.Console(1, this, "Socket send result error: {0}", result); - } - } - else if (WSClient == null) - { - Debug.Console(1, this, "Cannot send. Not connected."); - } - } - - /// - /// Disconnects the SSE Client and stops the heartbeat timer - /// - /// - void CleanUpWebsocketClient() - { - Debug.Console(1, this, "Disconnecting websocket"); - if (WSClient != null) - { - WSClient.SendCallBack = null; - WSClient.ReceiveCallBack = null; - WSClient.ConnectionCallBack = null; - WSClient.DisconnectCallBack = null; - if (WSClient.Connected) - { - WSClient.Disconnect(); - } - WSClient = null; - } - } - - /// - /// - /// - /// - /// - void StartServerReconnectTimer() - { - StopServerReconnectTimer(); - ServerReconnectTimer = new CTimer(ReconnectToServerTimerCallback, ServerReconnectInterval); - Debug.Console(1, this, "Reconnect Timer Started."); - } - - /// - /// Does what it says - /// - void StopServerReconnectTimer() - { - if (ServerReconnectTimer != null) - { - ServerReconnectTimer.Stop(); - ServerReconnectTimer = null; - } - } - - /// - /// Executes when we don't get a heartbeat message in time. Triggers reconnect. - /// - /// For CTimer callback. Not used - void HeartbeatExpiredTimerCallback(object o) - { - Debug.Console(1, this, "Heartbeat Timer Expired."); - if (ServerHeartbeatCheckTimer != null) - { - ServerHeartbeatCheckTimer.Stop(); - ServerHeartbeatCheckTimer = null; - } - CleanUpWebsocketClient(); - StartServerReconnectTimer(); - } - - /// - /// - /// - /// - /// - void ResetOrStartHearbeatTimer() - { - if (ServerHeartbeatCheckTimer == null) - { - ServerHeartbeatCheckTimer = new CTimer(HeartbeatExpiredTimerCallback, null, ServerHeartbeatInterval, ServerHeartbeatInterval); - Debug.Console(1, this, "Heartbeat Timer Started."); - } - else - { - ServerHeartbeatCheckTimer.Reset(ServerHeartbeatInterval, ServerHeartbeatInterval); - } - } - - /// - /// Waits two and goes again - /// - void ReconnectStreamClient() - { - new CTimer(o => ConnectWebsocketClient(), 2000); - } - - /// - /// - /// - /// - /// - int Websocket_DisconnectCallback(WebSocketClient.WEBSOCKET_RESULT_CODES code, object o) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Websocket disconnected with code: {0}", code); - - if (ServerHeartbeatCheckTimer != null) - ServerHeartbeatCheckTimer.Stop(); - // Start the reconnect timer - StartServerReconnectTimer(); - return 0; - } - - - /// - /// Resets reconnect timer and updates usercode - /// - /// - void HandleHeartBeat(JToken content) - { - SendMessageToServer(JObject.FromObject(new - { - type = "/system/heartbeatAck" - })); - - var code = content["userCode"]; - if(code != null) - { - foreach (var b in RoomBridges) - { - b.SetUserCode(code.Value()); - } - } - ResetOrStartHearbeatTimer(); - } - - /// - /// Outputs debug info when enabled - /// - /// - /// - /// - void CheckHttpDebug(HttpClientResponse r, HTTP_CALLBACK_ERROR e) - { - if (HttpDebugEnabled) - { - try - { - Debug.Console(0, this, "------ Begin HTTP Debug ---------------------------------------"); - if (r != null) - { - Debug.Console(0, this, "HTTP Response URL: {0}", r.ResponseUrl != null ? r.ResponseUrl.ToString() : "NONE"); - Debug.Console(0, this, "HTTP Response code: {0}", r.Code); - Debug.Console(0, this, "HTTP Response content: \r{0}", r.ContentString); - } - else - { - Debug.Console(0, this, "No HTTP response"); - } - Debug.Console(0, this, "HTTP Response 'error' {0}", e); - Debug.Console(0, this, "------ End HTTP Debug -----------------------------------------"); - } - catch (Exception ex) - { - Debug.Console(0, this, "HttpDebugError: {0}", ex); - } - } - } - - /// - /// - /// - /// - /// - /// - /// - int Websocket_ReceiveCallback(byte[] data, uint length, WebSocketClient.WEBSOCKET_PACKET_TYPES opcode, - WebSocketClient.WEBSOCKET_RESULT_CODES err) - { - if (opcode == WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME) - { - var rx = System.Text.Encoding.UTF8.GetString(data, 0, (int)length); - if (rx.Length > 0) - ParseStreamRx(rx); - WSClient.ReceiveAsync(); - } - - else if (opcode == WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__CLOSE) - { - Debug.Console(1, this, "Websocket disconnect received from remote"); - CleanUpWebsocketClient(); - } - else - { - Debug.Console(1, this, "websocket rx opcode/err {0}/{1}", opcode, err); - WSClient.ReceiveAsync(); - } - return 0; - } - - /// - /// Callback to catch possible errors in sending via the websocket - /// - /// - /// - int Websocket_SendCallback(Crestron.SimplSharp.CrestronWebSocketClient.WebSocketClient.WEBSOCKET_RESULT_CODES result) - { - if(result != WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SendCallback questionable result: {0}", result); - return 1; - } - - /// - /// - /// - /// - /// - void ParseStreamRx(string message) - { - if(string.IsNullOrEmpty(message)) - return; - - if (!message.Contains("/system/heartbeat")) - { - Debug.Console(1, this, "Message RX: {0}", message); - } - else - { - LastAckMessage = DateTime.Now; - } - - try - { - var messageObj = JObject.Parse(message); - - var type = messageObj["type"].Value(); - - if (type == "hello") - { - SendInitialMessage(); - ResetOrStartHearbeatTimer(); - } - else if (type == "/system/heartbeat") - { - HandleHeartBeat(messageObj["content"]); - } - else if (type == "raw") - { - var wrapper = messageObj["content"].ToObject(); - DeviceJsonApi.DoDeviceAction(wrapper); - } - else if (type == "close") - { - Debug.Console(1, this, "Received close message from server."); - // DisconnectWebsocketClient(); - - if (ServerHeartbeatCheckTimer != null) - ServerHeartbeatCheckTimer.Stop(); - } - else - { - // Check path against Action dictionary - if (ActionDictionary.ContainsKey(type)) - { - var action = ActionDictionary[type]; - - if (action is Action) - { - (action as Action)(); - } - else if (action is PressAndHoldAction) - { - var stateString = messageObj["content"]["state"].Value(); - - // Look for a button press event - if (!string.IsNullOrEmpty(stateString)) - { - switch (stateString) - { - case "true": - { - if (!PushedActions.ContainsKey(type)) - { - PushedActions.Add(type, new CTimer(o => - { - (action as PressAndHoldAction)(false); - PushedActions.Remove(type); - }, null, ButtonHeartbeatInterval, ButtonHeartbeatInterval)); - } - // Maybe add an else to reset the timer - break; - } - case "held": - { - if (PushedActions.ContainsKey(type)) - { - PushedActions[type].Reset(ButtonHeartbeatInterval, ButtonHeartbeatInterval); - } - return; - } - case "false": - { - if (PushedActions.ContainsKey(type)) - { - PushedActions[type].Stop(); - PushedActions.Remove(type); - } - break; - } - } - - (action as PressAndHoldAction)(stateString == "true"); - } - } - else if (action is Action) - { - var stateString = messageObj["content"]["state"].Value(); - - if (!string.IsNullOrEmpty(stateString)) - { - (action as Action)(stateString == "true"); - } - } - else if (action is Action) - { - (action as Action)(messageObj["content"]["value"].Value()); - } - else if (action is Action) - { - (action as Action)(messageObj["content"]["value"].Value()); - } - else if (action is Action) - { - (action as Action)(messageObj["content"] - .ToObject()); - } - } - else - { - Debug.Console(1, this, "-- Warning: Incoming message has no registered handler"); - } - } - } - catch (Exception err) - { - //Debug.Console(1, "SseMessageLengthBeforeFailureCount: {0}", SseMessageLengthBeforeFailureCount); - //SseMessageLengthBeforeFailureCount = 0; - Debug.Console(1, this, "Unable to parse message: {0}", err); - } - } - - void TestHttpRequest(string s) - { - { - s = s.Trim(); - if (string.IsNullOrEmpty(s)) - { - PrintTestHttpRequestUsage(); - return; - } - var tokens = s.Split(' '); - if (tokens.Length < 2) - { - CrestronConsole.ConsoleCommandResponse("Too few paramaters\r"); - PrintTestHttpRequestUsage(); - return; - } - - try - { - var url = tokens[1]; - if (tokens[0].ToLower() == "get") - { - var resp = new HttpClient().Get(url); - CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); - } - else if (tokens[0].ToLower() == "post") - { - var resp = new HttpClient().Post(url, new byte[] { }); - CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); - } - - else - { - CrestronConsole.ConsoleCommandResponse("Only get or post supported\r"); - PrintTestHttpRequestUsage(); - } - } - catch (HttpException e) - { - CrestronConsole.ConsoleCommandResponse("Exception in request:\r"); - CrestronConsole.ConsoleCommandResponse("Response URL: {0}\r", e.Response.ResponseUrl); - CrestronConsole.ConsoleCommandResponse("Response Error Code: {0}\r", e.Response.Code); - CrestronConsole.ConsoleCommandResponse("Response body: {0}\r", e.Response.ContentString); - } - - } - } - - void PrintTestHttpRequestUsage() - { - CrestronConsole.ConsoleCommandResponse("Usage: mobilehttprequest:N get/post url\r"); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp.Reflection; +using Crestron.SimplSharpPro.CrestronThread; +using Crestron.SimplSharp.CrestronWebSocketClient; +using Crestron.SimplSharpPro; +using Crestron.SimplSharp.Net.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Room.MobileControl; +using PepperDash.Essentials.AppServer.Messengers; + +namespace PepperDash.Essentials +{ + public class MobileControlSystemController : Device + { + WebSocketClient WSClient; + + //bool LinkUp; + + /// + /// Prevents post operations from stomping on each other and getting lost + /// + CEvent PostLockEvent = new CEvent(true, true); + + CEvent RegisterLockEvent = new CEvent(true, true); + + public MobileControlConfig Config { get; private set; } + + Dictionary ActionDictionary = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + Dictionary PushedActions = new Dictionary(); + + public ConfigMessenger ConfigMessenger { get; private set; } + + CTimer ServerHeartbeatCheckTimer; + + long ServerHeartbeatInterval = 20000; + + CTimer ServerReconnectTimer; + + long ServerReconnectInterval = 5000; + + DateTime LastAckMessage; + + public string SystemUuid; + + List RoomBridges = new List(); + + long ButtonHeartbeatInterval = 1000; + + /// + /// Used for tracking HTTP debugging + /// + bool HttpDebugEnabled; + + /// + /// + /// + /// + /// + /// + public MobileControlSystemController(string key, string name, MobileControlConfig config) : base(key, name) + { + Config = config; + + SystemUuid = ConfigReader.ConfigObject.SystemUuid; + + Debug.Console(0, this, "Mobile UI controller initializing for server:{0}", config.ServerUrl); + + CrestronConsole.AddNewConsoleCommand(AuthorizeSystem, + "mobileauth", "Authorizes system to talk to Mobile Control server", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => ShowInfo(), + "mobileinfo", "Shows information for current mobile control session", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => { + s = s.Trim(); + if(!string.IsNullOrEmpty(s)) + { + HttpDebugEnabled = (s.Trim() != "0"); + } + CrestronConsole.ConsoleCommandResponse("HTTP Debug {0}", HttpDebugEnabled ? "Enabled" : "Disabled"); + }, + "mobilehttpdebug", "1 enables more verbose HTTP response debugging", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(TestHttpRequest, + "mobilehttprequest", "Tests an HTTP get to URL given", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(PrintActionDictionaryPaths, "mobileshowactionpaths", + "Prints the paths in the Action Dictionary", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => ConnectWebsocketClient(), "mobileconnect", + "Forces connect of websocket", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CleanUpWebsocketClient(), "mobiledisco", + "Disconnects websocket", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(s => ParseStreamRx(s), "mobilesimulateaction", "Simulates a message from the server", ConsoleAccessLevelEnum.AccessOperator); + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + + // Config Messenger + var cmKey = Key + "-config"; + ConfigMessenger = new ConfigMessenger(cmKey, "/config"); + ConfigMessenger.RegisterWithAppServer(this); + } + + /// + /// If config rooms is empty or null then go + /// + /// + public override bool CustomActivate() + { + if (ConfigReader.ConfigObject.Rooms == null || ConfigReader.ConfigObject.Rooms.Count == 0) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Config contains no rooms. Registering with Server."); + RegisterSystemToServer(); + } + + return base.CustomActivate(); + } + + /// + /// + /// + /// + void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs args) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Ethernet status change, port {0}: {1}", + args.EthernetAdapter, args.EthernetEventType); + + if (args.EthernetEventType == eEthernetEventType.LinkDown && WSClient != null && args.EthernetAdapter == WSClient.EthernetAdapter) + { + CleanUpWebsocketClient(); + } + } + + /// + /// Sends message to server to indicate the system is shutting down + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping + && WSClient != null + && WSClient.Connected) + { + CleanUpWebsocketClient(); + } + } + + public void PrintActionDictionaryPaths(object o) + { + Debug.Console(0, this, "ActionDictionary Contents:"); + + foreach (var item in ActionDictionary) + { + Debug.Console(0, this, "{0}", item.Key); + } + } + + /// + /// Adds an action to the dictionary + /// + /// The path of the API command + /// The action to be triggered by the commmand + public void AddAction(string key, object action) + { + if (!ActionDictionary.ContainsKey(key)) + { + ActionDictionary.Add(key, action); + } + else + { + Debug.Console(1, this, "Cannot add action with key '{0}' because key already exists in ActionDictionary.", key); + } + } + + /// + /// Removes an action from the dictionary + /// + /// + public void RemoveAction(string key) + { + if (ActionDictionary.ContainsKey(key)) + ActionDictionary.Remove(key); + } + + /// + /// + /// + /// + public void AddBridge(MobileControlBridgeBase bridge) + { + RoomBridges.Add(bridge); + var b = bridge as IDelayedConfiguration; + if (b != null) + { + Debug.Console(0, this, "Adding room bridge with delayed configuration"); + b.ConfigurationIsReady += new EventHandler(bridge_ConfigurationIsReady); + } + else + { + Debug.Console(0, this, "Adding room bridge and sending configuration"); + //SystemUuid = ConfigReader.ConfigObject.SystemUuid; + RegisterSystemToServer(); + } + } + + /// + /// + /// + /// + /// + void bridge_ConfigurationIsReady(object sender, EventArgs e) + { + Debug.Console(1, this, "Bridge ready. Registering"); + //SystemUuid = ConfigReader.ConfigObject.SystemUuid; + // send the configuration object to the server + RegisterSystemToServer(); + } + + /// + /// + /// + /// + void ReconnectToServerTimerCallback(object o) + { + RegisterSystemToServer(); + } + + /// + /// Verifies system connection with servers + /// + /// + void AuthorizeSystem(string code) + { + //SystemUuid = ConfigReader.ConfigObject.SystemUuid; + + if (string.IsNullOrEmpty(SystemUuid)) + { + CrestronConsole.ConsoleCommandResponse("System does not have a UUID. Please ensure proper portal-format configuration is loaded and restart."); + return; + } + + if (string.IsNullOrEmpty(code)) + { + CrestronConsole.ConsoleCommandResponse("Please enter a user code to authorize a system"); + return; + } + + var req = new HttpClientRequest(); + string url = string.Format("http://{0}/api/system/grantcode/{1}/{2}", Config.ServerUrl, code, SystemUuid); + Debug.Console(0, this, "Authorizing to: {0}", url); + + if (string.IsNullOrEmpty(Config.ServerUrl)) + { + CrestronConsole.ConsoleCommandResponse("Config URL address is not set. Check portal configuration"); + return; + } + try + { + req.Url.Parse(url); + new HttpClient().DispatchAsync(req, (r, e) => + { + CheckHttpDebug(r, e); + if (e == HTTP_CALLBACK_ERROR.COMPLETED) + { + if (r.Code == 200) + { + Debug.Console(0, "System authorized, sending config."); +#warning This registration may need to wait for config ready. Maybe. + RegisterSystemToServer(); + } + else if (r.Code == 404) + { + if (r.ContentString.Contains("codeNotFound")) + { + Debug.Console(0, "Authorization failed, code not found for system UUID {0}", SystemUuid); + } + else if (r.ContentString.Contains("uuidNotFound")) + { + Debug.Console(0, "Authorization failed, uuid {0} not found. Check Essentials configuration is correct", + SystemUuid); + } + } + } + else + Debug.Console(0, this, "Error {0} in authorizing system", e); + }); + } + catch (Exception e) + { + Debug.Console(0, this, "Error in authorizing: {0}", e); + } + } + + /// + /// Dumps info in response to console command. + /// + void ShowInfo() + { + var url = Config != null ? Config.ServerUrl : "No config"; + string name; + string code; + if (RoomBridges != null && RoomBridges.Count > 0) + { + name = RoomBridges[0].RoomName; + code = RoomBridges[0].UserCode; + } + else + { + name = "No config"; + code = "Not available"; + } + var conn = WSClient == null ? "No client" : (WSClient.Connected ? "Yes" : "No"); + var secSinceLastAck = DateTime.Now - LastAckMessage; + + + CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: + Server address: {0} + System Name: {1} + System URL: {2} + System UUID: {3} + System User code: {4} + Connected?: {5} + Seconds Since Last Ack: {6}" + , url, name, ConfigReader.ConfigObject.SystemUrl, SystemUuid, + code, conn, secSinceLastAck.Seconds); + } + + /// + /// Registers the room with the server + /// + /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" + void RegisterSystemToServer() + { + ConnectWebsocketClient(); + } + + /// + /// Connects the Websocket Client + /// + /// + void ConnectWebsocketClient() + { + + Debug.Console(1, this, "Initializing Stream client to server."); + + if (WSClient != null) + { + Debug.Console(1, this, "Cleaning up previous socket"); + CleanUpWebsocketClient(); + } + + WSClient = new WebSocketClient(); + WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); + WSClient.ConnectionCallBack = Websocket_ConnectCallback; + WSClient.ConnectAsync(); + } + + /// + /// + /// + /// + /// + int Websocket_ConnectCallback(WebSocketClient.WEBSOCKET_RESULT_CODES code) + { + if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) + { + StopServerReconnectTimer(); + Debug.Console(1, this, "Websocket connected"); + WSClient.DisconnectCallBack = Websocket_DisconnectCallback; + WSClient.SendCallBack = Websocket_SendCallback; + WSClient.ReceiveCallBack = Websocket_ReceiveCallback; + WSClient.ReceiveAsync(); + SendMessageObjectToServer(new + { + type = "hello" + }); + } + else + { + if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_HTTP_HANDSHAKE_TOKEN_ERROR) + { + // This is the case when app server is running behind a websever and app server is down + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failed. Check that app server is running behind web server"); + } + else if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SOCKET_CONNECTION_FAILED) + { + // this will be the case when webserver is unreachable + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failed"); + } + else + { + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failure: {0}", code); + } + StartServerReconnectTimer(); + } + + return 0; + } + + /// + /// After a "hello" from the server, sends config and stuff + /// + void SendInitialMessage() + { + Debug.Console(1, this, "Sending initial join message"); + var confObject = ConfigReader.ConfigObject; + confObject.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; + var version = Assembly.GetExecutingAssembly().GetName().Version; + confObject.Info.RuntimeInfo.AssemblyVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); + + var msg = new + { + type = "join", + content = new + { + config = confObject + } + }; + SendMessageObjectToServer(msg); + } + + /// + /// Sends any object type to server + /// + /// + public void SendMessageObjectToServer(object o) + { + SendMessageToServer(JObject.FromObject(o)); + } + + /// + /// Sends a message to the server from a room + /// + /// room from which the message originates + /// object to be serialized and sent in post body + public void SendMessageToServer(JObject o) + { + if (WSClient != null && WSClient.Connected) + { + string message = JsonConvert.SerializeObject(o, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + + if (!message.Contains("/system/heartbeat")) + Debug.Console(1, this, "Message TX: {0}", message); + //else + // Debug.Console(1, this, "TX messages contains /system/heartbeat"); + + var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); + var result = WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); + if (result != WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) + { + Debug.Console(1, this, "Socket send result error: {0}", result); + } + } + else if (WSClient == null) + { + Debug.Console(1, this, "Cannot send. Not connected."); + } + } + + /// + /// Disconnects the SSE Client and stops the heartbeat timer + /// + /// + void CleanUpWebsocketClient() + { + Debug.Console(1, this, "Disconnecting websocket"); + if (WSClient != null) + { + WSClient.SendCallBack = null; + WSClient.ReceiveCallBack = null; + WSClient.ConnectionCallBack = null; + WSClient.DisconnectCallBack = null; + if (WSClient.Connected) + { + WSClient.Disconnect(); + } + WSClient = null; + } + } + + /// + /// + /// + /// + /// + void StartServerReconnectTimer() + { + StopServerReconnectTimer(); + ServerReconnectTimer = new CTimer(ReconnectToServerTimerCallback, ServerReconnectInterval); + Debug.Console(1, this, "Reconnect Timer Started."); + } + + /// + /// Does what it says + /// + void StopServerReconnectTimer() + { + if (ServerReconnectTimer != null) + { + ServerReconnectTimer.Stop(); + ServerReconnectTimer = null; + } + } + + /// + /// Executes when we don't get a heartbeat message in time. Triggers reconnect. + /// + /// For CTimer callback. Not used + void HeartbeatExpiredTimerCallback(object o) + { + Debug.Console(1, this, "Heartbeat Timer Expired."); + if (ServerHeartbeatCheckTimer != null) + { + ServerHeartbeatCheckTimer.Stop(); + ServerHeartbeatCheckTimer = null; + } + CleanUpWebsocketClient(); + StartServerReconnectTimer(); + } + + /// + /// + /// + /// + /// + void ResetOrStartHearbeatTimer() + { + if (ServerHeartbeatCheckTimer == null) + { + ServerHeartbeatCheckTimer = new CTimer(HeartbeatExpiredTimerCallback, null, ServerHeartbeatInterval, ServerHeartbeatInterval); + Debug.Console(1, this, "Heartbeat Timer Started."); + } + else + { + ServerHeartbeatCheckTimer.Reset(ServerHeartbeatInterval, ServerHeartbeatInterval); + } + } + + /// + /// Waits two and goes again + /// + void ReconnectStreamClient() + { + new CTimer(o => ConnectWebsocketClient(), 2000); + } + + /// + /// + /// + /// + /// + int Websocket_DisconnectCallback(WebSocketClient.WEBSOCKET_RESULT_CODES code, object o) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Websocket disconnected with code: {0}", code); + + if (ServerHeartbeatCheckTimer != null) + ServerHeartbeatCheckTimer.Stop(); + // Start the reconnect timer + StartServerReconnectTimer(); + return 0; + } + + + /// + /// Resets reconnect timer and updates usercode + /// + /// + void HandleHeartBeat(JToken content) + { + SendMessageToServer(JObject.FromObject(new + { + type = "/system/heartbeatAck" + })); + + var code = content["userCode"]; + if(code != null) + { + foreach (var b in RoomBridges) + { + b.SetUserCode(code.Value()); + } + } + ResetOrStartHearbeatTimer(); + } + + /// + /// Outputs debug info when enabled + /// + /// + /// + /// + void CheckHttpDebug(HttpClientResponse r, HTTP_CALLBACK_ERROR e) + { + if (HttpDebugEnabled) + { + try + { + Debug.Console(0, this, "------ Begin HTTP Debug ---------------------------------------"); + if (r != null) + { + Debug.Console(0, this, "HTTP Response URL: {0}", r.ResponseUrl != null ? r.ResponseUrl.ToString() : "NONE"); + Debug.Console(0, this, "HTTP Response code: {0}", r.Code); + Debug.Console(0, this, "HTTP Response content: \r{0}", r.ContentString); + } + else + { + Debug.Console(0, this, "No HTTP response"); + } + Debug.Console(0, this, "HTTP Response 'error' {0}", e); + Debug.Console(0, this, "------ End HTTP Debug -----------------------------------------"); + } + catch (Exception ex) + { + Debug.Console(0, this, "HttpDebugError: {0}", ex); + } + } + } + + /// + /// + /// + /// + /// + /// + /// + int Websocket_ReceiveCallback(byte[] data, uint length, WebSocketClient.WEBSOCKET_PACKET_TYPES opcode, + WebSocketClient.WEBSOCKET_RESULT_CODES err) + { + if (opcode == WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME) + { + var rx = System.Text.Encoding.UTF8.GetString(data, 0, (int)length); + if (rx.Length > 0) + ParseStreamRx(rx); + WSClient.ReceiveAsync(); + } + + else if (opcode == WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__CLOSE) + { + Debug.Console(1, this, "Websocket disconnect received from remote"); + CleanUpWebsocketClient(); + } + else + { + Debug.Console(1, this, "websocket rx opcode/err {0}/{1}", opcode, err); + WSClient.ReceiveAsync(); + } + return 0; + } + + /// + /// Callback to catch possible errors in sending via the websocket + /// + /// + /// + int Websocket_SendCallback(Crestron.SimplSharp.CrestronWebSocketClient.WebSocketClient.WEBSOCKET_RESULT_CODES result) + { + if(result != WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SendCallback questionable result: {0}", result); + return 1; + } + + /// + /// + /// + /// + /// + void ParseStreamRx(string message) + { + if(string.IsNullOrEmpty(message)) + return; + + if (!message.Contains("/system/heartbeat")) + { + Debug.Console(1, this, "Message RX: {0}", message); + } + else + { + LastAckMessage = DateTime.Now; + } + + try + { + var messageObj = JObject.Parse(message); + + var type = messageObj["type"].Value(); + + if (type == "hello") + { + SendInitialMessage(); + ResetOrStartHearbeatTimer(); + } + else if (type == "/system/heartbeat") + { + HandleHeartBeat(messageObj["content"]); + } + else if (type == "raw") + { + var wrapper = messageObj["content"].ToObject(); + DeviceJsonApi.DoDeviceAction(wrapper); + } + else if (type == "close") + { + Debug.Console(1, this, "Received close message from server."); + // DisconnectWebsocketClient(); + + if (ServerHeartbeatCheckTimer != null) + ServerHeartbeatCheckTimer.Stop(); + } + else + { + // Check path against Action dictionary + if (ActionDictionary.ContainsKey(type)) + { + var action = ActionDictionary[type]; + + if (action is Action) + { + (action as Action)(); + } + else if (action is PressAndHoldAction) + { + var stateString = messageObj["content"]["state"].Value(); + + // Look for a button press event + if (!string.IsNullOrEmpty(stateString)) + { + switch (stateString) + { + case "true": + { + if (!PushedActions.ContainsKey(type)) + { + PushedActions.Add(type, new CTimer(o => + { + (action as PressAndHoldAction)(false); + PushedActions.Remove(type); + }, null, ButtonHeartbeatInterval, ButtonHeartbeatInterval)); + } + // Maybe add an else to reset the timer + break; + } + case "held": + { + if (PushedActions.ContainsKey(type)) + { + PushedActions[type].Reset(ButtonHeartbeatInterval, ButtonHeartbeatInterval); + } + return; + } + case "false": + { + if (PushedActions.ContainsKey(type)) + { + PushedActions[type].Stop(); + PushedActions.Remove(type); + } + break; + } + } + + (action as PressAndHoldAction)(stateString == "true"); + } + } + else if (action is Action) + { + var stateString = messageObj["content"]["state"].Value(); + + if (!string.IsNullOrEmpty(stateString)) + { + (action as Action)(stateString == "true"); + } + } + else if (action is Action) + { + (action as Action)(messageObj["content"]["value"].Value()); + } + else if (action is Action) + { + (action as Action)(messageObj["content"]["value"].Value()); + } + else if (action is Action) + { + (action as Action)(messageObj["content"] + .ToObject()); + } + } + else + { + Debug.Console(1, this, "-- Warning: Incoming message has no registered handler"); + } + } + } + catch (Exception err) + { + //Debug.Console(1, "SseMessageLengthBeforeFailureCount: {0}", SseMessageLengthBeforeFailureCount); + //SseMessageLengthBeforeFailureCount = 0; + Debug.Console(1, this, "Unable to parse message: {0}", err); + } + } + + void TestHttpRequest(string s) + { + { + s = s.Trim(); + if (string.IsNullOrEmpty(s)) + { + PrintTestHttpRequestUsage(); + return; + } + var tokens = s.Split(' '); + if (tokens.Length < 2) + { + CrestronConsole.ConsoleCommandResponse("Too few paramaters\r"); + PrintTestHttpRequestUsage(); + return; + } + + try + { + var url = tokens[1]; + if (tokens[0].ToLower() == "get") + { + var resp = new HttpClient().Get(url); + CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); + } + else if (tokens[0].ToLower() == "post") + { + var resp = new HttpClient().Post(url, new byte[] { }); + CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); + } + + else + { + CrestronConsole.ConsoleCommandResponse("Only get or post supported\r"); + PrintTestHttpRequestUsage(); + } + } + catch (HttpException e) + { + CrestronConsole.ConsoleCommandResponse("Exception in request:\r"); + CrestronConsole.ConsoleCommandResponse("Response URL: {0}\r", e.Response.ResponseUrl); + CrestronConsole.ConsoleCommandResponse("Response Error Code: {0}\r", e.Response.Code); + CrestronConsole.ConsoleCommandResponse("Response body: {0}\r", e.Response.ContentString); + } + + } + } + + void PrintTestHttpRequestUsage() + { + CrestronConsole.ConsoleCommandResponse("Usage: mobilehttprequest:N get/post url\r"); + } + } } \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/RoomBridges/CotijaBridgeBase.cs b/PepperDashEssentials/AppServer/RoomBridges/MobileControlBridgeBase.cs similarity index 81% rename from PepperDashEssentials/AppServer/RoomBridges/CotijaBridgeBase.cs rename to PepperDashEssentials/AppServer/RoomBridges/MobileControlBridgeBase.cs index a578a0ae..d7258070 100644 --- a/PepperDashEssentials/AppServer/RoomBridges/CotijaBridgeBase.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/MobileControlBridgeBase.cs @@ -12,15 +12,15 @@ namespace PepperDash.Essentials /// /// /// - public abstract class CotijaBridgeBase: Device + public abstract class MobileControlBridgeBase: Device { - public CotijaSystemController Parent { get; private set; } + public MobileControlSystemController Parent { get; private set; } public string UserCode { get; private set; } public abstract string RoomName { get; } - public CotijaBridgeBase(string key, string name) + public MobileControlBridgeBase(string key, string name) : base(key, name) { } @@ -30,7 +30,7 @@ namespace PepperDash.Essentials /// as adding actions to parent /// /// - public virtual void AddParent(CotijaSystemController parent) + public virtual void AddParent(MobileControlSystemController parent) { Parent = parent; } diff --git a/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs b/PepperDashEssentials/AppServer/RoomBridges/MobileControlDdvc01RoomBridge.cs similarity index 98% rename from PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs rename to PepperDashEssentials/AppServer/RoomBridges/MobileControlDdvc01RoomBridge.cs index 0ff3c80c..eefd8fbc 100644 --- a/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/MobileControlDdvc01RoomBridge.cs @@ -16,9 +16,9 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { - public class CotijaDdvc01RoomBridge : CotijaBridgeBase, IDelayedConfiguration + public class MobileControlDdvc01RoomBridge : MobileControlBridgeBase, IDelayedConfiguration { public class BoolJoin { @@ -236,7 +236,7 @@ namespace PepperDash.Essentials.Room.Cotija } } - CotijaDdvc01DeviceBridge SourceBridge; + MobileControlDdvc01DeviceBridge SourceBridge; Ddvc01AtcMessenger AtcMessenger; Ddvc01VtcMessenger VtcMessenger; @@ -248,7 +248,7 @@ namespace PepperDash.Essentials.Room.Cotija /// /// /// - public CotijaDdvc01RoomBridge(string key, string name, uint ipId) + public MobileControlDdvc01RoomBridge(string key, string name, uint ipId) : base(key, name) { try @@ -258,7 +258,7 @@ namespace PepperDash.Essentials.Room.Cotija if (reg != Crestron.SimplSharpPro.eDeviceRegistrationUnRegistrationResponse.Success) Debug.Console(0, this, "Cannot connect EISC at IPID {0}: \r{1}", ipId, reg); - SourceBridge = new CotijaDdvc01DeviceBridge(key + "-sourceBridge", "DDVC01 source bridge", EISC); + SourceBridge = new MobileControlDdvc01DeviceBridge(key + "-sourceBridge", "DDVC01 source bridge", EISC); DeviceManager.AddDevice(SourceBridge); } catch (Exception) @@ -590,7 +590,7 @@ namespace PepperDash.Essentials.Room.Cotija // rmProps.VolumeSliderNames.Add(EISC.StringInput[i].StringValue); //} - // There should be cotija devices in here, I think... + // There should be Mobile Control devices in here, I think... if(co.Devices == null) co.Devices = new List(); diff --git a/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs b/PepperDashEssentials/AppServer/RoomBridges/MobileControlEssentialsHuddleSpaceRoomBridge.cs similarity index 85% rename from PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs rename to PepperDashEssentials/AppServer/RoomBridges/MobileControlEssentialsHuddleSpaceRoomBridge.cs index 4fd75a36..fd36389a 100644 --- a/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/MobileControlEssentialsHuddleSpaceRoomBridge.cs @@ -9,14 +9,14 @@ using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Room.Cotija; +using PepperDash.Essentials.Room.MobileControl; using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec; using PepperDash.Essentials.Devices.Common.AudioCodec; namespace PepperDash.Essentials { - public class CotijaEssentialsHuddleSpaceRoomBridge : CotijaBridgeBase + public class MobileConrolEssentialsHuddleSpaceRoomBridge : MobileControlBridgeBase { public EssentialsRoomBase Room { get; private set; } @@ -42,7 +42,7 @@ namespace PepperDash.Essentials /// /// /// - public CotijaEssentialsHuddleSpaceRoomBridge(EssentialsRoomBase room): + public MobileConrolEssentialsHuddleSpaceRoomBridge(EssentialsRoomBase room): base("mobileControlBridge-essentialsHuddle", "Essentials Mobile Control Bridge-Huddle") { Room = room; @@ -52,7 +52,7 @@ namespace PepperDash.Essentials /// Override of base: calls base to add parent and then registers actions and events. /// /// - public override void AddParent(CotijaSystemController parent) + public override void AddParent(MobileControlSystemController parent) { base.AddParent(parent); @@ -93,7 +93,7 @@ namespace PepperDash.Essentials var sscRoom = Room as IHasCurrentSourceInfoChange; if(sscRoom != null) - sscRoom.CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSingleSourceChange); + sscRoom.CurrentSourceChange += new SourceInfoChangeHandler(Room_CurrentSingleSourceChange); var vcRoom = Room as IHasVideoCodec; if (vcRoom != null && vcRoom.VideoCodec != null) @@ -169,19 +169,6 @@ namespace PepperDash.Essentials }); } - ///// - ///// Handler for codec changes - ///// - //void codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) - //{ - // PostStatusMessage(new - // { - // calls = GetCallsMessageObject(), - // //vtc = GetVtcCallsMessageObject() - // }); - - //} - /// /// Helper for posting status message /// @@ -334,7 +321,7 @@ namespace PepperDash.Essentials } - void Room_CurrentSingleSourceChange(EssentialsRoomBase room, PepperDash.Essentials.Core.SourceListItem info, ChangeType type) + void Room_CurrentSingleSourceChange(PepperDash.Essentials.Core.SourceListItem info, ChangeType type) { /* Example message * { @@ -395,11 +382,14 @@ namespace PepperDash.Essentials if (dev is ITransport) (dev as ITransport).LinkActions(Parent); - var srcRm = room as IHasCurrentSourceInfoChange; - PostStatusMessage(new - { - selectedSourceKey = srcRm.CurrentSourceInfoKey - }); + var srcRm = Room as IHasCurrentSourceInfoChange; + if (srcRm != null) + { + PostStatusMessage(new + { + selectedSourceKey = srcRm.CurrentSourceInfoKey + }); + } } } } @@ -432,45 +422,6 @@ namespace PepperDash.Essentials volumes = volumes }); } - - ///// - ///// Helper to return a anonymous object with the call data for JSON message - ///// - ///// - //object GetCallsMessageObject() - //{ - // var callRm = Room as IHasVideoCodec; - // if (callRm == null) - // return null; - // return new - // { - // activeCalls = callRm.VideoCodec.ActiveCalls, - // callType = callRm.CallTypeFeedback.IntValue, - // inCall = callRm.InCallFeedback.BoolValue, - // isSharing = callRm.IsSharingFeedback.BoolValue, - // privacyModeIsOn = callRm.PrivacyModeIsOnFeedback.BoolValue - // }; - //} - - ///// - ///// Helper method to build call status for vtc - ///// - ///// - //object GetVtcCallsMessageObject() - //{ - // var callRm = Room as IHasVideoCodec; - // object vtc = null; - // if (callRm != null) - // { - // var codec = callRm.VideoCodec; - // vtc = new - // { - // isInCall = codec.IsInCall, - // calls = codec.ActiveCalls - // }; - // } - // return vtc; - //} } /// diff --git a/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs b/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs index 976c4121..b025de44 100644 --- a/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { /// diff --git a/PepperDashEssentials/AppServer/Volumes.cs b/PepperDashEssentials/AppServer/Volumes.cs index 5806f117..5b5096b0 100644 --- a/PepperDashEssentials/AppServer/Volumes.cs +++ b/PepperDashEssentials/AppServer/Volumes.cs @@ -6,7 +6,7 @@ using Crestron.SimplSharp; using Newtonsoft.Json; -namespace PepperDash.Essentials.Room.Cotija +namespace PepperDash.Essentials.Room.MobileControl { public class Volumes { diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index 65848f71..4cb58a24 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -15,7 +15,7 @@ using PepperDash.Essentials.Devices.Common; using PepperDash.Essentials.DM; using PepperDash.Essentials.Fusion; using PepperDash.Essentials.Room.Config; -using PepperDash.Essentials.Room.Cotija; +using PepperDash.Essentials.Room.MobileControl; using Newtonsoft.Json; @@ -42,8 +42,11 @@ namespace PepperDash.Essentials { DeterminePlatform(); - //CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Loads configuration file", - // ConsoleAccessLevelEnum.AccessOperator); + if (Debug.DoNotLoadOnNextBoot) + { + CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Loads configuration file", + ConsoleAccessLevelEnum.AccessOperator); + } // CrestronConsole.AddNewConsoleCommand(S => { ConfigWriter.WriteConfigFile(null); }, "writeconfig", "writes the current config to a file", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(s => @@ -73,7 +76,8 @@ namespace PepperDash.Essentials "Template URL: {1}", ConfigReader.ConfigObject.SystemUrl, ConfigReader.ConfigObject.TemplateUrl); }, "portalinfo", "Shows portal URLS from configuration", ConsoleAccessLevelEnum.AccessOperator); - GoWithLoad(); + if (!Debug.DoNotLoadOnNextBoot) + GoWithLoad(); } /// @@ -83,57 +87,64 @@ namespace PepperDash.Essentials /// public void DeterminePlatform() { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Determining Platform...."); - - string filePathPrefix; - - var dirSeparator = Global.DirectorySeparator; - - var version = Crestron.SimplSharp.Reflection.Assembly.GetExecutingAssembly().GetName().Version; - - var versionString = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); - - string directoryPrefix; - - directoryPrefix = Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory(); - - if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows OS + try { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on 3-series Appliance", versionString); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Determining Platform...."); - // Check if User/ProgramX exists - if (Directory.Exists(directoryPrefix + dirSeparator + "User" - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + string filePathPrefix; + + var dirSeparator = Global.DirectorySeparator; + + var version = Crestron.SimplSharp.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + + var versionString = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); + + string directoryPrefix; + + directoryPrefix = Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory(); + + if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows OS { - Debug.Console(0, @"User/program{0} directory found", InitialParametersClass.ApplicationNumber); - filePathPrefix = directoryPrefix + dirSeparator + "User" - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on 3-series Appliance", versionString); + + // Check if User/ProgramX exists + if (Directory.Exists(directoryPrefix + dirSeparator + "User" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + Debug.Console(0, @"User/program{0} directory found", InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + "User" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // Check if Nvram/Programx exists + else if (Directory.Exists(directoryPrefix + dirSeparator + "Nvram" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + Debug.Console(0, @"Nvram/program{0} directory found", InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + "Nvram" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // If neither exists, set path to User/ProgramX + else + { + Debug.Console(0, @"No previous directory found. Using User/program{0}", InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + "User" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } } - // Check if Nvram/Programx exists - else if (Directory.Exists(directoryPrefix + dirSeparator + "Nvram" - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + else // Handles Linux OS (Virtual Control) { - Debug.Console(0, @"Nvram/program{0} directory found", InitialParametersClass.ApplicationNumber); - filePathPrefix = directoryPrefix + dirSeparator + "Nvram" - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; - } - // If neither exists, set path to User/ProgramX - else - { - Debug.Console(0, @"No previous directory found. Using User/program{0}", InitialParametersClass.ApplicationNumber); - filePathPrefix = directoryPrefix + dirSeparator + "User" - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on Virtual Control Server", versionString); + + // Set path to User/ + filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; } + + Global.SetFilePathPrefix(filePathPrefix); } - else // Handles Linux OS (Virtual Control) + catch (Exception e) { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on Virtual Control Server", versionString); - - // Set path to User/ - filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; + Debug.Console(0, "Unable to Determine Platform due to Exception: {0}", e.Message); } - - Global.SetFilePathPrefix(filePathPrefix); } /// @@ -143,6 +154,8 @@ namespace PepperDash.Essentials { try { + Debug.SetDoNotLoadOnNextBoot(false); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials load from configuration"); var filesReady = SetupFilesystem(); @@ -337,7 +350,7 @@ namespace PepperDash.Essentials { var sysMon = DeviceManager.GetDeviceForKey("systemMonitor") as PepperDash.Essentials.Core.Monitoring.SystemMonitorController; - var appServer = DeviceManager.GetDeviceForKey("appServer") as CotijaSystemController; + var appServer = DeviceManager.GetDeviceForKey("appServer") as MobileControlSystemController; if (sysMon != null && appServer != null) @@ -493,16 +506,16 @@ namespace PepperDash.Essentials DeviceManager.AddDevice(room); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is EssentialsHuddleSpaceRoom, attempting to add to DeviceManager with Fusion"); - DeviceManager.AddDevice(new EssentialsHuddleSpaceFusionSystemControllerBase((EssentialsHuddleSpaceRoom)room, 0xf1)); + DeviceManager.AddDevice(new Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase((EssentialsHuddleSpaceRoom)room, 0xf1)); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Cotija Bridge..."); - // Cotija bridge - var bridge = new CotijaEssentialsHuddleSpaceRoomBridge(room as EssentialsHuddleSpaceRoom); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Mobile Control Bridge..."); + // Mobile Control bridge + var bridge = new MobileConrolEssentialsHuddleSpaceRoomBridge(room as EssentialsHuddleSpaceRoom); AddBridgePostActivationHelper(bridge); // Lets things happen later when all devices are present DeviceManager.AddDevice(bridge); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Cotija Bridge Added..."); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Mobile Control Bridge Added..."); } else if (room is EssentialsHuddleVtc1Room) { @@ -511,9 +524,9 @@ namespace PepperDash.Essentials Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is EssentialsHuddleVtc1Room, attempting to add to DeviceManager with Fusion"); DeviceManager.AddDevice(new EssentialsHuddleVtc1FusionController((EssentialsHuddleVtc1Room)room, 0xf1)); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Cotija Bridge..."); - // Cotija bridge - var bridge = new CotijaEssentialsHuddleSpaceRoomBridge(room); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Mobile Control Bridge..."); + // Mobile Control bridge + var bridge = new MobileConrolEssentialsHuddleSpaceRoomBridge(room); AddBridgePostActivationHelper(bridge); // Lets things happen later when all devices are present DeviceManager.AddDevice(bridge); } @@ -536,11 +549,11 @@ namespace PepperDash.Essentials /// Helps add the post activation steps that link bridges to main controller /// /// - void AddBridgePostActivationHelper(CotijaBridgeBase bridge) + void AddBridgePostActivationHelper(MobileControlBridgeBase bridge) { bridge.AddPostActivationAction(() => { - var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; + var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as MobileControlSystemController; if (parent == null) { Debug.Console(0, bridge, "ERROR: Cannot connect app server room bridge. System controller not present"); diff --git a/PepperDashEssentials/Devices/Amplifier.cs b/PepperDashEssentials/Devices/Amplifier.cs index bed85856..20d8efa0 100644 --- a/PepperDashEssentials/Devices/Amplifier.cs +++ b/PepperDashEssentials/Devices/Amplifier.cs @@ -12,6 +12,32 @@ namespace PepperDash.Essentials { public class Amplifier : Device, IRoutingSinkNoSwitching { + public event SourceInfoChangeHandler CurrentSourceChange; + + public string CurrentSourceInfoKey { get; set; } + public SourceListItem CurrentSourceInfo + { + get + { + return _CurrentSourceInfo; + } + set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSourceChange; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.WillChange); + + _CurrentSourceInfo = value; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + public RoutingInputPort AudioIn { get; private set; } public Amplifier(string key, string name) diff --git a/PepperDashEssentials/Factory/DeviceFactory.cs b/PepperDashEssentials/Factory/DeviceFactory.cs index 8e83fb9d..24c4bfe1 100644 --- a/PepperDashEssentials/Factory/DeviceFactory.cs +++ b/PepperDashEssentials/Factory/DeviceFactory.cs @@ -61,18 +61,18 @@ namespace PepperDash.Essentials else if (typeName == "appserver") { - var props = JsonConvert.DeserializeObject(properties.ToString()); - return new CotijaSystemController(key, name, props); + var props = JsonConvert.DeserializeObject(properties.ToString()); + return new MobileControlSystemController(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); + var bridge = new PepperDash.Essentials.Room.MobileControl.MobileControlDdvc01RoomBridge(key, name, comm.IpIdInt); bridge.AddPreActivationAction(() => { - var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; + var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as MobileControlSystemController; if (parent == null) { Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); @@ -87,7 +87,7 @@ namespace PepperDash.Essentials else if (typeName == "roomonwhenoccupancydetectedfeature") { - return new Room.Behaviours.RoomOnToDefaultSourceWhenOccupied(dc); + return new RoomOnToDefaultSourceWhenOccupied(dc); } return null; diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs b/PepperDashEssentials/Fusion/EssentialsHuddleVtc1FusionController.cs similarity index 98% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs rename to PepperDashEssentials/Fusion/EssentialsHuddleVtc1FusionController.cs index c79d49d7..8ec55881 100644 --- a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs +++ b/PepperDashEssentials/Fusion/EssentialsHuddleVtc1FusionController.cs @@ -14,6 +14,7 @@ using PepperDash.Core; using PepperDash.Essentials; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Fusion; using PepperDash.Essentials.Devices.Common; using PepperDash.Essentials.Devices.Common.Occupancy; @@ -181,7 +182,7 @@ namespace PepperDash.Essentials.Fusion // Moved to CurrentRoomSourceNameSig = FusionRoom.CreateOffsetStringSig(84, "Display 1 - Current Source", eSigIoMask.InputSigOnly); // Don't think we need to get current status of this as nothing should be alive yet. - (Room as EssentialsHuddleVtc1Room).CurrentSingleSourceChange += Room_CurrentSourceInfoChange; + (Room as EssentialsHuddleVtc1Room).CurrentSourceChange += Room_CurrentSourceInfoChange; FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction((Room as EssentialsHuddleVtc1Room).PowerOnToDefaultOrLastSource); @@ -220,7 +221,7 @@ namespace PepperDash.Essentials.Fusion break; } - var laptops = dict.Where(d => d.Value.SourceDevice is Laptop); + var laptops = dict.Where(d => d.Value.SourceDevice is Core.Devices.Laptop); i = 1; foreach (var kvp in laptops) { diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionSystemController.cs.orig b/PepperDashEssentials/Fusion/FusionSystemController.cs.orig similarity index 100% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionSystemController.cs.orig rename to PepperDashEssentials/Fusion/FusionSystemController.cs.orig diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj index 8ec13dc8..14725a1b 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -175,28 +175,23 @@ - - - - - + - - + - - + + - - - + + + @@ -208,10 +203,10 @@ + - @@ -245,7 +240,7 @@ - + @@ -275,6 +270,9 @@ Essentials Devices Common + + + diff --git a/PepperDashEssentials/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs b/PepperDashEssentials/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs new file mode 100644 index 00000000..949e1212 --- /dev/null +++ b/PepperDashEssentials/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Room.Config +{ + public class EssentialsDualDisplayRoomPropertiesConfig : EssentialsNDisplayRoomPropertiesConfig + { + + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs b/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs index 16e49933..5ce13bfa 100644 --- a/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs @@ -8,19 +8,10 @@ using Newtonsoft.Json; namespace PepperDash.Essentials.Room.Config { - public class EssentialsHuddleVtc1PropertiesConfig : EssentialsRoomPropertiesConfig + public class EssentialsHuddleVtc1PropertiesConfig : EssentialsConferenceRoomPropertiesConfig { [JsonProperty("defaultDisplayKey")] public string DefaultDisplayKey { get; set; } - [JsonProperty("defaultAudioKey")] - public string DefaultAudioKey { get; set; } - [JsonProperty("sourceListKey")] - public string SourceListKey { get; set; } - [JsonProperty("defaultSourceItem")] - public string DefaultSourceItem { get; set; } - [JsonProperty("videoCodecKey")] - public string VideoCodecKey { get; set; } - [JsonProperty("audioCodecKey")] - public string AudioCodecKey { get; set; } + } } \ No newline at end of file diff --git a/PepperDashEssentials/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs b/PepperDashEssentials/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs index 6bd2009e..bb6623aa 100644 --- a/PepperDashEssentials/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs @@ -4,22 +4,36 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +using Newtonsoft.Json; + namespace PepperDash.Essentials.Room.Config { /// /// /// - public class EssentialsNDisplayRoomPropertiesConfig : EssentialsRoomPropertiesConfig + public class EssentialsNDisplayRoomPropertiesConfig : EssentialsConferenceRoomPropertiesConfig { + [JsonProperty("defaultAudioBehavior")] public string DefaultAudioBehavior { get; set; } - public string DefaultAudioKey { get; set; } + [JsonProperty("defaultVideoBehavior")] public string DefaultVideoBehavior { get; set; } - public Dictionary Displays { get; set; } - public string SourceListKey { get; set; } + [JsonProperty("displays")] + public Dictionary Displays { get; set; } public EssentialsNDisplayRoomPropertiesConfig() { - Displays = new Dictionary(); + Displays = new Dictionary(); } + } + + public class DisplayItem : IKeyName + { + public string Key { get; set; } + public string Name { get; set; } + } + } \ No newline at end of file diff --git a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs index 7b7718eb..b34e923b 100644 --- a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs @@ -38,6 +38,12 @@ namespace PepperDash.Essentials.Room.Config { return new Device(roomConfig.Key, roomConfig.Name); // placeholder device that does nothing. } + else if (typeName == "dualdisplay") + { + var rm = new EssentialsDualDisplayRoom(roomConfig); + + return rm; + } return null; } @@ -65,8 +71,8 @@ namespace PepperDash.Essentials.Room.Config /// /// /// - public static PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController GetMicrophonePrivacy( - EssentialsRoomPropertiesConfig props, EssentialsHuddleVtc1Room room) + public static Core.Privacy.MicrophonePrivacyController GetMicrophonePrivacy( + EssentialsRoomPropertiesConfig props, IPrivacy room) { var microphonePrivacy = props.MicrophonePrivacy; if (microphonePrivacy == null) @@ -76,7 +82,7 @@ namespace PepperDash.Essentials.Room.Config } // Get the MicrophonePrivacy device from the device manager var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as - PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController); + Core.Privacy.MicrophonePrivacyController); // Set this room as the IPrivacy device if (mP == null) { @@ -95,28 +101,30 @@ namespace PepperDash.Essentials.Room.Config if (behaviour == "trackroomstate") { // Tie LED enable to room power state - room.OnFeedback.OutputChange += (o, a) => + var essRoom = room as EssentialsRoomBase; + essRoom.OnFeedback.OutputChange += (o, a) => { - if (room.OnFeedback.BoolValue) + if (essRoom.OnFeedback.BoolValue) mP.EnableLeds = true; else mP.EnableLeds = false; }; - mP.EnableLeds = room.OnFeedback.BoolValue; + mP.EnableLeds = essRoom.OnFeedback.BoolValue; } else if (behaviour == "trackcallstate") { // Tie LED enable to room power state - room.InCallFeedback.OutputChange += (o, a) => + var inCallRoom = room as IHasInCallFeedback; + inCallRoom.InCallFeedback.OutputChange += (o, a) => { - if (room.InCallFeedback.BoolValue) + if (inCallRoom.InCallFeedback.BoolValue) mP.EnableLeds = true; else mP.EnableLeds = false; }; - mP.EnableLeds = room.InCallFeedback.BoolValue; + mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue; } return mP; @@ -175,6 +183,25 @@ namespace PepperDash.Essentials.Room.Config public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } } + public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig + { + [JsonProperty("defaultAudioKey")] + public string DefaultAudioKey { get; set; } + [JsonProperty("sourceListKey")] + public string SourceListKey { get; set; } + [JsonProperty("defaultSourceItem")] + public string DefaultSourceItem { get; set; } + + } + + public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig + { + [JsonProperty("videoCodecKey")] + public string VideoCodecKey { get; set; } + [JsonProperty("audioCodecKey")] + public string AudioCodecKey { get; set; } + } + public class EssentialsEnvironmentPropertiesConfig { public bool Enabled { get; set; } diff --git a/PepperDashEssentials/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs b/PepperDashEssentials/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs index 09e09bda..ab40d2fe 100644 --- a/PepperDashEssentials/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs +++ b/PepperDashEssentials/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs @@ -12,15 +12,7 @@ using PepperDash.Essentials.Room.Config; namespace PepperDash.Essentials.Room { - public abstract class EssentialsRoomEmergencyBase : IKeyed - { - public string Key { get; private set; } - public EssentialsRoomEmergencyBase(string key) - { - Key = key; - } - } public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase diff --git a/PepperDashEssentials/Room/Types/EssentialsDualDisplayRoom.cs b/PepperDashEssentials/Room/Types/EssentialsDualDisplayRoom.cs new file mode 100644 index 00000000..44c88724 --- /dev/null +++ b/PepperDashEssentials/Room/Types/EssentialsDualDisplayRoom.cs @@ -0,0 +1,656 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Devices; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Room.Config; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Devices.Common.AudioCodec; + +namespace PepperDash.Essentials +{ + public class EssentialsDualDisplayRoom : EssentialsNDisplayRoomBase, IHasCurrentVolumeControls, + IRunRouteAction, IPrivacy, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasInCallFeedback + { + public event EventHandler CurrentVolumeDeviceChange; + + public EssentialsDualDisplayRoomPropertiesConfig PropertiesConfig { get; private set; } + + //************************ + // Call-related stuff + + public BoolFeedback InCallFeedback { get; private set; } + + /// + /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis + /// + public IntFeedback CallTypeFeedback { get; private set; } + + /// + /// + /// + public BoolFeedback PrivacyModeIsOnFeedback { get; private set; } + + /// + /// When something in the room is sharing with the far end or through other means + /// + public BoolFeedback IsSharingFeedback { get; private set; } + + public IRoutingSinkWithSwitching LeftDisplay { get; private set; } + public IRoutingSinkWithSwitching RightDisplay { get; private set; } + + + protected override Func OnFeedbackFunc + { + get + { + return () => + { + var leftDisp = LeftDisplay as DisplayBase; + var rightDisp = RightDisplay as DisplayBase; + var val = leftDisp != null && leftDisp.CurrentSourceInfo != null + && leftDisp.CurrentSourceInfo.Type == eSourceListItemType.Route + && rightDisp != null && rightDisp.CurrentSourceInfo != null + && rightDisp.CurrentSourceInfo.Type == eSourceListItemType.Route; + return val; + }; + } + } + + /// + /// + /// + protected override Func IsWarmingFeedbackFunc + { + get + { + return () => + { + var leftDisp = LeftDisplay as DisplayBase; + var rightDisp = RightDisplay as DisplayBase; + if (leftDisp != null && RightDisplay != null) + return leftDisp.IsWarmingUpFeedback.BoolValue || rightDisp.IsWarmingUpFeedback.BoolValue; + else + return false; + }; + } + } + + /// + /// + /// + protected override Func IsCoolingFeedbackFunc + { + get + { + return () => + { + var leftDisp = LeftDisplay as DisplayBase; + var rightDisp = RightDisplay as DisplayBase; + if (leftDisp != null && RightDisplay != null) + return leftDisp.IsCoolingDownFeedback.BoolValue || rightDisp.IsCoolingDownFeedback.BoolValue; + else + return false; + }; + } + } + + public IBasicVolumeControls DefaultAudioDevice { get; private set; } + public IBasicVolumeControls DefaultVolumeControls { get; private set; } + + public VideoCodecBase VideoCodec { get; private set; } + + public AudioCodecBase AudioCodec { get; private set; } + + public bool ExcludeFromGlobalFunctions { get; set; } + + public string DefaultSourceItem { get; set; } + + public ushort DefaultVolume { get; set; } + + /// + /// If room is off, enables power on to last source. Default true + /// + public bool EnablePowerOnToLastSource { get; set; } + string LastSourceKey; + + /// + /// Sets the volume control device, and attaches/removes InUseTrackers with "audio" + /// tag to device. + /// + public IBasicVolumeControls CurrentVolumeControls + { + get { return _CurrentAudioDevice; } + set + { + if (value == _CurrentAudioDevice) return; + + var oldDev = _CurrentAudioDevice; + // derigister this room from the device, if it can + if (oldDev is IInUseTracking) + (oldDev as IInUseTracking).InUseTracker.RemoveUser(this, "audio"); + var handler = CurrentVolumeDeviceChange; + if (handler != null) + CurrentVolumeDeviceChange(this, new VolumeDeviceChangeEventArgs(oldDev, value, ChangeType.WillChange)); + _CurrentAudioDevice = value; + if (handler != null) + CurrentVolumeDeviceChange(this, new VolumeDeviceChangeEventArgs(oldDev, value, ChangeType.DidChange)); + // register this room with new device, if it can + if (_CurrentAudioDevice is IInUseTracking) + (_CurrentAudioDevice as IInUseTracking).InUseTracker.AddUser(this, "audio"); + } + } + IBasicVolumeControls _CurrentAudioDevice; + + /// + /// "codecOsd" + /// + public string DefaultCodecRouteString { get { return "codecOsd"; } } + + /// + /// Temporary implementation. Returns the schedule-ready object or null if none. Fow now, + /// always returns the VideoCodec if it is capable + /// + public IHasScheduleAwareness ScheduleSource { get { return VideoCodec as IHasScheduleAwareness; } } + + CCriticalSection SourceSelectLock = new CCriticalSection(); + + public EssentialsDualDisplayRoom(DeviceConfig config) + : base(config) + { + try + { + PropertiesConfig = JsonConvert.DeserializeObject + (config.Properties.ToString()); + + var leftDisp = PropertiesConfig.Displays[eSourceListItemDestinationTypes.leftDisplay]; + if (leftDisp != null) + { + if (!string.IsNullOrEmpty(leftDisp.Key)) + { + LeftDisplay = DeviceManager.GetDeviceForKey(leftDisp.Key) as IRoutingSinkWithSwitching; + Displays.Add(eSourceListItemDestinationTypes.leftDisplay, LeftDisplay); + } + else + Debug.Console(0, this, "Unable to get LeftDisplay for Room"); + } + + var rightDisp = PropertiesConfig.Displays[eSourceListItemDestinationTypes.rightDisplay]; + if (rightDisp != null) + { + if (!string.IsNullOrEmpty(rightDisp.Key)) + { + LeftDisplay = DeviceManager.GetDeviceForKey(rightDisp.Key) as IRoutingSinkWithSwitching; + Displays.Add(eSourceListItemDestinationTypes.rightDisplay, RightDisplay); + } + else + Debug.Console(0, this, "Unable to get LeftDisplay for Room"); + } + + VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as + PepperDash.Essentials.Devices.Common.VideoCodec.VideoCodecBase; + if (VideoCodec == null) + throw new ArgumentNullException("codec cannot be null"); + + AudioCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.AudioCodecKey) as + PepperDash.Essentials.Devices.Common.AudioCodec.AudioCodecBase; + if (AudioCodec == null) + Debug.Console(0, this, "No Audio Codec Found"); + + DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IBasicVolumeControls; + + Initialize(); + } + catch (Exception e) + { + Debug.Console(1, this, "Error building room \n{0}", e); + } + } + + void Initialize() + { + if (DefaultAudioDevice is IBasicVolumeControls) + DefaultVolumeControls = DefaultAudioDevice as IBasicVolumeControls; + else if (DefaultAudioDevice is IHasVolumeDevice) + DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice; + CurrentVolumeControls = DefaultVolumeControls; + + + var leftDisp = LeftDisplay as DisplayBase; + if (leftDisp != null) + InitializeDisplay(leftDisp); + + var rightDisp = RightDisplay as DisplayBase; + if (rightDisp != null) + InitializeDisplay(rightDisp); + + // Get Microphone Privacy object, if any + this.MicrophonePrivacy = EssentialsRoomConfigHelper.GetMicrophonePrivacy(PropertiesConfig, this); + + Debug.Console(2, this, "Microphone Privacy Config evaluated."); + + // Get emergency object, if any + this.Emergency = EssentialsRoomConfigHelper.GetEmergency(PropertiesConfig, this); + + Debug.Console(2, this, "Emergency Config evaluated."); + + // Combines call feedback from both codecs if available + InCallFeedback = new BoolFeedback(() => + { + bool inAudioCall = false; + bool inVideoCall = false; + + if (AudioCodec != null) + inAudioCall = AudioCodec.IsInCall; + + if (VideoCodec != null) + inVideoCall = VideoCodec.IsInCall; + + if (inAudioCall || inVideoCall) + return true; + else + return false; + }); + + VideoCodec.CallStatusChange += (o, a) => this.InCallFeedback.FireUpdate(); + + if (AudioCodec != null) + AudioCodec.CallStatusChange += (o, a) => this.InCallFeedback.FireUpdate(); + + IsSharingFeedback = new BoolFeedback(() => VideoCodec.SharingContentIsOnFeedback.BoolValue); + VideoCodec.SharingContentIsOnFeedback.OutputChange += (o, a) => this.IsSharingFeedback.FireUpdate(); + + // link privacy to VC (for now?) + PrivacyModeIsOnFeedback = new BoolFeedback(() => VideoCodec.PrivacyModeIsOnFeedback.BoolValue); + VideoCodec.PrivacyModeIsOnFeedback.OutputChange += (o, a) => this.PrivacyModeIsOnFeedback.FireUpdate(); + + CallTypeFeedback = new IntFeedback(() => 0); + + SourceListKey = "default"; + EnablePowerOnToLastSource = true; + } + + void InitializeDisplay(DisplayBase disp) + { + if (disp != null) + { + // Link power, warming, cooling to display + disp.PowerIsOnFeedback.OutputChange += (o, a) => + { + if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) + { + if (!disp.PowerIsOnFeedback.BoolValue) + disp.CurrentSourceInfo = null; + OnFeedback.FireUpdate(); + } + if (disp.PowerIsOnFeedback.BoolValue) + { + SetDefaultLevels(); + } + }; + + disp.IsWarmingUpFeedback.OutputChange += (o, a) => + { + IsWarmingUpFeedback.FireUpdate(); + if (!IsWarmingUpFeedback.BoolValue) + (CurrentVolumeControls as IBasicVolumeWithFeedback).SetVolume(DefaultVolume); + }; + disp.IsCoolingDownFeedback.OutputChange += (o, a) => + { + IsCoolingDownFeedback.FireUpdate(); + }; + } + } + + protected override void CustomSetConfig(DeviceConfig config) + { + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + if (newPropertiesConfig != null) + PropertiesConfig = newPropertiesConfig; + + ConfigWriter.UpdateRoomConfig(config); + } + + public override bool CustomActivate() + { + // Add Occupancy object from config + if (PropertiesConfig.Occupancy != null) + this.SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as + IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); + + this.LogoUrl = PropertiesConfig.Logo.GetUrl(); + this.SourceListKey = PropertiesConfig.SourceListKey; + this.DefaultSourceItem = PropertiesConfig.DefaultSourceItem; + this.DefaultVolume = (ushort)(PropertiesConfig.Volumes.Master.Level * 65535 / 100); + + return base.CustomActivate(); + } + + /// + /// + /// + protected override void EndShutdown() + { + VideoCodec.EndAllCalls(); + + SetDefaultLevels(); + + RunDefaultPresentRoute(); + + CrestronEnvironment.Sleep(1000); + + RunRouteAction("roomOff"); + } + + /// + /// Routes the default source item, if any. Returns true when default route exists + /// + public override bool RunDefaultPresentRoute() + { + if (DefaultSourceItem != null) + RunRouteAction(DefaultSourceItem); + + return DefaultSourceItem != null; + } + + /// + /// Sets up the room when started into call mode without presenting a source + /// + /// + public bool RunDefaultCallRoute() + { + RunRouteAction(DefaultCodecRouteString); + return true; + } + + /// + /// + /// + /// + public void RunRouteAction(string routeKey) + { + RunRouteAction(routeKey, null); + } + + /// + /// Gets a source from config list SourceListKey and dynamically build and executes the + /// route or commands + /// + /// + public void RunRouteAction(string routeKey, Action successCallback) + { + // Run this on a separate thread + new CTimer(o => + { + // try to prevent multiple simultaneous selections + SourceSelectLock.TryEnter(); + + try + { + + Debug.Console(1, this, "Run route action '{0}'", routeKey); + var dict = ConfigReader.ConfigObject.GetSourceListForKey(SourceListKey); + if (dict == null) + { + Debug.Console(1, this, "WARNING: Config source list '{0}' not found", SourceListKey); + return; + } + + // Try to get the list item by it's string key + if (!dict.ContainsKey(routeKey)) + { + Debug.Console(1, this, "WARNING: No item '{0}' found on config list '{1}'", + routeKey, SourceListKey); + return; + } + + // End usage timer on last source + if (!string.IsNullOrEmpty(LastSourceKey)) + { + var usageLastSource = dict[LastSourceKey].SourceDevice as IUsageTracking; + if (usageLastSource != null && usageLastSource.UsageTracker != null) + { + try + { + // There MAY have been failures in here. Protect + usageLastSource.UsageTracker.EndDeviceUsage(); + } + catch (Exception e) + { + Debug.Console(1, this, "*#* EXCEPTION in end usage tracking:\r{0}", e); + } + } + } + + // Let's run it + var item = dict[routeKey]; + if (routeKey.ToLower() != "roomoff") + { + + LastSourceKey = routeKey; + } + //else + // CurrentSourceInfoKey = null; + + // hand off the individual routes to this helper + foreach (var route in item.RouteList) + DoRouteItem(route, item, routeKey); + + // Start usage timer on routed source + var usageNewSource = item.SourceDevice as IUsageTracking; + if (usageNewSource != null && usageNewSource.UsageTracker != null) // Have to make sure there is a usage tracker! + { + (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage(); + } + + // See if this can be moved into common, base-class method ------------- + + + // Set volume control, using default if non provided + IBasicVolumeControls volDev = null; + // Handle special cases for volume control + if (string.IsNullOrEmpty(item.VolumeControlKey) + || item.VolumeControlKey.Equals("$defaultAudio", StringComparison.OrdinalIgnoreCase)) + volDev = DefaultVolumeControls; + + // Or a specific device, probably rarely used. + else + { + var dev = DeviceManager.GetDeviceForKey(item.VolumeControlKey); + if (dev is IBasicVolumeControls) + volDev = dev as IBasicVolumeControls; + else if (dev is IHasVolumeDevice) + volDev = (dev as IHasVolumeDevice).VolumeDevice; + } + + if (volDev != CurrentVolumeControls) + { + // zero the volume on the device we are leaving. + // Set the volume to default on device we are entering + if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback) + { + var vd = CurrentVolumeControls as IBasicVolumeWithFeedback; + SavedVolumeLevels[vd] = (uint)vd.VolumeLevelFeedback.IntValue; + vd.SetVolume(0); + } + + CurrentVolumeControls = volDev; + if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback) + { + var vd = CurrentVolumeControls as IBasicVolumeWithFeedback; + ushort vol = (SavedVolumeLevels.ContainsKey(vd) ? (ushort)SavedVolumeLevels[vd] : DefaultVolume); + vd.SetVolume(vol); + } + } + // ----------------------------------------------------------------------- + + + + // store the name and UI info for routes + if (item.SourceKey == "$off") + { + LeftDisplay.CurrentSourceInfoKey = routeKey; + LeftDisplay.CurrentSourceInfo = null; + RightDisplay.CurrentSourceInfoKey = routeKey; + RightDisplay.CurrentSourceInfo = null; + } + //else if (item.SourceKey != null) + //{ + // if(item.RouteList + // CurrentSourceInfoKey = routeKey; + // CurrentSourceInfo = item; + //} + + OnFeedback.FireUpdate(); + + // report back when done + if (successCallback != null) + successCallback(); + } + catch (Exception e) + { + Debug.Console(1, this, "ERROR in routing: {0}", e); + } + + SourceSelectLock.Leave(); + }, 0); // end of CTimer + } + + + /// + /// + /// + /// + void DoRouteItem(SourceRouteListItem route, SourceListItem sourceItem, string sourceItemKey) + { + // if there is a $defaultAll on route, run two separate + if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase)) + { + // Going to assume a single-path route for now + var tempVideo = new SourceRouteListItem + { + DestinationKey = "$defaultDisplay", + SourceKey = route.SourceKey, + Type = eRoutingSignalType.Video + }; + DoRoute(tempVideo, sourceItem, sourceItemKey); + } + else + DoRoute(route, sourceItem, sourceItemKey); + } + + /// + /// + /// + /// + /// + bool DoRoute(SourceRouteListItem route, SourceListItem sourceItem, string sourceItemKey) + { + IRoutingSinkNoSwitching dest = null; + + if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) + dest = DefaultAudioDevice as IRoutingSinkNoSwitching; + else if (route.DestinationKey.Equals(LeftDisplay.Key, StringComparison.OrdinalIgnoreCase)) + dest = LeftDisplay; + else if (route.DestinationKey.Equals(RightDisplay.Key, StringComparison.OrdinalIgnoreCase)) + dest = RightDisplay; + else + dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching; + + if (dest == null) + { + Debug.Console(1, this, "Cannot route, unknown destination '{0}'", route.DestinationKey); + return false; + } + + if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) + { + dest.ReleaseRoute(); + + + + if (dest is IPower) + (dest as IPower).PowerOff(); + } + else + { + var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs; + if (source == null) + { + Debug.Console(1, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey); + return false; + } + dest.ReleaseAndMakeRoute(source, route.Type); + + dest.CurrentSourceInfoKey = sourceItemKey; + dest.CurrentSourceInfo = sourceItem; + } + return true; + } + + public override void RoomVacatedForTimeoutPeriod(object o) + { + //Implement this + } + + /// + /// Does what it says + /// + public override void SetDefaultLevels() + { + Debug.Console(1, this, "Restoring default levels"); + var vc = CurrentVolumeControls as IBasicVolumeWithFeedback; + if (vc != null) + vc.SetVolume(DefaultVolume); + } + /// + /// Will power the room on with the last-used source + /// + public override void PowerOnToDefaultOrLastSource() + { + if (!EnablePowerOnToLastSource || LastSourceKey == null) + return; + RunRouteAction(LastSourceKey); + } + + /// + /// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions + /// + public static void AllRoomsOff() + { + var allRooms = DeviceManager.AllDevices.Where(d => + d is EssentialsHuddleSpaceRoom && !(d as EssentialsHuddleSpaceRoom).ExcludeFromGlobalFunctions); + foreach (var room in allRooms) + (room as EssentialsHuddleSpaceRoom).RunRouteAction("roomOff"); + } + + #region IPrivacy Members + + + public void PrivacyModeOff() + { + VideoCodec.PrivacyModeOff(); + } + + public void PrivacyModeOn() + { + VideoCodec.PrivacyModeOn(); + } + + public void PrivacyModeToggle() + { + VideoCodec.PrivacyModeToggle(); + } + + #endregion + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs index 63f0bb29..e4b503c3 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs @@ -13,10 +13,10 @@ using PepperDash.Essentials.Room.Config; namespace PepperDash.Essentials { - public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IHasCurrentSourceInfoChange, IRunRouteAction, IRunDefaultPresentRoute, IHasCurrentVolumeControls + public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IHasCurrentSourceInfoChange, IRunRouteAction, IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay { public event EventHandler CurrentVolumeDeviceChange; - public event SourceInfoChangeHandler CurrentSingleSourceChange; + public event SourceInfoChangeHandler CurrentSourceChange; protected override Func OnFeedbackFunc { @@ -76,11 +76,6 @@ namespace PepperDash.Essentials public bool ExcludeFromGlobalFunctions { get; set; } - /// - /// The config name of the source list - /// - public string SourceListKey { get; set; } - public string DefaultSourceItem { get; set; } public ushort DefaultVolume { get; set; } @@ -124,17 +119,17 @@ namespace PepperDash.Essentials public SourceListItem CurrentSourceInfo { get { return _CurrentSourceInfo; } - private set + set { if (value == _CurrentSourceInfo) return; - var handler = CurrentSingleSourceChange; + var handler = CurrentSourceChange; // remove from in-use tracker, if so equipped if(_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); if (handler != null) - handler(this, _CurrentSourceInfo, ChangeType.WillChange); + handler(_CurrentSourceInfo, ChangeType.WillChange); _CurrentSourceInfo = value; @@ -142,12 +137,12 @@ namespace PepperDash.Essentials if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); if (handler != null) - handler(this, _CurrentSourceInfo, ChangeType.DidChange); + handler( _CurrentSourceInfo, ChangeType.DidChange); } } SourceListItem _CurrentSourceInfo; - public string CurrentSourceInfoKey { get; private set; } + public string CurrentSourceInfoKey { get; set; } public EssentialsHuddleSpaceRoom(DeviceConfig config) : base(config) @@ -251,7 +246,7 @@ namespace PepperDash.Essentials // Add Occupancy object from config if (PropertiesConfig.Occupancy != null) this.SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as - PepperDash.Essentials.Devices.Common.Occupancy.IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); + IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); this.LogoUrl = PropertiesConfig.Logo.GetUrl(); this.SourceListKey = PropertiesConfig.SourceListKey; diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs index 991a3470..62fbe754 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs @@ -16,11 +16,11 @@ using PepperDash.Essentials.Devices.Common.AudioCodec; namespace PepperDash.Essentials { - public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange, - IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec + public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange, + IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback { public event EventHandler CurrentVolumeDeviceChange; - public event SourceInfoChangeHandler CurrentSingleSourceChange; + public event SourceInfoChangeHandler CurrentSourceChange; //************************ @@ -112,11 +112,6 @@ namespace PepperDash.Essentials public AudioCodecBase AudioCodec { get; private set; } public bool ExcludeFromGlobalFunctions { get; set; } - - /// - /// The config name of the source list - /// - public string SourceListKey { get; set; } public string DefaultSourceItem { get; set; } @@ -162,17 +157,17 @@ namespace PepperDash.Essentials public SourceListItem CurrentSourceInfo { get { return _CurrentSourceInfo; } - private set + set { if (value == _CurrentSourceInfo) return; - var handler = CurrentSingleSourceChange; + var handler = CurrentSourceChange; // remove from in-use tracker, if so equipped if(_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); if (handler != null) - handler(this, _CurrentSourceInfo, ChangeType.WillChange); + handler(_CurrentSourceInfo, ChangeType.WillChange); _CurrentSourceInfo = value; @@ -180,12 +175,12 @@ namespace PepperDash.Essentials if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); if (handler != null) - handler(this, _CurrentSourceInfo, ChangeType.DidChange); + handler(_CurrentSourceInfo, ChangeType.DidChange); } } SourceListItem _CurrentSourceInfo; - public string CurrentSourceInfoKey { get; private set; } + public string CurrentSourceInfoKey { get; set; } /// /// "codecOsd" @@ -340,7 +335,7 @@ namespace PepperDash.Essentials // Add Occupancy object from config if (PropertiesConfig.Occupancy != null) this.SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as - PepperDash.Essentials.Devices.Common.Occupancy.IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); + IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); this.LogoUrl = PropertiesConfig.Logo.GetUrl(); this.SourceListKey = PropertiesConfig.SourceListKey; diff --git a/PepperDashEssentials/Room/Types/EssentialsNDisplayRoomBase.cs b/PepperDashEssentials/Room/Types/EssentialsNDisplayRoomBase.cs index 9cdb14c9..85f52c9f 100644 --- a/PepperDashEssentials/Room/Types/EssentialsNDisplayRoomBase.cs +++ b/PepperDashEssentials/Room/Types/EssentialsNDisplayRoomBase.cs @@ -12,34 +12,22 @@ using PepperDash.Essentials.Core.Devices; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials.Room.Types +namespace PepperDash.Essentials { /// /// Base class for rooms with more than a single display /// - public abstract class EssentialsNDisplayRoomBase : EssentialsRoomBase + public abstract class EssentialsNDisplayRoomBase : EssentialsRoomBase, IHasMultipleDisplays { - public event SourceInfoChangeHandler CurrentSingleSourceChange; + //public event SourceInfoChangeHandler CurrentSingleSourceChange; - public Dictionary Displays { get; protected set; } - - protected override Func IsWarmingFeedbackFunc { get { return () => false; ; } } - protected override Func IsCoolingFeedbackFunc { get { return () => false; } } + public Dictionary Displays { get; protected set;} public EssentialsNDisplayRoomBase(DeviceConfig config) : base (config) { - Displays = new Dictionary(); + Displays = new Dictionary(); - var propertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); - - foreach (var display in propertiesConfig.Displays) - { - var displayDevice = DeviceManager.GetDeviceForKey(display.Value) as IRoutingSinkWithSwitching; - - if (displayDevice != null) - Displays.Add(display.Key, displayDevice); - } } } } \ No newline at end of file diff --git a/PepperDashEssentials/UI/EssentialsTouchpanelController.cs b/PepperDashEssentials/UI/EssentialsTouchpanelController.cs index e15f5b6c..678355e3 100644 --- a/PepperDashEssentials/UI/EssentialsTouchpanelController.cs +++ b/PepperDashEssentials/UI/EssentialsTouchpanelController.cs @@ -13,7 +13,7 @@ using PepperDash.Essentials.Core.PageManagers; namespace PepperDash.Essentials { - public class EssentialsTouchpanelController : Device + public class EssentialsTouchpanelController : Device, IHasBasicTriListWithSmartObject { public BasicTriListWithSmartObject Panel { get; private set; } diff --git a/PepperDashEssentials/UI/SubpageReferenceListSourceItem.cs b/PepperDashEssentials/UI/SubpageReferenceListSourceItem.cs index 73cc5e71..3e1869cc 100644 --- a/PepperDashEssentials/UI/SubpageReferenceListSourceItem.cs +++ b/PepperDashEssentials/UI/SubpageReferenceListSourceItem.cs @@ -25,11 +25,11 @@ namespace PepperDash.Essentials public void RegisterForSourceChange(IHasCurrentSourceInfoChange room) { - room.CurrentSingleSourceChange -= room_CurrentSourceInfoChange; - room.CurrentSingleSourceChange += room_CurrentSourceInfoChange; + room.CurrentSourceChange -= room_CurrentSourceInfoChange; + room.CurrentSourceChange += room_CurrentSourceInfoChange; } - void room_CurrentSourceInfoChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + void room_CurrentSourceInfoChange(SourceListItem info, ChangeType type) { if (type == ChangeType.WillChange && info == SourceItem) ClearFeedback(); diff --git a/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs index 9a2b7a31..144fafb4 100644 --- a/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs +++ b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs @@ -733,7 +733,7 @@ namespace PepperDash.Essentials // Disconnect current room _CurrentRoom.CurrentVolumeDeviceChange -= this.CurrentRoom_CurrentAudioDeviceChange; ClearAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange -= this.CurrentRoom_SourceInfoChange; + _CurrentRoom.CurrentSourceChange -= this.CurrentRoom_SourceInfoChange; DisconnectSource(_CurrentRoom.CurrentSourceInfo); _CurrentRoom.ShutdownPromptTimer.HasStarted -= ShutdownPromptTimer_HasStarted; _CurrentRoom.ShutdownPromptTimer.HasFinished -= ShutdownPromptTimer_HasFinished; @@ -803,7 +803,7 @@ namespace PepperDash.Essentials _CurrentRoom.CurrentVolumeDeviceChange += CurrentRoom_CurrentAudioDeviceChange; RefreshAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange += CurrentRoom_SourceInfoChange; + _CurrentRoom.CurrentSourceChange += CurrentRoom_SourceInfoChange; RefreshSourceInfo(); (Parent as EssentialsPanelMainInterfaceDriver).HeaderDriver.SetupHeaderButtons(this, CurrentRoom); @@ -1064,8 +1064,7 @@ namespace PepperDash.Essentials /// /// Handles source change /// - void CurrentRoom_SourceInfoChange(EssentialsRoomBase room, - SourceListItem info, ChangeType change) + void CurrentRoom_SourceInfoChange(SourceListItem info, ChangeType change) { if (change == ChangeType.WillChange) DisconnectSource(info); diff --git a/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs index be51a4aa..57e68010 100644 --- a/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs +++ b/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs @@ -885,7 +885,7 @@ namespace PepperDash.Essentials // Disconnect current room _CurrentRoom.CurrentVolumeDeviceChange -= this.CurrentRoom_CurrentAudioDeviceChange; ClearAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange -= this.CurrentRoom_SourceInfoChange; + _CurrentRoom.CurrentSourceChange -= this.CurrentRoom_SourceInfoChange; DisconnectSource(_CurrentRoom.CurrentSourceInfo); _CurrentRoom.ShutdownPromptTimer.HasStarted -= ShutdownPromptTimer_HasStarted; _CurrentRoom.ShutdownPromptTimer.HasFinished -= ShutdownPromptTimer_HasFinished; @@ -924,7 +924,7 @@ namespace PepperDash.Essentials _CurrentRoom.CurrentVolumeDeviceChange += CurrentRoom_CurrentAudioDeviceChange; RefreshAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange += CurrentRoom_SourceInfoChange; + _CurrentRoom.CurrentSourceChange += CurrentRoom_SourceInfoChange; RefreshSourceInfo(); if (_CurrentRoom.VideoCodec is IHasScheduleAwareness) @@ -939,7 +939,7 @@ namespace PepperDash.Essentials SetActiveCallListSharingContentStatus(); if (_CurrentRoom != null) - _CurrentRoom.CurrentSingleSourceChange += new SourceInfoChangeHandler(CurrentRoom_CurrentSingleSourceChange); + _CurrentRoom.CurrentSourceChange += new SourceInfoChangeHandler(CurrentRoom_CurrentSingleSourceChange); TriList.SetSigFalseAction(UIBoolJoin.CallStopSharingPress, () => _CurrentRoom.RunRouteAction("codecOsd")); @@ -1050,7 +1050,7 @@ namespace PepperDash.Essentials /// /// /// - void CurrentRoom_CurrentSingleSourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + void CurrentRoom_CurrentSingleSourceChange(SourceListItem info, ChangeType type) { if (_CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && _CurrentRoom.CurrentSourceInfo != null) TriList.StringInput[UIStringJoin.CallSharedSourceNameText].StringValue = _CurrentRoom.CurrentSourceInfo.PreferredName; @@ -1363,8 +1363,7 @@ namespace PepperDash.Essentials /// /// Handles source change /// - void CurrentRoom_SourceInfoChange(EssentialsRoomBase room, - SourceListItem info, ChangeType change) + void CurrentRoom_SourceInfoChange(SourceListItem info, ChangeType change) { if (change == ChangeType.WillChange) DisconnectSource(info); diff --git a/PepperDashEssentials/UIDrivers/SmartObjectRoomsList.cs b/PepperDashEssentials/UIDrivers/SmartObjectRoomsList.cs index e942913a..e43abc09 100644 --- a/PepperDashEssentials/UIDrivers/SmartObjectRoomsList.cs +++ b/PepperDashEssentials/UIDrivers/SmartObjectRoomsList.cs @@ -64,11 +64,11 @@ namespace PepperDash.Essentials parent.SetItemMainText(index, room.Name); UpdateItem(room.CurrentSourceInfo); // Watch for later changes - room.CurrentSingleSourceChange += new SourceInfoChangeHandler(room_CurrentSourceInfoChange); + room.CurrentSourceChange += new SourceInfoChangeHandler(room_CurrentSourceInfoChange); parent.SetItemButtonAction(index, buttonAction); } - void room_CurrentSourceInfoChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + void room_CurrentSourceInfoChange(SourceListItem info, ChangeType type) { UpdateItem(info); } diff --git a/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs b/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs index 621ba257..c777c08e 100644 --- a/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs +++ b/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs @@ -9,8 +9,8 @@ using PepperDash.Essentials.Core; namespace PepperDash.Essentials { - /// - /// The handler type for a Room's SourceInfoChange - /// - public delegate void SourceInfoChangeHandler(EssentialsRoomBase room, SourceListItem info, ChangeType type); + ///// + ///// The handler type for a Room's SourceInfoChange + ///// + //public delegate void SourceInfoChangeHandler(EssentialsRoomBase room, SourceListItem info, ChangeType type); } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/EssentialsConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/EssentialsConfig.cs index 69f162d0..cc3375e2 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/EssentialsConfig.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/EssentialsConfig.cs @@ -21,8 +21,6 @@ namespace PepperDash.Essentials.Core.Config public string TemplateUrl { get; set; } - //public CotijaConfig Cotija { get; private set; } - [JsonProperty("systemUuid")] public string SystemUuid { diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/PC/InRoomPc.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PC/InRoomPc.cs similarity index 97% rename from essentials-framework/Essentials Devices Common/Essentials Devices Common/PC/InRoomPc.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PC/InRoomPc.cs index f3c8ca0e..9a13106f 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/PC/InRoomPc.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PC/InRoomPc.cs @@ -7,7 +7,7 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Routing; using PepperDash.Core; -namespace PepperDash.Essentials.Devices.Common +namespace PepperDash.Essentials.Core.Devices { /// /// This DVD class should cover most IR, one-way DVD and Bluray fuctions diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/PC/Laptop.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PC/Laptop.cs similarity index 97% rename from essentials-framework/Essentials Devices Common/Essentials Devices Common/PC/Laptop.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PC/Laptop.cs index 6ed08e58..a1a8b30b 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/PC/Laptop.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PC/Laptop.cs @@ -7,7 +7,7 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Routing; using PepperDash.Core; -namespace PepperDash.Essentials.Devices.Common +namespace PepperDash.Essentials.Core.Devices { /// /// This DVD class should cover most IR, one-way DVD and Bluray fuctions diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/SourceListItem.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/SourceListItem.cs index 86c940d3..efa2b2ad 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/SourceListItem.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/SourceListItem.cs @@ -95,6 +95,9 @@ namespace PepperDash.Essentials.Core [JsonProperty("disableRoutedSharing")] public bool DisableRoutedSharing { get; set; } + [JsonProperty("destinations")] + public List Destinations { get; set; } + public SourceListItem() { Icon = "Blank"; @@ -112,4 +115,16 @@ namespace PepperDash.Essentials.Core [JsonProperty("type")] public eRoutingSignalType Type { get; set; } } + + /// + /// Defines the valid destination types for SourceListItems in a room + /// + public enum eSourceListItemDestinationTypes + { + defaultDisplay, + leftDisplay, + rightDisplay, + programAudio, + codecContent + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/BasicIrDisplay.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/BasicIrDisplay.cs index b724366f..3ffd0bf4 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/BasicIrDisplay.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/BasicIrDisplay.cs @@ -12,7 +12,7 @@ using PepperDash.Essentials.Core.Routing; namespace PepperDash.Essentials.Core { - public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IPower, IWarmingCooling, IRoutingSinkWithSwitching + public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IPower, IWarmingCooling { public IrOutputPortController IrPort { get; private set; } public ushort IrPulseTime { get; set; } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs index 9ef0ee86..98659ab2 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs @@ -18,6 +18,32 @@ namespace PepperDash.Essentials.Core /// public abstract class DisplayBase : Device, IHasFeedback, IRoutingSinkWithSwitching, IPower, IWarmingCooling, IUsageTracking { + public event SourceInfoChangeHandler CurrentSourceChange; + + public string CurrentSourceInfoKey { get; set; } + public SourceListItem CurrentSourceInfo + { + get + { + return _CurrentSourceInfo; + } + set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSourceChange; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.WillChange); + + _CurrentSourceInfo = value; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + public BoolFeedback PowerIsOnFeedback { get; protected set; } public BoolFeedback IsCoolingDownFeedback { get; protected set; } public BoolFeedback IsWarmingUpFeedback { get; private set; } diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs similarity index 98% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index e7623975..ef5634aa 100644 --- a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -18,12 +18,10 @@ using PepperDash.Core; using PepperDash.Essentials; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Devices.Common; -using PepperDash.Essentials.Devices.Common.Occupancy; -namespace PepperDash.Essentials.Fusion +namespace PepperDash.Essentials.Core.Fusion { public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider { @@ -330,11 +328,11 @@ namespace PepperDash.Essentials.Fusion // Moved to CurrentRoomSourceNameSig = FusionRoom.CreateOffsetStringSig(84, "Display 1 - Current Source", eSigIoMask.InputSigOnly); // Don't think we need to get current status of this as nothing should be alive yet. - (Room as EssentialsHuddleSpaceRoom).CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSourceInfoChange); + (Room as IHasCurrentSourceInfoChange).CurrentSourceChange += new SourceInfoChangeHandler(Room_CurrentSourceInfoChange); - FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction((Room as EssentialsHuddleSpaceRoom).PowerOnToDefaultOrLastSource); - FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() => (Room as EssentialsHuddleSpaceRoom).RunRouteAction("roomOff")); + FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction((Room as EssentialsRoomBase).PowerOnToDefaultOrLastSource); + FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() => (Room as IRunRouteAction).RunRouteAction("roomOff")); // NO!! room.RoomIsOn.LinkComplementInputSig(FusionRoom.SystemPowerOff.InputSig); FusionRoom.ErrorMessage.InputSig.StringValue = "3: 7 Errors: This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;"; @@ -990,7 +988,7 @@ namespace PepperDash.Essentials.Fusion protected virtual void SetUpSources() { // Sources - var dict = ConfigReader.ConfigObject.GetSourceListForKey((Room as EssentialsHuddleSpaceRoom).SourceListKey); + var dict = ConfigReader.ConfigObject.GetSourceListForKey((Room as EssentialsRoomBase).SourceListKey); if (dict != null) { // NEW PROCESS: @@ -1015,7 +1013,7 @@ namespace PepperDash.Essentials.Fusion break; } - var laptops = dict.Where(d => d.Value.SourceDevice is Laptop); + var laptops = dict.Where(d => d.Value.SourceDevice is Devices.Laptop); i = 1; foreach (var kvp in laptops) { @@ -1041,7 +1039,7 @@ namespace PepperDash.Essentials.Fusion else { Debug.Console(1, this, "WARNING: Config source list '{0}' not found for room '{1}'", - (Room as EssentialsHuddleSpaceRoom).SourceListKey, Room.Key); + (Room as EssentialsRoomBase).SourceListKey, Room.Key); } } @@ -1088,7 +1086,7 @@ namespace PepperDash.Essentials.Fusion SourceToFeedbackSigs.Add(pSrc, sigD.InputSig); // And respond to selection in Fusion - sigD.OutputSig.SetSigFalseAction(() => (Room as EssentialsHuddleSpaceRoom).RunRouteAction(routeKey)); + sigD.OutputSig.SetSigFalseAction(() => (Room as IRunRouteAction).RunRouteAction(routeKey)); } catch (Exception) { @@ -1125,7 +1123,7 @@ namespace PepperDash.Essentials.Fusion //uint attrNum = Convert.ToUInt32(keyNum); // Check for UI devices - var uiDev = dev as EssentialsTouchpanelController; + var uiDev = dev as IHasBasicTriListWithSmartObject; if (uiDev != null) { if (uiDev.Panel is Crestron.SimplSharpPro.UI.XpanelForSmartGraphics) @@ -1206,7 +1204,7 @@ namespace PepperDash.Essentials.Fusion display.UsageTracker.DeviceUsageEnded += new EventHandler(UsageTracker_DeviceUsageEnded); } - var defaultDisplay = (Room as EssentialsHuddleSpaceRoom).DefaultDisplay as DisplayBase; + var defaultDisplay = (Room as IHasDefaultDisplay).DefaultDisplay as DisplayBase; if (defaultDisplay == null) { Debug.Console(1, this, "Cannot link null display to Fusion because default display is null"); @@ -1271,7 +1269,7 @@ namespace PepperDash.Essentials.Fusion string displayName = string.Format("Display {0} - ", displayIndex); - if (display == (Room as EssentialsHuddleSpaceRoom).DefaultDisplay) + if (display == (Room as IHasDefaultDisplay).DefaultDisplay) { // Display volume var defaultDisplayVolume = FusionRoom.CreateOffsetUshortSig(50, "Volume - Fader01", eSigIoMask.InputOutputSig); @@ -1290,7 +1288,7 @@ namespace PepperDash.Essentials.Fusion // Current Source var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8, displayName + "Source None", eSigIoMask.InputOutputSig); - defaultDisplaySourceNone.OutputSig.UserObject = new Action(b => { if (!b) (Room as EssentialsHuddleSpaceRoom).RunRouteAction("roomOff"); }); ; + defaultDisplaySourceNone.OutputSig.UserObject = new Action(b => { if (!b) (Room as IRunRouteAction).RunRouteAction("roomOff"); }); ; } } @@ -1389,7 +1387,7 @@ namespace PepperDash.Essentials.Fusion /// /// Event handler for when room source changes /// - protected void Room_CurrentSourceInfoChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + protected void Room_CurrentSourceInfoChange(SourceListItem info, ChangeType type) { // Handle null. Nothing to do when switching from or to null if (info == null || info.SourceDevice == null) @@ -1405,7 +1403,7 @@ namespace PepperDash.Essentials.Fusion { if (SourceToFeedbackSigs.ContainsKey(dev)) SourceToFeedbackSigs[dev].BoolValue = true; - var name = (room == null ? "" : room.Name); + //var name = (room == null ? "" : room.Name); CurrentRoomSourceNameSig.InputSig.StringValue = info.SourceDevice.Name; } } diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionCustomPropertiesBridge.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionCustomPropertiesBridge.cs similarity index 98% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionCustomPropertiesBridge.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionCustomPropertiesBridge.cs index b5704b40..01b230f9 100644 --- a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionCustomPropertiesBridge.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionCustomPropertiesBridge.cs @@ -10,9 +10,8 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Devices; -using PepperDash.Essentials.Room.Behaviours; -namespace PepperDash.Essentials.Fusion +namespace PepperDash.Essentials.Core.Fusion { /// /// Handles mapping Fusion Custom Property values to system properties diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionEventHandlers.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionEventHandlers.cs similarity index 88% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionEventHandlers.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionEventHandlers.cs index 4987c047..26647a96 100644 --- a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionEventHandlers.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionEventHandlers.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Fusion +namespace PepperDash.Essentials.Core.Fusion { public class ScheduleChangeEventArgs : EventArgs { diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionProcessorQueries.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionProcessorQueries.cs similarity index 97% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionProcessorQueries.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionProcessorQueries.cs index 8757be62..62109d97 100644 --- a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionProcessorQueries.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionProcessorQueries.cs @@ -4,9 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; using PepperDash.Core; -using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Fusion +namespace PepperDash.Essentials.Core.Fusion { /// /// When created, runs progcomments on every slot and stores the program names in a list diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionRviDataClasses.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionRviDataClasses.cs similarity index 99% rename from PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionRviDataClasses.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionRviDataClasses.cs index 3752531a..99fc0abb 100644 --- a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionRviDataClasses.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/FusionRviDataClasses.cs @@ -7,7 +7,7 @@ using Crestron.SimplSharpPro.Fusion; using PepperDash.Core; -namespace PepperDash.Essentials.Fusion +namespace PepperDash.Essentials.Core.Fusion { // Helper Classes for GUIDs diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/MOVED FusionSystemController.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/MOVED FusionSystemController.cs deleted file mode 100644 index 8e7e70c6..00000000 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/MOVED FusionSystemController.cs +++ /dev/null @@ -1,377 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; - -//using Crestron.SimplSharp; -//using Crestron.SimplSharpPro; -//using Crestron.SimplSharpPro.DeviceSupport; - -//using Crestron.SimplSharpPro.Fusion; -//using PepperDash.Essentials.Core; - -//using PepperDash.Core; - - -//namespace PepperDash.Essentials.Core.Fusion -//{ -// public class EssentialsHuddleSpaceFusionSystemController : Device -// { -// FusionRoom FusionRoom; -// Room Room; -// Dictionary SourceToFeedbackSigs = new Dictionary(); - -// StatusMonitorCollection ErrorMessageRollUp; - -// public EssentialsHuddleSpaceFusionSystemController(HuddleSpaceRoom room, uint ipId) -// : base(room.Key + "-fusion") -// { -// Room = room; - -// FusionRoom = new FusionRoom(ipId, Global.ControlSystem, room.Name, "awesomeGuid-" + room.Key); -// FusionRoom.Register(); - -// FusionRoom.FusionStateChange += new FusionStateEventHandler(FusionRoom_FusionStateChange); - -// // Room to fusion room -// room.RoomIsOn.LinkInputSig(FusionRoom.SystemPowerOn.InputSig); -// var srcName = FusionRoom.CreateOffsetStringSig(50, "Source - Name", eSigIoMask.InputSigOnly); -// room.CurrentSourceName.LinkInputSig(srcName.InputSig); - -// FusionRoom.SystemPowerOn.OutputSig.UserObject = new Action(b => { if (b) room.RoomOn(null); }); -// FusionRoom.SystemPowerOff.OutputSig.UserObject = new Action(b => { if (b) room.RoomOff(); }); -// // NO!! room.RoomIsOn.LinkComplementInputSig(FusionRoom.SystemPowerOff.InputSig); -// FusionRoom.ErrorMessage.InputSig.StringValue = "3: 7 Errors: This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;"; - -// // Sources -// foreach (var src in room.Sources) -// { -// var srcNum = src.Key; -// var pSrc = src.Value as IPresentationSource; -// var keyNum = ExtractNumberFromKey(pSrc.Key); -// if (keyNum == -1) -// { -// Debug.Console(1, this, "WARNING: Cannot link source '{0}' to numbered Fusion attributes", pSrc.Key); -// continue; -// } -// string attrName = null; -// uint attrNum = Convert.ToUInt32(keyNum); -// switch (pSrc.Type) -// { -// case PresentationSourceType.None: -// break; -// case PresentationSourceType.SetTopBox: -// attrName = "Source - TV " + keyNum; -// attrNum += 115; // TV starts at 116 -// break; -// case PresentationSourceType.Dvd: -// attrName = "Source - DVD " + keyNum; -// attrNum += 120; // DVD starts at 121 -// break; -// case PresentationSourceType.PC: -// attrName = "Source - PC " + keyNum; -// attrNum += 110; // PC starts at 111 -// break; -// case PresentationSourceType.Laptop: -// attrName = "Source - Laptop " + keyNum; -// attrNum += 100; // Laptops start at 101 -// break; -// case PresentationSourceType.VCR: -// attrName = "Source - VCR " + keyNum; -// attrNum += 125; // VCRs start at 126 -// break; -// } -// if (attrName == null) -// { -// Debug.Console(1, this, "Source type {0} does not have corresponsing Fusion attribute type, skipping", pSrc.Type); -// continue; -// } -// Debug.Console(2, this, "Creating attribute '{0}' with join {1} for source {2}", attrName, attrNum, pSrc.Key); -// var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputOutputSig); -// // Need feedback when this source is selected -// // Event handler, added below, will compare source changes with this sig dict -// SourceToFeedbackSigs.Add(pSrc, sigD.InputSig); - -// // And respond to selection in Fusion -// sigD.OutputSig.UserObject = new Action(b => { if(b) room.SelectSource(pSrc); }); -// } - -// // Attach to all room's devices with monitors. -// //foreach (var dev in DeviceManager.Devices) -// foreach (var dev in DeviceManager.GetDevices()) -// { -// if (!(dev is ICommunicationMonitor)) -// continue; - -// var keyNum = ExtractNumberFromKey(dev.Key); -// if (keyNum == -1) -// { -// Debug.Console(1, this, "WARNING: Cannot link device '{0}' to numbered Fusion monitoring attributes", dev.Key); -// continue; -// } -// string attrName = null; -// uint attrNum = Convert.ToUInt32(keyNum); - -// //if (dev is SmartGraphicsTouchpanelControllerBase) -// //{ -// // if (attrNum > 10) -// // continue; -// // attrName = "Device Ok - Touch Panel " + attrNum; -// // attrNum += 200; -// //} -// //// add xpanel here - -// //else -// if (dev is DisplayBase) -// { -// if (attrNum > 10) -// continue; -// attrName = "Device Ok - Display " + attrNum; -// attrNum += 240; -// } -// //else if (dev is DvdDeviceBase) -// //{ -// // if (attrNum > 5) -// // continue; -// // attrName = "Device Ok - DVD " + attrNum; -// // attrNum += 260; -// //} -// // add set top box - -// // add Cresnet roll-up - -// // add DM-devices roll-up - -// if (attrName != null) -// { -// // Link comm status to sig and update -// var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputSigOnly); -// var smd = dev as ICommunicationMonitor; -// sigD.InputSig.BoolValue = smd.CommunicationMonitor.Status == MonitorStatus.IsOk; -// smd.CommunicationMonitor.StatusChange += (o, a) => { sigD.InputSig.BoolValue = a.Status == MonitorStatus.IsOk; }; -// Debug.Console(0, this, "Linking '{0}' communication monitor to Fusion '{1}'", dev.Key, attrName); -// } -// } - -// // Don't think we need to get current status of this as nothing should be alive yet. -// room.PresentationSourceChange += Room_PresentationSourceChange; - -// // these get used in multiple places -// var display = room.Display; -// var dispPowerOnAction = new Action(b => { if (!b) display.PowerOn(); }); -// var dispPowerOffAction = new Action(b => { if (!b) display.PowerOff(); }); - -// // Display to fusion room sigs -// FusionRoom.DisplayPowerOn.OutputSig.UserObject = dispPowerOnAction; -// FusionRoom.DisplayPowerOff.OutputSig.UserObject = dispPowerOffAction; -// display.PowerIsOnFeedback.LinkInputSig(FusionRoom.DisplayPowerOn.InputSig); -// if (display is IDisplayUsage) -// (display as IDisplayUsage).LampHours.LinkInputSig(FusionRoom.DisplayUsage.InputSig); - -// // Roll up ALL device errors -// ErrorMessageRollUp = new StatusMonitorCollection(this); -// foreach (var dev in DeviceManager.GetDevices()) -// { -// var md = dev as ICommunicationMonitor; -// if (md != null) -// { -// ErrorMessageRollUp.AddMonitor(md.CommunicationMonitor); -// Debug.Console(2, this, "Adding '{0}' to room's overall error monitor", md.CommunicationMonitor.Parent.Key); -// } -// } -// ErrorMessageRollUp.Start(); -// FusionRoom.ErrorMessage.InputSig.StringValue = ErrorMessageRollUp.Message; -// ErrorMessageRollUp.StatusChange += (o, a) => { -// FusionRoom.ErrorMessage.InputSig.StringValue = ErrorMessageRollUp.Message; }; - - -// // static assets --------------- testing - -// // test assets --- THESE ARE BOTH WIRED TO AssetUsage somewhere internally. -// var ta1 = FusionRoom.CreateStaticAsset(1, "Test asset 1", "Awesome Asset", "Awesome123"); -// ta1.AssetError.InputSig.StringValue = "This should be error"; - - -// var ta2 = FusionRoom.CreateStaticAsset(2, "Test asset 2", "Awesome Asset", "Awesome1232"); -// ta2.AssetUsage.InputSig.StringValue = "This should be usage"; - - -// // Make a display asset -// var dispAsset = FusionRoom.CreateStaticAsset(3, display.Name, "Display", "awesomeDisplayId" + room.Key); -// dispAsset.PowerOn.OutputSig.UserObject = dispPowerOnAction; -// dispAsset.PowerOff.OutputSig.UserObject = dispPowerOffAction; -// display.PowerIsOnFeedback.LinkInputSig(dispAsset.PowerOn.InputSig); -// // NO!! display.PowerIsOn.LinkComplementInputSig(dispAsset.PowerOff.InputSig); -// // Use extension methods -// dispAsset.TrySetMakeModel(display); -// dispAsset.TryLinkAssetErrorToCommunication(display); - - -// // Make it so! -// FusionRVI.GenerateFileForAllFusionDevices(); -// } - -// /// -// /// Helper to get the number from the end of a device's key string -// /// -// /// -1 if no number matched -// int ExtractNumberFromKey(string key) -// { -// var capture = System.Text.RegularExpressions.Regex.Match(key, @"\D+(\d+)"); -// if (!capture.Success) -// return -1; -// else return Convert.ToInt32(capture.Groups[1].Value); -// } - -// void Room_PresentationSourceChange(object sender, EssentialsRoomSourceChangeEventArgs e) -// { -// if (e.OldSource != null) -// { -// if (SourceToFeedbackSigs.ContainsKey(e.OldSource)) -// SourceToFeedbackSigs[e.OldSource].BoolValue = false; -// } -// if (e.NewSource != null) -// { -// if (SourceToFeedbackSigs.ContainsKey(e.NewSource)) -// SourceToFeedbackSigs[e.NewSource].BoolValue = true; -// } -// } - -// void FusionRoom_FusionStateChange(FusionBase device, FusionStateEventArgs args) -// { - -// // The sig/UO method: Need separate handlers for fixed and user sigs, all flavors, -// // even though they all contain sigs. - -// var sigData = (args.UserConfiguredSigDetail as BooleanSigDataFixedName); -// if (sigData != null) -// { -// var outSig = sigData.OutputSig; -// if (outSig.UserObject is Action) -// (outSig.UserObject as Action).Invoke(outSig.BoolValue); -// else if (outSig.UserObject is Action) -// (outSig.UserObject as Action).Invoke(outSig.UShortValue); -// else if (outSig.UserObject is Action) -// (outSig.UserObject as Action).Invoke(outSig.StringValue); -// return; -// } - -// var attrData = (args.UserConfiguredSigDetail as BooleanSigData); -// if (attrData != null) -// { -// var outSig = attrData.OutputSig; -// if (outSig.UserObject is Action) -// (outSig.UserObject as Action).Invoke(outSig.BoolValue); -// else if (outSig.UserObject is Action) -// (outSig.UserObject as Action).Invoke(outSig.UShortValue); -// else if (outSig.UserObject is Action) -// (outSig.UserObject as Action).Invoke(outSig.StringValue); -// return; -// } - -// } -// } - - -// public static class FusionRoomExtensions -// { -// /// -// /// Creates and returns a fusion attribute. The join number will match the established Simpl -// /// standard of 50+, and will generate a 50+ join in the RVI. It calls -// /// FusionRoom.AddSig with join number - 49 -// /// -// /// The new attribute -// public static BooleanSigData CreateOffsetBoolSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) -// { -// if (number < 50) throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); -// number -= 49; -// fr.AddSig(eSigType.Bool, number, name, mask); -// return fr.UserDefinedBooleanSigDetails[number]; -// } - -// /// -// /// Creates and returns a fusion attribute. The join number will match the established Simpl -// /// standard of 50+, and will generate a 50+ join in the RVI. It calls -// /// FusionRoom.AddSig with join number - 49 -// /// -// /// The new attribute -// public static UShortSigData CreateOffsetUshortSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) -// { -// if (number < 50) throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); -// number -= 49; -// fr.AddSig(eSigType.UShort, number, name, mask); -// return fr.UserDefinedUShortSigDetails[number]; -// } - -// /// -// /// Creates and returns a fusion attribute. The join number will match the established Simpl -// /// standard of 50+, and will generate a 50+ join in the RVI. It calls -// /// FusionRoom.AddSig with join number - 49 -// /// -// /// The new attribute -// public static StringSigData CreateOffsetStringSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) -// { -// if (number < 50) throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); -// number -= 49; -// fr.AddSig(eSigType.String, number, name, mask); -// return fr.UserDefinedStringSigDetails[number]; -// } - -// /// -// /// Creates and returns a static asset -// /// -// /// the new asset -// public static FusionStaticAsset CreateStaticAsset(this FusionRoom fr, uint number, string name, string type, string instanceId) -// { -// fr.AddAsset(eAssetType.StaticAsset, number, name, type, instanceId); -// return fr.UserConfigurableAssetDetails[number].Asset as FusionStaticAsset; -// } -// } - -// //************************************************************************************************ -// /// -// /// Extensions to enhance Fusion room, asset and signal creation. -// /// -// public static class FusionStaticAssetExtensions -// { -// /// -// /// Tries to set a Fusion asset with the make and model of a device. -// /// If the provided Device is IMakeModel, will set the corresponding parameters on the fusion static asset. -// /// Otherwise, does nothing. -// /// -// public static void TrySetMakeModel(this FusionStaticAsset asset, Device device) -// { -// var mm = device as IMakeModel; -// if (mm != null) -// { -// asset.ParamMake.Value = mm.DeviceMake; -// asset.ParamModel.Value = mm.DeviceModel; -// } -// } - -// /// -// /// Tries to attach the AssetError input on a Fusion asset to a Device's -// /// CommunicationMonitor.StatusChange event. Does nothing if the device is not -// /// IStatusMonitor -// /// -// /// -// /// -// public static void TryLinkAssetErrorToCommunication(this FusionStaticAsset asset, Device device) -// { -// if (device is ICommunicationMonitor) -// { -// var monitor = (device as ICommunicationMonitor).CommunicationMonitor; -// monitor.StatusChange += (o, a) => -// { -// // Link connected and error inputs on asset -// asset.Connected.InputSig.BoolValue = a.Status == MonitorStatus.IsOk; -// asset.AssetError.InputSig.StringValue = a.Status.ToString(); -// }; -// // set current value -// asset.Connected.InputSig.BoolValue = monitor.Status == MonitorStatus.IsOk; -// asset.AssetError.InputSig.StringValue = monitor.Status.ToString(); -// } -// } -// } - -//} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/MIcrophone/MicrophonePrivacyController.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Microphone Privacy/MicrophonePrivacyController.cs similarity index 99% rename from essentials-framework/Essentials Devices Common/Essentials Devices Common/MIcrophone/MicrophonePrivacyController.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Microphone Privacy/MicrophonePrivacyController.cs index d8a7f543..e238c40e 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/MIcrophone/MicrophonePrivacyController.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Microphone Privacy/MicrophonePrivacyController.cs @@ -9,7 +9,7 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.CrestronIO; -namespace PepperDash.Essentials.Devices.Common.Microphones +namespace PepperDash.Essentials.Core.Privacy { /// /// Used for applications where one or more microphones with momentary contact closure outputs are used to diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/MIcrophone/MicrophonePrivacyControllerConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Microphone Privacy/MicrophonePrivacyControllerConfig.cs similarity index 89% rename from essentials-framework/Essentials Devices Common/Essentials Devices Common/MIcrophone/MicrophonePrivacyControllerConfig.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Microphone Privacy/MicrophonePrivacyControllerConfig.cs index 238fc7cf..1c172090 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/MIcrophone/MicrophonePrivacyControllerConfig.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Microphone Privacy/MicrophonePrivacyControllerConfig.cs @@ -6,7 +6,7 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core.CrestronIO; -namespace PepperDash.Essentials.Devices.Common.Microphones +namespace PepperDash.Essentials.Core.Privacy { public class MicrophonePrivacyControllerConfig { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 9eca4e25..7c3cd563 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -123,6 +123,8 @@ + + @@ -132,12 +134,19 @@ + + + + + + + @@ -173,6 +182,10 @@ + + + + @@ -191,7 +204,6 @@ - @@ -217,6 +229,7 @@ + @@ -238,7 +251,6 @@ - diff --git a/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs similarity index 96% rename from PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs index b12fd2e5..fcb04420 100644 --- a/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs @@ -1,526 +1,525 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Crestron.SimplSharp; -using Crestron.SimplSharp.Scheduler; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Devices; -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 : ReconfigurableDevice - { - RoomOnToDefaultSourceWhenOccupiedConfig PropertiesConfig; - - 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(DeviceConfig config) : - base (config) - { - PropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); - - FeatureEventGroup = new ScheduledEventGroup(this.Key); - - FeatureEventGroup.RetrieveAllEvents(); - - // Add to the global class for tracking - Scheduler.AddEventGroup(FeatureEventGroup); - - AddPostActivationAction(() => - { - // Subscribe to room event to know when RoomOccupancy is set and ready to be subscribed to - if (Room != null) - Room.RoomOccupancyIsSet += new EventHandler(RoomOccupancyIsSet); - - else - Debug.Console(1, this, "Room has no RoomOccupancy object set"); - - var fusionRoomKey = PropertiesConfig.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() - { - SetUpDevice(); - - return base.CustomActivate(); - } - - /// - /// Sets up device based on config values - /// - void SetUpDevice() - { - Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as EssentialsRoomBase; - - if (Room != null) - { - try - { - FeatureEnabledTime = DateTime.Parse(PropertiesConfig.OccupancyStartTime); - - if (FeatureEnabledTime != null) - { - Debug.Console(1, this, "Enabled Time: {0}", FeatureEnabledTime.ToString()); - } - else - Debug.Console(1, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyStartTime); - } - catch (Exception e) - { - Debug.Console(1, this, "Unable to parse OccupancyStartTime property: {0} \n Error: {1}", PropertiesConfig.OccupancyStartTime, e); - } - - try - { - FeatureDisabledTime = DateTime.Parse(PropertiesConfig.OccupancyEndTime); - - if (FeatureDisabledTime != null) - { - Debug.Console(1, this, "Disabled Time: {0}", FeatureDisabledTime.ToString()); - } - else - Debug.Console(1, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyEndTime); - } - catch (Exception e) - { - Debug.Console(1, this, "Unable to parse a DateTime config value \n Error: {1}", e); - } - - if (!PropertiesConfig.EnableRoomOnWhenOccupied) - FeatureEventGroup.ClearAllEvents(); - else - { - AddEnableEventToGroup(); - - AddDisableEventToGroup(); - - FeatureEventGroup.UserGroupCallBack += new ScheduledEventGroup.UserEventGroupCallBack(FeatureEventGroup_UserGroupCallBack); - - FeatureEventGroup.EnableAllEvents(); - } - - FeatureEnabled = CheckIfFeatureShouldBeEnabled(); - } - else - Debug.Console(1, this, "Unable to get room from Device Manager with key: {0}", PropertiesConfig.RoomKey); - } - - - protected override void CustomSetConfig(DeviceConfig config) - { - var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); - - if(newPropertiesConfig != null) - PropertiesConfig = newPropertiesConfig; - - ConfigWriter.UpdateDeviceConfig(config); - - SetUpDevice(); - } - - /// - /// Subscribe to feedback from RoomIsOccupiedFeedback on Room - /// - /// - /// - void RoomOccupancyIsSet(object sender, EventArgs e) - { - if (Room.RoomOccupancy != null) - { - Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; - Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler(RoomIsOccupiedFeedback_OutputChange); - Debug.Console(1, this, "Subscribed to RoomOccupancy status from: '{0}'", Room.Key); - } - } - - void FeatureEventGroup_UserGroupCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) - { - if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) - { - if (SchEvent.Name == FeatureEnableEventName) - { - if (PropertiesConfig.EnableRoomOnWhenOccupied) - FeatureEnabled = true; - - Debug.Console(1, this, "*****Feature Enabled by event.*****"); - } - else if (SchEvent.Name == FeatureDisableEventName) - { - FeatureEnabled = false; - - Debug.Console(1, this, "*****Feature Disabled by event.*****"); - } - } - } - - /// - /// 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(PropertiesConfig.EnableRoomOnWhenOccupied) - { - Debug.Console(1, this, "Current Time: {0} \n FeatureEnabledTime: {1} \n FeatureDisabledTime: {2}", DateTime.Now, FeatureEnabledTime, FeatureDisabledTime); - - if (DateTime.Now.TimeOfDay.CompareTo(FeatureEnabledTime.TimeOfDay) >= 0 && FeatureDisabledTime.TimeOfDay.CompareTo(DateTime.Now.TimeOfDay) > 0) - { - if (SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(DateTime.Now, CalculateDaysOfWeekRecurrence())) - { - enabled = true; - } - } - } - - if(enabled) - Debug.Console(1, this, "*****Feature Enabled*****"); - else - Debug.Console(1, this, "*****Feature Disabled*****"); - - return enabled; - } - - /// - /// Respond to Occupancy status event - /// - /// - /// - void RoomIsOccupiedFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - Debug.Console(1, this, "RoomIsOccupiedFeeback.OutputChange event fired. e.BoolValue: {0}", e.BoolValue); - if(e.BoolValue) - { - // Occupancy detected - - if (FeatureEnabled) - { - // Check room power state first - if (!Room.OnFeedback.BoolValue) - { - Debug.Console(1, this, "Powering Room on to default source"); - Room.RunDefaultPresentRoute(); - } - } - } - } - - 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 - - if(!schEvent.Acknowledgeable) - schEvent.Acknowledgeable = true; - - if(!schEvent.Persistent) - schEvent.Persistent = true; - - schEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); - - // Set config driven properties - - if (schEvent.Name == FeatureEnableEventName) - { - schEvent.Description = "Enables the RoomOnToDefaultSourceWhenOccupiedFeature"; - - var eventRecurrennce = CalculateDaysOfWeekRecurrence(); - - var eventTime = new DateTime(); - - // Check to make sure the date for this event is in the future - if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0) - eventTime = FeatureEnabledTime.AddDays(1); - else - eventTime = FeatureEnabledTime; - - Debug.Console(1, this, "eventTime (before recurrence check): {0}", eventTime); - - // Check day of week against recurrence days and move date ahead as necessary to avoid throwing an exception by trying to set the event - // start date on a day of the week that doesn't match teh recurrence values - while(!SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(eventTime, eventRecurrennce)) - { - eventTime = eventTime.AddDays(1); - Debug.Console(1, this, "eventTime does not fall on a recurrence weekday. eventTime: {0}", eventTime); - } - - schEvent.DateAndTime.SetAbsoluteEventTime(eventTime); - - Debug.Console(1, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); - - CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureEnabledTime, FeatureDisabledTime); - - schEvent.Recurrence.Weekly(eventRecurrennce); - - } - 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); - } - - /// - /// Checks existing event to see if it matches the execution time - /// - /// - /// - bool CheckExistingEventTimeForMatch(ScheduledEvent existingEvent, DateTime newTime) - { - bool isMatch = true; - - // Check to see if hour and minute match - if (existingEvent.DateAndTime.Hour != newTime.Hour || existingEvent.DateAndTime.Minute != newTime.Minute) - return false; - - - return isMatch; - } - - /// - /// Checks existing event to see if it matches the recurrence days - /// - /// - /// - /// - bool CheckExistingEventRecurrenceForMatch(ScheduledEvent existingEvent, ScheduledEventCommon.eWeekDays eWeekdays) - { - bool isMatch = true; - - // Check to see if recurrence matches - if (eWeekdays != existingEvent.Recurrence.RecurrenceDays) - return false; - - return isMatch; - } - - /// - /// 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 - { - // Check if existing event has same time and recurrence as config values - - FeatureEnableEvent = FeatureEventGroup.ScheduledEvents[FeatureEnableEventName]; - Debug.Console(1, this, "Enable event already found in group"); - - // Check config times and days against DateAndTime of existing event. If different, delete existing event and create new event - if(!CheckExistingEventTimeForMatch(FeatureEnableEvent, FeatureEnabledTime) || !CheckExistingEventRecurrenceForMatch(FeatureEnableEvent, CalculateDaysOfWeekRecurrence())) - { - Debug.Console(1, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureEnableEvent.Name); - FeatureEventGroup.DeleteEvent(FeatureEnableEvent); - - FeatureEnableEvent = null; - - CreateEvent(FeatureEnableEvent, FeatureEnableEventName); - } - } - - } - - /// - /// 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"); - - // Check config times against DateAndTime of existing event. If different, delete existing event and create new event - if(!CheckExistingEventTimeForMatch(FeatureDisableEvent, FeatureDisabledTime)) - { - Debug.Console(1, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureDisableEvent.Name); - - FeatureEventGroup.DeleteEvent(FeatureDisableEvent); - - FeatureDisableEvent = null; - - CreateEvent(FeatureDisableEvent, FeatureDisableEventName); - } - } - } - - - /// - /// Calculates the correct bitfield enum value for the event recurrence based on the config values - /// - /// - ScheduledEventCommon.eWeekDays CalculateDaysOfWeekRecurrence() - { - ScheduledEventCommon.eWeekDays value = new ScheduledEventCommon.eWeekDays(); - - if (PropertiesConfig.EnableSunday) - value = value | ScheduledEventCommon.eWeekDays.Sunday; - if (PropertiesConfig.EnableMonday) - value = value | ScheduledEventCommon.eWeekDays.Monday; - if (PropertiesConfig.EnableTuesday) - value = value | ScheduledEventCommon.eWeekDays.Tuesday; - if (PropertiesConfig.EnableWednesday) - value = value | ScheduledEventCommon.eWeekDays.Wednesday; - if (PropertiesConfig.EnableThursday) - value = value | ScheduledEventCommon.eWeekDays.Thursday; - if (PropertiesConfig.EnableFriday) - value = value | ScheduledEventCommon.eWeekDays.Friday; - if (PropertiesConfig.EnableSaturday) - value = value | ScheduledEventCommon.eWeekDays.Saturday; - - return value; - } - - /// - /// Callback for event that enables feature. Enables feature if config property is true - /// - /// - /// - void FeatureEnableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) - { - if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) - { - if(PropertiesConfig.EnableRoomOnWhenOccupied) - FeatureEnabled = true; - - Debug.Console(1, this, "RoomOnToDefaultSourceWhenOccupied Feature Enabled."); - } - } - - /// - /// Callback for event that enables feature. Disables 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; } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Crestron.SimplSharp; +using Crestron.SimplSharp.Scheduler; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Devices; + +namespace PepperDash.Essentials.Core +{ + /// + /// A device that when linked to a room can power the room on when enabled during scheduled hours. + /// + public class RoomOnToDefaultSourceWhenOccupied : ReconfigurableDevice + { + RoomOnToDefaultSourceWhenOccupiedConfig PropertiesConfig; + + 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(DeviceConfig config) : + base (config) + { + PropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + FeatureEventGroup = new ScheduledEventGroup(this.Key); + + FeatureEventGroup.RetrieveAllEvents(); + + // Add to the global class for tracking + Scheduler.AddEventGroup(FeatureEventGroup); + + AddPostActivationAction(() => + { + // Subscribe to room event to know when RoomOccupancy is set and ready to be subscribed to + if (Room != null) + Room.RoomOccupancyIsSet += new EventHandler(RoomOccupancyIsSet); + + else + Debug.Console(1, this, "Room has no RoomOccupancy object set"); + + var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion"; + + FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.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() + { + SetUpDevice(); + + return base.CustomActivate(); + } + + /// + /// Sets up device based on config values + /// + void SetUpDevice() + { + Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as EssentialsRoomBase; + + if (Room != null) + { + try + { + FeatureEnabledTime = DateTime.Parse(PropertiesConfig.OccupancyStartTime); + + if (FeatureEnabledTime != null) + { + Debug.Console(1, this, "Enabled Time: {0}", FeatureEnabledTime.ToString()); + } + else + Debug.Console(1, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyStartTime); + } + catch (Exception e) + { + Debug.Console(1, this, "Unable to parse OccupancyStartTime property: {0} \n Error: {1}", PropertiesConfig.OccupancyStartTime, e); + } + + try + { + FeatureDisabledTime = DateTime.Parse(PropertiesConfig.OccupancyEndTime); + + if (FeatureDisabledTime != null) + { + Debug.Console(1, this, "Disabled Time: {0}", FeatureDisabledTime.ToString()); + } + else + Debug.Console(1, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyEndTime); + } + catch (Exception e) + { + Debug.Console(1, this, "Unable to parse a DateTime config value \n Error: {1}", e); + } + + if (!PropertiesConfig.EnableRoomOnWhenOccupied) + FeatureEventGroup.ClearAllEvents(); + else + { + AddEnableEventToGroup(); + + AddDisableEventToGroup(); + + FeatureEventGroup.UserGroupCallBack += new ScheduledEventGroup.UserEventGroupCallBack(FeatureEventGroup_UserGroupCallBack); + + FeatureEventGroup.EnableAllEvents(); + } + + FeatureEnabled = CheckIfFeatureShouldBeEnabled(); + } + else + Debug.Console(1, this, "Unable to get room from Device Manager with key: {0}", PropertiesConfig.RoomKey); + } + + + protected override void CustomSetConfig(DeviceConfig config) + { + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + if(newPropertiesConfig != null) + PropertiesConfig = newPropertiesConfig; + + ConfigWriter.UpdateDeviceConfig(config); + + SetUpDevice(); + } + + /// + /// Subscribe to feedback from RoomIsOccupiedFeedback on Room + /// + /// + /// + void RoomOccupancyIsSet(object sender, EventArgs e) + { + if (Room.RoomOccupancy != null) + { + Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; + Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler(RoomIsOccupiedFeedback_OutputChange); + Debug.Console(1, this, "Subscribed to RoomOccupancy status from: '{0}'", Room.Key); + } + } + + void FeatureEventGroup_UserGroupCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + if (SchEvent.Name == FeatureEnableEventName) + { + if (PropertiesConfig.EnableRoomOnWhenOccupied) + FeatureEnabled = true; + + Debug.Console(1, this, "*****Feature Enabled by event.*****"); + } + else if (SchEvent.Name == FeatureDisableEventName) + { + FeatureEnabled = false; + + Debug.Console(1, this, "*****Feature Disabled by event.*****"); + } + } + } + + /// + /// 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(PropertiesConfig.EnableRoomOnWhenOccupied) + { + Debug.Console(1, this, "Current Time: {0} \n FeatureEnabledTime: {1} \n FeatureDisabledTime: {2}", DateTime.Now, FeatureEnabledTime, FeatureDisabledTime); + + if (DateTime.Now.TimeOfDay.CompareTo(FeatureEnabledTime.TimeOfDay) >= 0 && FeatureDisabledTime.TimeOfDay.CompareTo(DateTime.Now.TimeOfDay) > 0) + { + if (SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(DateTime.Now, CalculateDaysOfWeekRecurrence())) + { + enabled = true; + } + } + } + + if(enabled) + Debug.Console(1, this, "*****Feature Enabled*****"); + else + Debug.Console(1, this, "*****Feature Disabled*****"); + + return enabled; + } + + /// + /// Respond to Occupancy status event + /// + /// + /// + void RoomIsOccupiedFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + Debug.Console(1, this, "RoomIsOccupiedFeeback.OutputChange event fired. e.BoolValue: {0}", e.BoolValue); + if(e.BoolValue) + { + // Occupancy detected + + if (FeatureEnabled) + { + // Check room power state first + if (!Room.OnFeedback.BoolValue) + { + Debug.Console(1, this, "Powering Room on to default source"); + Room.RunDefaultPresentRoute(); + } + } + } + } + + 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 + + if(!schEvent.Acknowledgeable) + schEvent.Acknowledgeable = true; + + if(!schEvent.Persistent) + schEvent.Persistent = true; + + schEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); + + // Set config driven properties + + if (schEvent.Name == FeatureEnableEventName) + { + schEvent.Description = "Enables the RoomOnToDefaultSourceWhenOccupiedFeature"; + + var eventRecurrennce = CalculateDaysOfWeekRecurrence(); + + var eventTime = new DateTime(); + + // Check to make sure the date for this event is in the future + if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0) + eventTime = FeatureEnabledTime.AddDays(1); + else + eventTime = FeatureEnabledTime; + + Debug.Console(1, this, "eventTime (before recurrence check): {0}", eventTime); + + // Check day of week against recurrence days and move date ahead as necessary to avoid throwing an exception by trying to set the event + // start date on a day of the week that doesn't match teh recurrence values + while(!SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(eventTime, eventRecurrennce)) + { + eventTime = eventTime.AddDays(1); + Debug.Console(1, this, "eventTime does not fall on a recurrence weekday. eventTime: {0}", eventTime); + } + + schEvent.DateAndTime.SetAbsoluteEventTime(eventTime); + + Debug.Console(1, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); + + CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureEnabledTime, FeatureDisabledTime); + + schEvent.Recurrence.Weekly(eventRecurrennce); + + } + 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); + } + + /// + /// Checks existing event to see if it matches the execution time + /// + /// + /// + bool CheckExistingEventTimeForMatch(ScheduledEvent existingEvent, DateTime newTime) + { + bool isMatch = true; + + // Check to see if hour and minute match + if (existingEvent.DateAndTime.Hour != newTime.Hour || existingEvent.DateAndTime.Minute != newTime.Minute) + return false; + + + return isMatch; + } + + /// + /// Checks existing event to see if it matches the recurrence days + /// + /// + /// + /// + bool CheckExistingEventRecurrenceForMatch(ScheduledEvent existingEvent, ScheduledEventCommon.eWeekDays eWeekdays) + { + bool isMatch = true; + + // Check to see if recurrence matches + if (eWeekdays != existingEvent.Recurrence.RecurrenceDays) + return false; + + return isMatch; + } + + /// + /// 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 + { + // Check if existing event has same time and recurrence as config values + + FeatureEnableEvent = FeatureEventGroup.ScheduledEvents[FeatureEnableEventName]; + Debug.Console(1, this, "Enable event already found in group"); + + // Check config times and days against DateAndTime of existing event. If different, delete existing event and create new event + if(!CheckExistingEventTimeForMatch(FeatureEnableEvent, FeatureEnabledTime) || !CheckExistingEventRecurrenceForMatch(FeatureEnableEvent, CalculateDaysOfWeekRecurrence())) + { + Debug.Console(1, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureEnableEvent.Name); + FeatureEventGroup.DeleteEvent(FeatureEnableEvent); + + FeatureEnableEvent = null; + + CreateEvent(FeatureEnableEvent, FeatureEnableEventName); + } + } + + } + + /// + /// 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"); + + // Check config times against DateAndTime of existing event. If different, delete existing event and create new event + if(!CheckExistingEventTimeForMatch(FeatureDisableEvent, FeatureDisabledTime)) + { + Debug.Console(1, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureDisableEvent.Name); + + FeatureEventGroup.DeleteEvent(FeatureDisableEvent); + + FeatureDisableEvent = null; + + CreateEvent(FeatureDisableEvent, FeatureDisableEventName); + } + } + } + + + /// + /// Calculates the correct bitfield enum value for the event recurrence based on the config values + /// + /// + ScheduledEventCommon.eWeekDays CalculateDaysOfWeekRecurrence() + { + ScheduledEventCommon.eWeekDays value = new ScheduledEventCommon.eWeekDays(); + + if (PropertiesConfig.EnableSunday) + value = value | ScheduledEventCommon.eWeekDays.Sunday; + if (PropertiesConfig.EnableMonday) + value = value | ScheduledEventCommon.eWeekDays.Monday; + if (PropertiesConfig.EnableTuesday) + value = value | ScheduledEventCommon.eWeekDays.Tuesday; + if (PropertiesConfig.EnableWednesday) + value = value | ScheduledEventCommon.eWeekDays.Wednesday; + if (PropertiesConfig.EnableThursday) + value = value | ScheduledEventCommon.eWeekDays.Thursday; + if (PropertiesConfig.EnableFriday) + value = value | ScheduledEventCommon.eWeekDays.Friday; + if (PropertiesConfig.EnableSaturday) + value = value | ScheduledEventCommon.eWeekDays.Saturday; + + return value; + } + + /// + /// Callback for event that enables feature. Enables feature if config property is true + /// + /// + /// + void FeatureEnableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + if(PropertiesConfig.EnableRoomOnWhenOccupied) + FeatureEnabled = true; + + Debug.Console(1, this, "RoomOnToDefaultSourceWhenOccupied Feature Enabled."); + } + } + + /// + /// Callback for event that enables feature. Disables 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/Types/EssentialsRoomBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs similarity index 93% rename from PepperDashEssentials/Room/Types/EssentialsRoomBase.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs index fc177b51..e8193837 100644 --- a/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs @@ -1,297 +1,311 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.Scheduler; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Devices; -using PepperDash.Essentials.Devices.Common.Occupancy; - -namespace PepperDash.Essentials -{ - /// - /// - /// - public abstract class EssentialsRoomBase : ReconfigurableDevice - { - /// - /// - /// - public BoolFeedback OnFeedback { get; private set; } - - /// - /// Fires when the RoomOccupancy object is set - /// - public event EventHandler RoomOccupancyIsSet; - - public BoolFeedback IsWarmingUpFeedback { get; private set; } - public BoolFeedback IsCoolingDownFeedback { get; private set; } - - public IOccupancyStatusProvider RoomOccupancy { get; private set; } - - public bool OccupancyStatusProviderIsRemote { get; private set; } - - protected abstract Func IsWarmingFeedbackFunc { get; } - protected abstract Func IsCoolingFeedbackFunc { get; } - - /// - /// Timer used for informing the UIs of a shutdown - /// - public SecondsCountdownTimer ShutdownPromptTimer { get; private set; } - - /// - /// - /// - public int ShutdownPromptSeconds { get; set; } - public int ShutdownVacancySeconds { get; set; } - public eShutdownType ShutdownType { get; private set; } - - public PepperDash.Essentials.Room.EssentialsRoomEmergencyBase Emergency { get; set; } - - public PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController MicrophonePrivacy { get; set; } - - public string LogoUrl { get; set; } - - protected SecondsCountdownTimer RoomVacancyShutdownTimer { get; private set; } - - public eVacancyMode VacancyMode { get; private set; } - - /// - /// Seconds after vacancy prompt is displayed until shutdown - /// - protected int RoomVacancyShutdownSeconds; - - /// - /// Seconds after vacancy detected until prompt is displayed - /// - protected int RoomVacancyShutdownPromptSeconds; - - /// - /// - /// - protected abstract Func OnFeedbackFunc { get; } - - protected Dictionary SavedVolumeLevels = new Dictionary(); - - /// - /// When volume control devices change, should we zero the one that we are leaving? - /// - public bool ZeroVolumeWhenSwtichingVolumeDevices { get; private set; } - - - public EssentialsRoomBase(DeviceConfig config) - : base(config) - { - // Setup the ShutdownPromptTimer - ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer"); - ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) => - { - if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue) - ShutdownType = eShutdownType.None; - }; - ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered - - ShutdownPromptSeconds = 60; - ShutdownVacancySeconds = 120; - ShutdownType = eShutdownType.None; - - RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); - //RoomVacancyShutdownTimer.IsRunningFeedback.OutputChange += (o, a) => - //{ - // if (!RoomVacancyShutdownTimer.IsRunningFeedback.BoolValue) - // ShutdownType = ShutdownType.Vacancy; - //}; - RoomVacancyShutdownTimer.HasFinished += new EventHandler(RoomVacancyShutdownPromptTimer_HasFinished); // Shutdown is triggered - - RoomVacancyShutdownPromptSeconds = 1500; // 25 min to prompt warning - RoomVacancyShutdownSeconds = 240; // 4 min after prompt will trigger shutdown prompt - VacancyMode = eVacancyMode.None; - - OnFeedback = new BoolFeedback(OnFeedbackFunc); - - IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); - IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); - - AddPostActivationAction(() => - { - if (RoomOccupancy != null) - OnRoomOccupancyIsSet(); - }); - } - - void RoomVacancyShutdownPromptTimer_HasFinished(object sender, EventArgs e) - { - switch (VacancyMode) - { - case eVacancyMode.None: - StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); - break; - case eVacancyMode.InInitialVacancy: - StartRoomVacancyTimer(eVacancyMode.InShutdownWarning); - break; - case eVacancyMode.InShutdownWarning: - { - StartShutdown(eShutdownType.Vacancy); - Debug.Console(0, this, "Shutting Down due to vacancy."); - break; - } - default: - break; - } - } - - /// - /// - /// - /// - public void StartShutdown(eShutdownType type) - { - // Check for shutdowns running. Manual should override other shutdowns - - if (type == eShutdownType.Manual) - ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds; - else if (type == eShutdownType.Vacancy) - ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds; - ShutdownType = type; - ShutdownPromptTimer.Start(); - - Debug.Console(0, this, "ShutdwonPromptTimer Started. Type: {0}. Seconds: {1}", ShutdownType, ShutdownPromptTimer.SecondsToCount); - } - - public void StartRoomVacancyTimer(eVacancyMode mode) - { - if (mode == eVacancyMode.None) - RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownPromptSeconds; - else if (mode == eVacancyMode.InInitialVacancy) - RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownSeconds; - VacancyMode = mode; - RoomVacancyShutdownTimer.Start(); - - Debug.Console(0, this, "Vacancy Timer Started. Mode: {0}. Seconds: {1}", VacancyMode, RoomVacancyShutdownTimer.SecondsToCount); - } - - /// - /// Resets the vacancy mode and shutsdwon the room - /// - public void Shutdown() - { - VacancyMode = eVacancyMode.None; - EndShutdown(); - } - - /// - /// This method is for the derived class to define it's specific shutdown - /// requirements but should not be called directly. It is called by Shutdown() - /// - protected abstract void EndShutdown(); - - - /// - /// Override this to implement a default volume level(s) method - /// - public abstract void SetDefaultLevels(); - - /// - /// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device - /// - /// - public void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes) - { - if (statusProvider == null) - { - Debug.Console(0, this, "ERROR: Occupancy sensor device is null"); - return; - } - - // If status provider is fusion, set flag to remote - if (statusProvider is PepperDash.Essentials.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase) - OccupancyStatusProviderIsRemote = true; - - if(timeoutMinutes > 0) - RoomVacancyShutdownSeconds = timeoutMinutes * 60; - - Debug.Console(1, this, "RoomVacancyShutdownSeconds set to {0}", RoomVacancyShutdownSeconds); - - RoomOccupancy = statusProvider; - - OnRoomOccupancyIsSet(); - - RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; - RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; - - Debug.Console(0, this, "Room Occupancy set to device: '{0}'", (statusProvider as Device).Key); - } - - void OnRoomOccupancyIsSet() - { - var handler = RoomOccupancyIsSet; - if (handler != null) - handler(this, new EventArgs()); - } - - /// - /// To allow base class to power room on to last source - /// - public abstract void PowerOnToDefaultOrLastSource(); - - /// - /// To allow base class to power room on to default source - /// - /// - public abstract bool RunDefaultPresentRoute(); - - void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e) - { - if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false) - { - Debug.Console(1, this, "Notice: Vacancy Detected"); - // Trigger the timer when the room is vacant - StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); - } - else - { - Debug.Console(1, this, "Notice: Occupancy Detected"); - // Reset the timer when the room is occupied - RoomVacancyShutdownTimer.Cancel(); - } - } - - /// - /// Executes when RoomVacancyShutdownTimer expires. Used to trigger specific room actions as needed. Must nullify the timer object when executed - /// - /// - public abstract void RoomVacatedForTimeoutPeriod(object o); - } - - /// - /// To describe the various ways a room may be shutting down - /// - public enum eShutdownType - { - None = 0, - External, - Manual, - Vacancy - } - - public enum eVacancyMode - { - None = 0, - InInitialVacancy, - InShutdownWarning - } - - /// - /// - /// - public enum eWarmingCoolingMode - { - None, - Warming, - Cooling - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.Scheduler; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Devices; + +namespace PepperDash.Essentials.Core +{ + /// + /// + /// + public abstract class EssentialsRoomBase : ReconfigurableDevice + { + /// + /// + /// + public BoolFeedback OnFeedback { get; private set; } + + /// + /// Fires when the RoomOccupancy object is set + /// + public event EventHandler RoomOccupancyIsSet; + + public BoolFeedback IsWarmingUpFeedback { get; private set; } + public BoolFeedback IsCoolingDownFeedback { get; private set; } + + public IOccupancyStatusProvider RoomOccupancy { get; private set; } + + public bool OccupancyStatusProviderIsRemote { get; private set; } + + protected abstract Func IsWarmingFeedbackFunc { get; } + protected abstract Func IsCoolingFeedbackFunc { get; } + + /// + /// The config name of the source list + /// + public string SourceListKey { get; set; } + + /// + /// Timer used for informing the UIs of a shutdown + /// + public SecondsCountdownTimer ShutdownPromptTimer { get; private set; } + + /// + /// + /// + public int ShutdownPromptSeconds { get; set; } + public int ShutdownVacancySeconds { get; set; } + public eShutdownType ShutdownType { get; private set; } + + public EssentialsRoomEmergencyBase Emergency { get; set; } + + public Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; set; } + + public string LogoUrl { get; set; } + + protected SecondsCountdownTimer RoomVacancyShutdownTimer { get; private set; } + + public eVacancyMode VacancyMode { get; private set; } + + /// + /// Seconds after vacancy prompt is displayed until shutdown + /// + protected int RoomVacancyShutdownSeconds; + + /// + /// Seconds after vacancy detected until prompt is displayed + /// + protected int RoomVacancyShutdownPromptSeconds; + + /// + /// + /// + protected abstract Func OnFeedbackFunc { get; } + + protected Dictionary SavedVolumeLevels = new Dictionary(); + + /// + /// When volume control devices change, should we zero the one that we are leaving? + /// + public bool ZeroVolumeWhenSwtichingVolumeDevices { get; private set; } + + + public EssentialsRoomBase(DeviceConfig config) + : base(config) + { + // Setup the ShutdownPromptTimer + ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer"); + ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) => + { + if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue) + ShutdownType = eShutdownType.None; + }; + ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered + + ShutdownPromptSeconds = 60; + ShutdownVacancySeconds = 120; + ShutdownType = eShutdownType.None; + + RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); + //RoomVacancyShutdownTimer.IsRunningFeedback.OutputChange += (o, a) => + //{ + // if (!RoomVacancyShutdownTimer.IsRunningFeedback.BoolValue) + // ShutdownType = ShutdownType.Vacancy; + //}; + RoomVacancyShutdownTimer.HasFinished += new EventHandler(RoomVacancyShutdownPromptTimer_HasFinished); // Shutdown is triggered + + RoomVacancyShutdownPromptSeconds = 1500; // 25 min to prompt warning + RoomVacancyShutdownSeconds = 240; // 4 min after prompt will trigger shutdown prompt + VacancyMode = eVacancyMode.None; + + OnFeedback = new BoolFeedback(OnFeedbackFunc); + + IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); + IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); + + AddPostActivationAction(() => + { + if (RoomOccupancy != null) + OnRoomOccupancyIsSet(); + }); + } + + void RoomVacancyShutdownPromptTimer_HasFinished(object sender, EventArgs e) + { + switch (VacancyMode) + { + case eVacancyMode.None: + StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); + break; + case eVacancyMode.InInitialVacancy: + StartRoomVacancyTimer(eVacancyMode.InShutdownWarning); + break; + case eVacancyMode.InShutdownWarning: + { + StartShutdown(eShutdownType.Vacancy); + Debug.Console(0, this, "Shutting Down due to vacancy."); + break; + } + default: + break; + } + } + + /// + /// + /// + /// + public void StartShutdown(eShutdownType type) + { + // Check for shutdowns running. Manual should override other shutdowns + + if (type == eShutdownType.Manual) + ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds; + else if (type == eShutdownType.Vacancy) + ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds; + ShutdownType = type; + ShutdownPromptTimer.Start(); + + Debug.Console(0, this, "ShutdwonPromptTimer Started. Type: {0}. Seconds: {1}", ShutdownType, ShutdownPromptTimer.SecondsToCount); + } + + public void StartRoomVacancyTimer(eVacancyMode mode) + { + if (mode == eVacancyMode.None) + RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownPromptSeconds; + else if (mode == eVacancyMode.InInitialVacancy) + RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownSeconds; + VacancyMode = mode; + RoomVacancyShutdownTimer.Start(); + + Debug.Console(0, this, "Vacancy Timer Started. Mode: {0}. Seconds: {1}", VacancyMode, RoomVacancyShutdownTimer.SecondsToCount); + } + + /// + /// Resets the vacancy mode and shutsdwon the room + /// + public void Shutdown() + { + VacancyMode = eVacancyMode.None; + EndShutdown(); + } + + /// + /// This method is for the derived class to define it's specific shutdown + /// requirements but should not be called directly. It is called by Shutdown() + /// + protected abstract void EndShutdown(); + + + /// + /// Override this to implement a default volume level(s) method + /// + public abstract void SetDefaultLevels(); + + /// + /// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device + /// + /// + public void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes) + { + if (statusProvider == null) + { + Debug.Console(0, this, "ERROR: Occupancy sensor device is null"); + return; + } + + // If status provider is fusion, set flag to remote + if (statusProvider is Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase) + OccupancyStatusProviderIsRemote = true; + + if(timeoutMinutes > 0) + RoomVacancyShutdownSeconds = timeoutMinutes * 60; + + Debug.Console(1, this, "RoomVacancyShutdownSeconds set to {0}", RoomVacancyShutdownSeconds); + + RoomOccupancy = statusProvider; + + OnRoomOccupancyIsSet(); + + RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; + RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; + + Debug.Console(0, this, "Room Occupancy set to device: '{0}'", (statusProvider as Device).Key); + } + + void OnRoomOccupancyIsSet() + { + var handler = RoomOccupancyIsSet; + if (handler != null) + handler(this, new EventArgs()); + } + + /// + /// To allow base class to power room on to last source + /// + public abstract void PowerOnToDefaultOrLastSource(); + + /// + /// To allow base class to power room on to default source + /// + /// + public abstract bool RunDefaultPresentRoute(); + + void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e) + { + if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false) + { + Debug.Console(1, this, "Notice: Vacancy Detected"); + // Trigger the timer when the room is vacant + StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); + } + else + { + Debug.Console(1, this, "Notice: Occupancy Detected"); + // Reset the timer when the room is occupied + RoomVacancyShutdownTimer.Cancel(); + } + } + + /// + /// Executes when RoomVacancyShutdownTimer expires. Used to trigger specific room actions as needed. Must nullify the timer object when executed + /// + /// + public abstract void RoomVacatedForTimeoutPeriod(object o); + } + + /// + /// To describe the various ways a room may be shutting down + /// + public enum eShutdownType + { + None = 0, + External, + Manual, + Vacancy + } + + public enum eVacancyMode + { + None = 0, + InInitialVacancy, + InShutdownWarning + } + + /// + /// + /// + public enum eWarmingCoolingMode + { + None, + Warming, + Cooling + } + + public abstract class EssentialsRoomEmergencyBase : IKeyed + { + public string Key { get; private set; } + + public EssentialsRoomEmergencyBase(string key) + { + Key = key; + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Interfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Interfaces.cs new file mode 100644 index 00000000..36f390c3 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Interfaces.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core +{ + /// + /// For rooms with in call feedback + /// + public interface IHasInCallFeedback + { + BoolFeedback InCallFeedback { get; } + } + + /// + /// For rooms with a single display + /// + public interface IHasDefaultDisplay + { + IRoutingSinkWithSwitching DefaultDisplay { get; } + } + + /// + /// For rooms with multiple displays + /// + public interface IHasMultipleDisplays + { + Dictionary Displays { get; } + } + + /// + /// For rooms with routing + /// + public interface IRunRouteAction + { + void RunRouteAction(string routeKey); + + void RunRouteAction(string routeKey, Action successCallback); + } + + /// + /// For rooms that default presentation only routing + /// + public interface IRunDefaultPresentRoute + { + bool RunDefaultPresentRoute(); + } + + /// + /// For rooms that have default presentation and calling routes + /// + public interface IRunDefaultCallRoute : IRunDefaultPresentRoute + { + bool RunDefaultCallRoute(); + } + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/MOVED RoomEventArgs.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/MOVED RoomEventArgs.cs deleted file mode 100644 index bca9bb62..00000000 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/MOVED RoomEventArgs.cs +++ /dev/null @@ -1,41 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using Crestron.SimplSharp; - -//namespace PepperDash.Essentials.Core -//{ -// public class EssentialsRoomSourceChangeEventArgs : EventArgs -// { -// public EssentialsRoom Room { get; private set; } -// public IPresentationSource OldSource { get; private set; } -// public IPresentationSource NewSource { get; private set; } - -// public EssentialsRoomSourceChangeEventArgs(EssentialsRoom room, -// IPresentationSource oldSource, IPresentationSource newSource) -// { -// Room = room; -// OldSource = oldSource; -// NewSource = newSource; -// } -// } - - - -// public class EssentialsRoomAudioDeviceChangeEventArgs : EventArgs -// { -// public EssentialsRoom Room { get; private set; } -// public IBasicVolumeControls OldDevice { get; private set; } -// public IBasicVolumeControls NewDevice { get; private set; } - -// public EssentialsRoomAudioDeviceChangeEventArgs(EssentialsRoom room, -// IBasicVolumeControls oldDevice, IBasicVolumeControls newDevice) -// { -// Room = room; -// OldDevice = oldDevice; -// NewDevice = newDevice; -// } -// } - -//} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Occupancy/iOccupancyStatusProvider.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/iOccupancyStatusProvider.cs similarity index 82% rename from essentials-framework/Essentials Devices Common/Essentials Devices Common/Occupancy/iOccupancyStatusProvider.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/iOccupancyStatusProvider.cs index 02054535..f46f10b1 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Occupancy/iOccupancyStatusProvider.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/iOccupancyStatusProvider.cs @@ -6,7 +6,7 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Occupancy +namespace PepperDash.Essentials.Core { public interface IOccupancyStatusProvider { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs index 76745f22..0c642d5c 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs @@ -11,9 +11,25 @@ using PepperDash.Core; namespace PepperDash.Essentials.Core { + + /// + /// The handler type for a Room's SourceInfoChange + /// + public delegate void SourceInfoChangeHandler(/*EssentialsRoomBase room,*/ SourceListItem info, ChangeType type); + + //******************************************************************************************* // Interfaces + /// + /// For rooms with a single presentation source, change event + /// + public interface IHasCurrentSourceInfoChange + { + string CurrentSourceInfoKey { get; set; } + SourceListItem CurrentSourceInfo { get; set; } + event SourceInfoChangeHandler CurrentSourceChange; + } /// /// Defines a class that has a collection of RoutingInputPorts @@ -35,7 +51,7 @@ namespace PepperDash.Essentials.Core /// /// For fixed-source endpoint devices /// - public interface IRoutingSinkNoSwitching : IRoutingInputs + public interface IRoutingSinkNoSwitching : IRoutingInputs, IHasCurrentSourceInfoChange { } @@ -43,7 +59,7 @@ namespace PepperDash.Essentials.Core /// /// Endpoint device like a display, that selects inputs /// - public interface IRoutingSinkWithSwitching : IRoutingSinkNoSwitching + public interface IRoutingSinkWithSwitching : IRoutingSinkNoSwitching, IHasCurrentSourceInfoChange { //void ClearRoute(); void ExecuteSwitch(object inputSelector); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Interfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Interfaces.cs new file mode 100644 index 00000000..946f4f9d --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Interfaces.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; + +namespace PepperDash.Essentials.Core +{ + public interface IHasBasicTriListWithSmartObject + { + BasicTriListWithSmartObject Panel { get; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Audio/GenericAudioOut.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Audio/GenericAudioOut.cs index d9ba06cf..d89a36fc 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Audio/GenericAudioOut.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Audio/GenericAudioOut.cs @@ -15,6 +15,32 @@ namespace PepperDash.Essentials.Devices.Common /// public class GenericAudioOut : Device, IRoutingSinkNoSwitching { + public event SourceInfoChangeHandler CurrentSourceChange; + + public string CurrentSourceInfoKey { get; set; } + public SourceListItem CurrentSourceInfo + { + get + { + return _CurrentSourceInfo; + } + set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSourceChange; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.WillChange); + + _CurrentSourceInfo = value; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + public RoutingInputPort AnyAudioIn { get; private set; } public GenericAudioOut(string key, string name) diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj index ec276366..bed83feb 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj @@ -150,13 +150,8 @@ - - - - - diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Factory/DeviceFactory.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Factory/DeviceFactory.cs index 6c2a86f3..671dec12 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Factory/DeviceFactory.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Factory/DeviceFactory.cs @@ -136,12 +136,12 @@ namespace PepperDash.Essentials.Devices.Common else if (typeName == "inroompc") { - return new InRoomPc(key, name); + return new Core.Devices.InRoomPc(key, name); } else if (typeName == "laptop") { - return new Laptop(key, name); + return new Core.Devices.Laptop(key, name); } else if (typeName == "mockvc") @@ -299,9 +299,9 @@ namespace PepperDash.Essentials.Devices.Common else if (typeName == "microphoneprivacycontroller") { - var props = JsonConvert.DeserializeObject(properties.ToString()); + var props = JsonConvert.DeserializeObject(properties.ToString()); - return new Microphones.MicrophonePrivacyController(key, props); + return new Core.Privacy.MicrophonePrivacyController(key, props); } else if (typeName == "roku") {