diff --git a/PepperDashEssentials/Room/Cotija/CotijaConfig.cs b/PepperDashEssentials/AppServer/CotijaConfig.cs similarity index 100% rename from PepperDashEssentials/Room/Cotija/CotijaConfig.cs rename to PepperDashEssentials/AppServer/CotijaConfig.cs diff --git a/PepperDashEssentials/Room/Cotija/CotijaDdvc01DeviceBridge.cs b/PepperDashEssentials/AppServer/CotijaDdvc01DeviceBridge.cs similarity index 100% rename from PepperDashEssentials/Room/Cotija/CotijaDdvc01DeviceBridge.cs rename to PepperDashEssentials/AppServer/CotijaDdvc01DeviceBridge.cs diff --git a/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs b/PepperDashEssentials/AppServer/CotijaSystemController.cs similarity index 59% rename from PepperDashEssentials/Room/Cotija/CotijaSystemController.cs rename to PepperDashEssentials/AppServer/CotijaSystemController.cs index fc7d8212..b2e3c9dd 100644 --- a/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs +++ b/PepperDashEssentials/AppServer/CotijaSystemController.cs @@ -1,698 +1,828 @@ -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.Room.Cotija; - -namespace PepperDash.Essentials -{ - public class CotijaSystemController : Device - { - WebSocketClient WSClient; - - /// - /// 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(); - - CTimer ServerHeartbeatCheckTimer; - - long ServerHeartbeatInterval = 20000; - - CTimer ServerReconnectTimer; - - long ServerReconnectInterval = 5000; - - 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; - 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); - - } - - /// - /// 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"); - RegisterSystemToServer(); - } - } - - /// - /// - /// - /// - /// - void bridge_ConfigurationIsReady(object sender, EventArgs e) - { - Debug.Console(1, this, "Bridge ready. Registering"); - // send the configuration object to the server - RegisterSystemToServer(); - } - - /// - /// - /// - /// - void ReconnectToServerTimerCallback(object o) - { - RegisterSystemToServer(); - } - - /// - /// Verifies system connection with servers - /// - /// - void AuthorizeSystem(string code) - { - 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."); - 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"); - - CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: - Server address: {0} - System Name: {1} - System UUID: {2} - System User code: {3} - Connected?: {4}", url, name, SystemUuid, - code, conn); - } - - /// - /// Registers the room with the server - /// - /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" - void RegisterSystemToServer() - { - var ready = RegisterLockEvent.Wait(20000); - if (!ready) - { - Debug.Console(1, this, "RegisterSystemToServer failed to enter after 20 seconds. Ignoring"); - return; - } - RegisterLockEvent.Reset(); - - try - { - 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); - - -# warning I commented this out...read only property...Bad merge? JTA 2018-08-13 ; - //confObject.Info.RuntimeInfo.OsVersion = Crestron.SimplSharp.CrestronEnvironment.OSVersion.Firmware; - - string postBody = JsonConvert.SerializeObject(confObject); - SystemUuid = confObject.SystemUuid; - - if (string.IsNullOrEmpty(postBody)) - { - Debug.Console(1, this, "ERROR: Config body is empty. Cannot register with server."); - } - else - { - var regClient = new HttpClient(); - regClient.Verbose = true; - regClient.KeepAlive = true; - - string url = string.Format("http://{0}/api/system/join/{1}", Config.ServerUrl, SystemUuid); - Debug.Console(1, this, "Joining server at {0}", url); - - HttpClientRequest request = new HttpClientRequest(); - request.Url.Parse(url); - request.RequestType = RequestType.Post; - request.Header.SetHeaderValue("Content-Type", "application/json"); - request.ContentString = postBody; - - var err = regClient.DispatchAsync(request, RegistrationConnectionCallback); - } - - } - catch (Exception e) - { - Debug.Console(0, this, "ERROR: Initilizing Room: {0}", e); - RegisterLockEvent.Set(); - StartReconnectTimer(); - } - - } - - /// - /// 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 }); - Debug.Console(1, this, "Message TX: {0}", message); - var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); - WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - //WSClient.SendAsync(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - } - - } - - /// - /// Disconnects the SSE Client and stops the heartbeat timer - /// - /// - void DisconnectStreamClient(string command) - { - //if(SseClient != null) - // SseClient.Disconnect(); - - if (WSClient != null && WSClient.Connected) - WSClient.Disconnect(); - - if (ServerHeartbeatCheckTimer != null) - { - ServerHeartbeatCheckTimer.Stop(); - - ServerHeartbeatCheckTimer = null; - } - } - - /// - /// The callback that fires when we get a response from our registration attempt - /// - /// - /// - void RegistrationConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err) - { - CheckHttpDebug(resp, err); - Debug.Console(1, this, "RegistrationConnectionCallback: {0}", err); - try - { - if (resp != null && resp.Code == 200) - { - if(ServerReconnectTimer != null) - { - ServerReconnectTimer.Stop(); - ServerReconnectTimer = null; - } - - // Success here! - ConnectStreamClient(); - } - else - { - if (resp != null) - Debug.Console(1, this, "Response from server: {0}\n{1}", resp.Code, err); - else - { - Debug.Console(1, this, "Null response received from server."); - } - StartReconnectTimer(); - } - } - catch (Exception e) - { - Debug.Console(1, this, "Error Initializing Stream Client: {0}", e); - StartReconnectTimer(); - } - RegisterLockEvent.Set(); - } - - /// - /// 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; - } - StartReconnectTimer(); - } - - /// - /// - /// - /// - /// - void StartReconnectTimer() - { - // Start the reconnect timer - if (ServerReconnectTimer == null) - { - ServerReconnectTimer = new CTimer(ReconnectToServerTimerCallback, null, ServerReconnectInterval, ServerReconnectInterval); - Debug.Console(1, this, "Reconnect Timer Started."); - } - ServerReconnectTimer.Reset(ServerReconnectInterval, ServerReconnectInterval); - } - - /// - /// - /// - /// - /// - void ResetOrStartHearbeatTimer() - { - if (ServerHeartbeatCheckTimer == null) - { - ServerHeartbeatCheckTimer = new CTimer(HeartbeatExpiredTimerCallback, null, ServerHeartbeatInterval, ServerHeartbeatInterval); - - Debug.Console(1, this, "Heartbeat Timer Started."); - } - - ServerHeartbeatCheckTimer.Reset(ServerHeartbeatInterval, ServerHeartbeatInterval); - } - - - /// - /// Connects the SSE Client - /// - /// - void ConnectStreamClient() - { - Debug.Console(0, this, "Initializing Stream client to server."); - - if (WSClient == null) - { - WSClient = new WebSocketClient(); - } - WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); - WSClient.Connect(); - Debug.Console(0, this, "Websocket connected"); - WSClient.ReceiveCallBack = WebsocketReceiveCallback; - //WSClient.SendCallBack = WebsocketSendCallback; - WSClient.ReceiveAsync(); - } - - /// - /// Resets reconnect timer and updates usercode - /// - /// - void HandleHeartBeat(JToken content) - { - SendMessageToServer(JObject.FromObject(new - { - type = "/heartbeat-ack" - })); - - 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) - { - Debug.Console(0, this, "------ Begin HTTP Debug ---------------------------------------"); - Debug.Console(0, this, "HTTP Response URL: {0}", r.ResponseUrl.ToString()); - Debug.Console(0, this, "HTTP Response 'error' {0}", e); - Debug.Console(0, this, "HTTP Response code: {0}", r.Code); - Debug.Console(0, this, "HTTP Response content: \r{0}", r.ContentString); - Debug.Console(0, this, "------ End HTTP Debug -----------------------------------------"); - } - } - - /// - /// - /// - /// - /// - /// - /// - int WebsocketReceiveCallback(byte[] data, uint length, WebSocketClient.WEBSOCKET_PACKET_TYPES opcode, - WebSocketClient.WEBSOCKET_RESULT_CODES err) - { - var rx = System.Text.Encoding.UTF8.GetString(data, 0, (int)length); - if(rx.Length > 0) - ParseStreamRx(rx); - WSClient.ReceiveAsync(); - return 1; - } - - /// - /// Callback to catch possible errors in sending via the websocket - /// - /// - /// - int WebsocketSendCallback(Crestron.SimplSharp.CrestronWebSocketClient.WebSocketClient.WEBSOCKET_RESULT_CODES result) - { - Debug.Console(1, this, "SendCallback result: {0}", result); - - return 1; - } - - /// - /// - /// - /// - /// - void ParseStreamRx(string message) - { - if(string.IsNullOrEmpty(message)) - return; - - Debug.Console(1, this, "Message RX: '{0}'", message); - try - { - var messageObj = JObject.Parse(message); - - var type = messageObj["type"].Value(); - - if (type == "hello") - { - ResetOrStartHearbeatTimer(); - } - else if (type == "/system/heartbeat") - { - HandleHeartBeat(messageObj["content"]); - } - else if (type == "close") - { - WSClient.Disconnect(); - - ServerHeartbeatCheckTimer.Stop(); - // Start the reconnect timer - StartReconnectTimer(); - } - 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.Cotija; + +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(); + + CTimer ServerHeartbeatCheckTimer; + + long ServerHeartbeatInterval = 20000; + + CTimer ServerReconnectTimer; + + long ServerReconnectInterval = 5000; + + 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); + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + + } + + /// + /// 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.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"); + + CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: + Server address: {0} + System Name: {1} + System UUID: {2} + System User code: {3} + Connected?: {4}", url, name, SystemUuid, + code, conn); + } + + /// + /// 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 }); + Debug.Console(1, this, "Message TX: {0}", message); + 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.Connected) + { + Debug.Console(1, this, "Cannot send. Not connected {0}"); + } + } + + /// + /// 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; + + Debug.Console(1, this, "Message RX: {0}", message); + 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 == "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/Room/Cotija/DeviceTypeInterfaces/IChannelExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs similarity index 76% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IChannelExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs index b26a7f99..dd23d85a 100644 --- a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IChannelExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IChannelExtensions.cs @@ -14,9 +14,9 @@ namespace PepperDash.Essentials.Room.Cotija { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); - controller.AddAction(prefix + "chanup", new PressAndHoldAction(dev.ChannelUp)); - controller.AddAction(prefix + "chandown", new PressAndHoldAction(dev.ChannelDown)); - controller.AddAction(prefix + "lastchan", new PressAndHoldAction(dev.LastChannel)); + controller.AddAction(prefix + "chanUp", new PressAndHoldAction(dev.ChannelUp)); + controller.AddAction(prefix + "chanDown", new PressAndHoldAction(dev.ChannelDown)); + controller.AddAction(prefix + "lastChan", new PressAndHoldAction(dev.LastChannel)); controller.AddAction(prefix + "guide", new PressAndHoldAction(dev.Guide)); controller.AddAction(prefix + "info", new PressAndHoldAction(dev.Info)); controller.AddAction(prefix + "exit", new PressAndHoldAction(dev.Exit)); @@ -26,9 +26,9 @@ namespace PepperDash.Essentials.Room.Cotija { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); - controller.RemoveAction(prefix + "chanup"); - controller.RemoveAction(prefix + "chandown"); - controller.RemoveAction(prefix + "lastchan"); + controller.RemoveAction(prefix + "chanUp"); + controller.RemoveAction(prefix + "chanDown"); + controller.RemoveAction(prefix + "lastChan"); controller.RemoveAction(prefix + "guide"); controller.RemoveAction(prefix + "info"); controller.RemoveAction(prefix + "exit"); diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IColorExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IColorExtensions.cs similarity index 100% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IColorExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/IColorExtensions.cs diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IDPadExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDPadExtensions.cs similarity index 100% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IDPadExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDPadExtensions.cs diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IDvrExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDvrExtensions.cs similarity index 100% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IDvrExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/IDvrExtensions.cs diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs similarity index 86% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs index 7246cb51..376ed57b 100644 --- a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/INumericExtensions.cs @@ -24,8 +24,8 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "num7", new PressAndHoldAction(dev.Digit0)); controller.AddAction(prefix + "num8", new PressAndHoldAction(dev.Digit0)); controller.AddAction(prefix + "num9", new PressAndHoldAction(dev.Digit0)); - controller.AddAction(prefix + "dash", new PressAndHoldAction(dev.KeypadAccessoryButton1)); - controller.AddAction(prefix + "enter", new PressAndHoldAction(dev.KeypadAccessoryButton2)); + controller.AddAction(prefix + "numDash", new PressAndHoldAction(dev.KeypadAccessoryButton1)); + controller.AddAction(prefix + "numEnter", new PressAndHoldAction(dev.KeypadAccessoryButton2)); // Deal with the Accessory functions on the numpad later } @@ -43,8 +43,8 @@ namespace PepperDash.Essentials.Room.Cotija controller.RemoveAction(prefix + "num7"); controller.RemoveAction(prefix + "num8"); controller.RemoveAction(prefix + "num9"); - controller.RemoveAction(prefix + "dash"); - controller.RemoveAction(prefix + "enter"); + controller.RemoveAction(prefix + "numDash"); + controller.RemoveAction(prefix + "numEnter"); } } } \ No newline at end of file diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IPowerExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs similarity index 66% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IPowerExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs index 68b36675..732d2740 100644 --- a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/IPowerExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/IPowerExtensions.cs @@ -14,18 +14,18 @@ namespace PepperDash.Essentials.Room.Cotija { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); - controller.AddAction(prefix + "poweron", new Action(dev.PowerOn)); - controller.AddAction(prefix + "poweroff", new Action(dev.PowerOff)); - controller.AddAction(prefix + "powertoggle", new Action(dev.PowerToggle)); + controller.AddAction(prefix + "powerOn", new Action(dev.PowerOn)); + controller.AddAction(prefix + "powerOff", new Action(dev.PowerOff)); + controller.AddAction(prefix + "powerToggle", new Action(dev.PowerToggle)); } public static void UnlinkActions(this IPower dev, CotijaSystemController controller) { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); - controller.RemoveAction(prefix + "poweron"); - controller.RemoveAction(prefix + "poweroff"); - controller.RemoveAction(prefix + "powertoggle"); + controller.RemoveAction(prefix + "powerOn"); + controller.RemoveAction(prefix + "powerOff"); + controller.RemoveAction(prefix + "powerToggle"); } } diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs similarity index 87% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs index 7fa7d1b1..99198fa6 100644 --- a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ISetTopBoxControlsExtensions.cs @@ -14,7 +14,7 @@ namespace PepperDash.Essentials.Room.Cotija { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); - controller.AddAction(prefix + "dvrlist", new PressAndHoldAction(dev.DvrList)); + controller.AddAction(prefix + "dvrList", new PressAndHoldAction(dev.DvrList)); controller.AddAction(prefix + "replay", new PressAndHoldAction(dev.Replay)); } @@ -22,7 +22,7 @@ namespace PepperDash.Essentials.Room.Cotija { var prefix = string.Format(@"/device/{0}/", (dev as IKeyed).Key); - controller.RemoveAction(prefix + "dvrlist"); + controller.RemoveAction(prefix + "dvrList"); controller.RemoveAction(prefix + "replay"); } } diff --git a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/ITransportExtensions.cs b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs similarity index 86% rename from PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/ITransportExtensions.cs rename to PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs index 34bda457..9463d95f 100644 --- a/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/ITransportExtensions.cs +++ b/PepperDashEssentials/AppServer/DeviceTypeInterfaces/ITransportExtensions.cs @@ -17,8 +17,8 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "play", new PressAndHoldAction(dev.Play)); controller.AddAction(prefix + "pause", new PressAndHoldAction(dev.Pause)); controller.AddAction(prefix + "stop", new PressAndHoldAction(dev.Stop)); - controller.AddAction(prefix + "prevtrack", new PressAndHoldAction(dev.ChapPlus)); - controller.AddAction(prefix + "nexttrack", new PressAndHoldAction(dev.ChapMinus)); + controller.AddAction(prefix + "prevTrack", new PressAndHoldAction(dev.ChapPlus)); + controller.AddAction(prefix + "nextTrack", new PressAndHoldAction(dev.ChapMinus)); controller.AddAction(prefix + "rewind", new PressAndHoldAction(dev.Rewind)); controller.AddAction(prefix + "ffwd", new PressAndHoldAction(dev.FFwd)); controller.AddAction(prefix + "record", new PressAndHoldAction(dev.Record)); @@ -31,8 +31,8 @@ namespace PepperDash.Essentials.Room.Cotija controller.RemoveAction(prefix + "play"); controller.RemoveAction(prefix + "pause"); controller.RemoveAction(prefix + "stop"); - controller.RemoveAction(prefix + "prevtrack"); - controller.RemoveAction(prefix + "nexttrack"); + controller.RemoveAction(prefix + "prevTrack"); + controller.RemoveAction(prefix + "nextTrack"); controller.RemoveAction(prefix + "rewind"); controller.RemoveAction(prefix + "ffwd"); controller.RemoveAction(prefix + "record"); diff --git a/PepperDashEssentials/AppServer/Interfaces.cs b/PepperDashEssentials/AppServer/Interfaces.cs new file mode 100644 index 00000000..df651ef8 --- /dev/null +++ b/PepperDashEssentials/AppServer/Interfaces.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.Room.Cotija +{ + /// + /// Represents a room whose configuration is derived from runtime data, + /// perhaps from another program, and that the data may not be fully + /// available at startup. + /// + public interface IDelayedConfiguration + { + event EventHandler ConfigurationIsReady; + } +} + +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/AtcDdvc01Messenger.cs b/PepperDashEssentials/AppServer/Messengers/AtcDdvc01Messenger.cs new file mode 100644 index 00000000..c1491f83 --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/AtcDdvc01Messenger.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.EthernetCommunication; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.Codec; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + public class Ddvc01AtcMessenger : MessengerBase + { + BasicTriList EISC; + + const uint BDialHangup = 221; + const uint BIncomingAnswer = 251; + const uint BIncomingReject = 252; + const uint BSpeedDial1 = 241; + const uint BSpeedDial2 = 242; + const uint BSpeedDial3 = 243; + const uint BSpeedDial4 = 244; + + /// + /// 201 + /// + const uint SCurrentDialString = 201; + /// + /// 211 + /// + const uint SCurrentCallString = 211; + /// + /// 221 + /// + const uint SHookState = 221; + + /// + /// + /// + Dictionary DTMFMap = new Dictionary + { + { "1", 201 }, + { "2", 202 }, + { "3", 203 }, + { "4", 204 }, + { "5", 205 }, + { "6", 206 }, + { "7", 207 }, + { "8", 208 }, + { "9", 209 }, + { "0", 210 }, + { "*", 211 }, + { "#", 212 }, + }; + + CodecActiveCallItem CurrentCallItem; + + + /// + /// + /// + /// + /// + public Ddvc01AtcMessenger(string key, BasicTriList eisc, string messagePath) + : base(key, messagePath) + { + EISC = eisc; + + CurrentCallItem = new CodecActiveCallItem(); + CurrentCallItem.Type = eCodecCallType.Audio; + CurrentCallItem.Id = "-audio-"; + } + + /// + /// + /// + void SendFullStatus() + { + this.PostStatusMessage(new + { + calls = GetCurrentCallList(), + callStatus = EISC.GetString(SHookState), + currentCallString = EISC.GetString(SCurrentCallString), + currentDialString = EISC.GetString(SCurrentDialString), + }); + } + + /// + /// + /// + /// + protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + { + Action send = this.PostStatusMessage; + EISC.SetStringSigAction(SCurrentDialString, s => send(new { currentDialString = s })); + + EISC.SetStringSigAction(SHookState, s => + { + CurrentCallItem.Status = (eCodecCallStatus)Enum.Parse(typeof(eCodecCallStatus), s, true); + GetCurrentCallList(); + send(new + { + calls = GetCurrentCallList(), + callStatus = s + }); + }); + + EISC.SetStringSigAction(SCurrentCallString, s => + { + CurrentCallItem.Name = s; + CurrentCallItem.Number = s; + send(new + { + calls = GetCurrentCallList(), + currentCallString = s + }); + }); + + // Add press and holds using helper + Action addPHAction = (s, u) => + AppServerController.AddAction(MessagePath + s, new PressAndHoldAction(b => EISC.SetBool(u, b))); + + // Add straight pulse calls + Action addAction = (s, u) => + AppServerController.AddAction(MessagePath + s, new Action(() => EISC.PulseBool(u, 100))); + addAction("/endCall", BDialHangup); + addAction("/incomingAnswer", BIncomingAnswer); + addAction("/incomingReject", BIncomingReject); + addAction("/speedDial1", BSpeedDial1); + addAction("/speedDial2", BSpeedDial2); + addAction("/speedDial3", BSpeedDial3); + addAction("/speedDial4", BSpeedDial4); + + // Get status + AppServerController.AddAction(MessagePath + "/fullStatus", new Action(SendFullStatus)); + // Dial on string + AppServerController.AddAction(MessagePath + "/dial", new Action(s => EISC.SetString(SCurrentDialString, s))); + // Pulse DTMF + AppServerController.AddAction(MessagePath + "/dtmf", new Action(s => + { + if (DTMFMap.ContainsKey(s)) + { + EISC.PulseBool(DTMFMap[s], 100); + } + })); + } + + /// + /// Turns the + /// + /// + List GetCurrentCallList() + { + if (CurrentCallItem.Status == eCodecCallStatus.Disconnected) + { + return new List(); + } + else + { + return new List() { CurrentCallItem }; + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs b/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs new file mode 100644 index 00000000..f9833afb --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Core; + +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Provides a messaging bridge for a VideoCodecBase + /// + public abstract class MessengerBase : IKeyed + { + public string Key { get; private set; } + + /// + /// + /// + public CotijaSystemController AppServerController { get; private set; } + + public string MessagePath { get; private set; } + + /// + /// + /// + /// + public MessengerBase(string key, string messagePath) + { + Key = key; + + if (string.IsNullOrEmpty(messagePath)) + throw new ArgumentException("messagePath must not be empty or null"); + + MessagePath = messagePath; + } + + + /// + /// Registers this messenger with appserver controller + /// + /// + public void RegisterWithAppServer(CotijaSystemController appServerController) + { + if (appServerController == null) + throw new ArgumentNullException("appServerController"); + + AppServerController = appServerController; + CustomRegisterWithAppServer(AppServerController); + } + + /// + /// Implemented in extending classes. Wire up API calls and feedback here + /// + /// + abstract protected void CustomRegisterWithAppServer(CotijaSystemController appServerController); + + /// + /// Helper for posting status message + /// + /// The contents of the content object + protected void PostStatusMessage(object contentObject) + { + if (AppServerController != null) + { + AppServerController.SendMessageToServer(JObject.FromObject(new + { + type = MessagePath, + content = contentObject + })); + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs b/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs new file mode 100644 index 00000000..6f59cf05 --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs @@ -0,0 +1,99 @@ +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.Monitoring; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + public class SystemMonitorMessenger : MessengerBase + { + public SystemMonitorController SysMon { get; private set; } + + public SystemMonitorMessenger(string key, SystemMonitorController sysMon, string messagePath) + : base(key, messagePath) + { + if (sysMon == null) + throw new ArgumentNullException("sysMon"); + + SysMon = sysMon; + + SysMon.SystemMonitorPropertiesChanged += new EventHandler(SysMon_SystemMonitorPropertiesChanged); + + foreach (var p in SysMon.ProgramStatusFeedbackCollection) + { + p.Value.AggregatedProgramInfoFeedback.OutputChange += new EventHandler(AggregatedProgramInfoFeedback_OutputChange); + } + } + + /// + /// Posts the program information message + /// + /// + /// + void AggregatedProgramInfoFeedback_OutputChange(object sender, PepperDash.Essentials.Core.FeedbackEventArgs e) + { + SendProgramInfoStatusMessage(e.StringValue); + } + + // Deserializes the program info into an object that can be setn in a status message + void SendProgramInfoStatusMessage(string serializedProgramInfo) + { + var programInfo = JsonConvert.DeserializeObject(serializedProgramInfo); + + Debug.Console(2, "Posting Status Message: {0}", programInfo.ToString()); + + PostStatusMessage(programInfo); + } + + /// + /// Posts the system monitor properties + /// + /// + /// + void SysMon_SystemMonitorPropertiesChanged(object sender, EventArgs e) + { + SendSystemMonitorStatusMessage(); + } + + void SendFullStatusMessage() + { + SendSystemMonitorStatusMessage(); + + foreach (var p in SysMon.ProgramStatusFeedbackCollection) + { + SendProgramInfoStatusMessage(p.Value.AggregatedProgramInfoFeedback.StringValue); + } + } + + void SendSystemMonitorStatusMessage() + { + Debug.Console(2, "Posting System Monitor Status Message."); + + // This takes a while, launch a new thread + CrestronInvoke.BeginInvoke((o) => + { + PostStatusMessage(new + { + timeZone = SysMon.TimeZoneFeedback.IntValue, + timeZoneName = SysMon.TimeZoneTextFeedback.StringValue, + ioControllerVersion = SysMon.IOControllerVersionFeedback.StringValue, + snmpVersion = SysMon.SnmpVersionFeedback.StringValue, + bacnetVersion = SysMon.BACnetAppVersionFeedback.StringValue, + controllerVersion = SysMon.ControllerVersionFeedback.StringValue + }); + }); + } + + protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + { + AppServerController.AddAction(MessagePath + "/fullStatus", new Action(SendFullStatusMessage)); + } + + } +} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs b/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs new file mode 100644 index 00000000..3076cb56 --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Provides a messaging bridge for a VideoCodecBase + /// + public class VideoCodecBaseMessenger : MessengerBase + { + /// + /// + /// + public VideoCodecBase Codec { get; private set; } + + /// + /// + /// + /// + public VideoCodecBaseMessenger(string key, VideoCodecBase codec, string messagePath) + : base(key, messagePath) + { + if (codec == null) + throw new ArgumentNullException("codec"); + + Codec = codec; + codec.CallStatusChange += new EventHandler(codec_CallStatusChange); + codec.IsReadyChange += new EventHandler(codec_IsReadyChange); + + var dirCodec = codec as IHasDirectory; + if (dirCodec != null) + { + dirCodec.DirectoryResultReturned += new EventHandler(dirCodec_DirectoryResultReturned); + } + } + + /// + /// + /// + /// + /// + void dirCodec_DirectoryResultReturned(object sender, DirectoryEventArgs e) + { + var dir = e.Directory; + PostStatusMessage(new + { + currentDirectory = e.Directory + }); + } + + /// + /// + /// + /// + /// + void codec_IsReadyChange(object sender, EventArgs e) + { + PostStatusMessage(new + { + isReady = true + }); + } + + /// + /// Called from base's RegisterWithAppServer method + /// + /// + protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + { + appServerController.AddAction("/device/videoCodec/isReady", new Action(SendIsReady)); + appServerController.AddAction("/device/videoCodec/fullStatus", new Action(SendVtcFullMessageObject)); + appServerController.AddAction("/device/videoCodec/dial", new Action(s => Codec.Dial(s))); + appServerController.AddAction("/device/videoCodec/endCallById", new Action(s => + { + var call = GetCallWithId(s); + if (call != null) + Codec.EndCall(call); + })); + appServerController.AddAction(MessagePath + "/endAllCalls", new Action(Codec.EndAllCalls)); + appServerController.AddAction(MessagePath + "/dtmf", new Action(s => Codec.SendDtmf(s))); + appServerController.AddAction(MessagePath + "/rejectById", new Action(s => + { + var call = GetCallWithId(s); + if (call != null) + Codec.RejectCall(call); + })); + appServerController.AddAction(MessagePath + "/acceptById", new Action(s => + { + var call = GetCallWithId(s); + if (call != null) + Codec.AcceptCall(call); + })); + appServerController.AddAction(MessagePath + "/directoryRoot", new Action(GetDirectoryRoot)); + appServerController.AddAction(MessagePath + "/directoryById", new Action(s => GetDirectory(s))); + appServerController.AddAction(MessagePath + "/directorySearch", new Action(s => DirectorySearch(s))); + appServerController.AddAction(MessagePath + "/privacyModeOn", new Action(Codec.PrivacyModeOn)); + appServerController.AddAction(MessagePath + "/privacyModeOff", new Action(Codec.PrivacyModeOff)); + appServerController.AddAction(MessagePath + "/privacyModeToggle", new Action(Codec.PrivacyModeToggle)); + appServerController.AddAction(MessagePath + "/sharingStart", new Action(Codec.StartSharing)); + appServerController.AddAction(MessagePath + "/sharingStop", new Action(Codec.StopSharing)); + appServerController.AddAction(MessagePath + "/standbyOn", new Action(Codec.StandbyActivate)); + appServerController.AddAction(MessagePath + "/standbyOff", new Action(Codec.StandbyDeactivate)); + } + + public void GetFullStatusMessage() + { + + } + + /// + /// Helper to grab a call with string ID + /// + /// + /// + CodecActiveCallItem GetCallWithId(string id) + { + return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id); + } + + /// + /// + /// + /// + void DirectorySearch(string s) + { + var dirCodec = Codec as IHasDirectory; + if (dirCodec != null) + { + dirCodec.SearchDirectory(s); + } + } + + /// + /// + /// + /// + void GetDirectory(string id) + { + var dirCodec = Codec as IHasDirectory; + if(dirCodec == null) + { + return; + } + dirCodec.GetDirectoryFolderContents(id); + } + + /// + /// + /// + void GetDirectoryRoot() + { + var dirCodec = Codec as IHasDirectory; + if (dirCodec == null) + { + // do something else? + return; + } + if (!dirCodec.PhonebookSyncState.InitialSyncComplete) + { + PostStatusMessage(new + { + initialSyncComplete = false + }); + return; + } + + PostStatusMessage(new + { + currentDirectory = dirCodec.DirectoryRoot + }); + } + + /// + /// Handler for codec changes + /// + void codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) + { + SendVtcFullMessageObject(); + } + + /// + /// + /// + void SendIsReady() + { + PostStatusMessage(new + { + isReady = Codec.IsReady + }); + } + + /// + /// Helper method to build call status for vtc + /// + /// + void SendVtcFullMessageObject() + { + if (!Codec.IsReady) + { + return; + } + + var info = Codec.CodecInfo; + PostStatusMessage(new + { + isInCall = Codec.IsInCall, + privacyModeIsOn = Codec.PrivacyModeIsOnFeedback.BoolValue, + sharingContentIsOn = Codec.SharingContentIsOnFeedback.BoolValue, + sharingSource = Codec.SharingSourceFeedback.StringValue, + standbyIsOn = Codec.StandbyIsOnFeedback.StringValue, + calls = Codec.ActiveCalls, + info = new + { + autoAnswerEnabled = info.AutoAnswerEnabled, + e164Alias = info.E164Alias, + h323Id = info.H323Id, + ipAddress = info.IpAddress, + sipPhoneNumber = info.SipPhoneNumber, + sipURI = info.SipUri + }, + showSelfViewByDefault = Codec.ShowSelfViewByDefault, + hasDirectory = Codec is IHasDirectory + }); + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Cotija/RoomBridges/CotijaBridgeBase.cs b/PepperDashEssentials/AppServer/RoomBridges/CotijaBridgeBase.cs similarity index 100% rename from PepperDashEssentials/Room/Cotija/RoomBridges/CotijaBridgeBase.cs rename to PepperDashEssentials/AppServer/RoomBridges/CotijaBridgeBase.cs diff --git a/PepperDashEssentials/Room/Cotija/RoomBridges/CotijaDdvc01RoomBridge.cs b/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs similarity index 67% rename from PepperDashEssentials/Room/Cotija/RoomBridges/CotijaDdvc01RoomBridge.cs rename to PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs index 48bd33fa..55c1f61b 100644 --- a/PepperDashEssentials/Room/Cotija/RoomBridges/CotijaDdvc01RoomBridge.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp; +using Crestron.SimplSharp.Reflection; using Crestron.SimplSharpPro.EthernetCommunication; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; +using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Room.Config; @@ -25,6 +27,14 @@ namespace PepperDash.Essentials.Room.Cotija /// public const uint RoomIsOn = 301; + /// + /// 41 + /// + public const uint PromptForCode = 41; + /// + /// 42 + /// + public const uint ClientJoined = 42; /// /// 51 /// @@ -59,14 +69,15 @@ namespace PepperDash.Essentials.Room.Cotija /// 63 /// public const uint ShutdownStart = 63; - - - /// /// 72 /// public const uint SourceHasChanged = 72; /// + /// 261 - The start of the range of speed dial visibles + /// + public const uint SpeedDialVisibleStartJoin = 261; + /// /// 501 /// public const uint ConfigIsReady = 501; @@ -91,6 +102,16 @@ namespace PepperDash.Essentials.Room.Cotija /// 71 /// public const uint SelectedSourceKey = 71; + + /// + /// 241 + /// + public const uint SpeedDialNameStartJoin = 241; + + /// + /// 251 + /// + public const uint SpeedDialNumberStartJoin = 251; /// /// 501 @@ -144,6 +165,8 @@ namespace PepperDash.Essentials.Room.Cotija CotijaDdvc01DeviceBridge SourceBridge; + Ddvc01AtcMessenger AtcMessenger; + /// /// @@ -181,6 +204,10 @@ namespace PepperDash.Essentials.Room.Cotija SetupFunctions(); SetupFeedbacks(); + var key = this.Key + "-" + Parent.Key; + AtcMessenger = new Ddvc01AtcMessenger(key, EISC, "/device/audioCodec"); + AtcMessenger.RegisterWithAppServer(Parent); + EISC.SigChange += EISC_SigChange; EISC.OnlineStatusChange += (o, a) => { @@ -215,8 +242,6 @@ namespace PepperDash.Essentials.Room.Cotija } }, "mobilebridgedump", "Dumps DDVC01 bridge EISC data b,u,s", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => LoadConfigValues(), "loadddvc", "", ConsoleAccessLevelEnum.AccessOperator); - return base.CustomActivate(); } @@ -226,6 +251,9 @@ namespace PepperDash.Essentials.Room.Cotija /// void SetupFunctions() { +#warning need join numbers for these + Parent.AddAction(@"/room/room1/promptForCode", new Action(() => EISC.PulseBool(BoolJoin.PromptForCode))); + Parent.AddAction(@"/room/room1/clientJoined", new Action(() => EISC.PulseBool(BoolJoin.ClientJoined))); Parent.AddAction(@"/room/room1/status", new Action(SendFullStatus)); @@ -235,13 +263,16 @@ namespace PepperDash.Essentials.Room.Cotija EISC.PulseBool(BoolJoin.SourceHasChanged); })); -#warning CHANGE to activityshare. Perhaps Parent.AddAction(@"/room/room1/defaultsource", new Action(() => EISC.PulseBool(BoolJoin.ActivitySharePress))); + Parent.AddAction(@"/room/room1/activityVideo", new Action(() => + EISC.PulseBool(BoolJoin.ActivityVideoCallPress))); + Parent.AddAction(@"/room/room1/activityPhone", new Action(() => + EISC.PulseBool(BoolJoin.ActivityPhoneCallPress))); - Parent.AddAction(@"/room/room1/masterVolumeLevel", new Action(u => + Parent.AddAction(@"/room/room1/volumes/master/level", new Action(u => EISC.SetUshort(UshortJoin.MasterVolumeLevel, u))); - Parent.AddAction(@"/room/room1/masterVolumeMuteToggle", new Action(() => + Parent.AddAction(@"/room/room1/volumes/master/muteToggle", new Action(() => EISC.PulseBool(BoolJoin.MasterVolumeIsMuted))); Parent.AddAction(@"/room/room1/shutdownStart", new Action(() => @@ -252,6 +283,24 @@ namespace PepperDash.Essentials.Room.Cotija EISC.PulseBool(BoolJoin.ShutdownCancel))); } + /// + /// + /// + /// + void SetupSourceFunctions(string devKey) + { + SourceDeviceMapDictionary sourceJoinMap = new SourceDeviceMapDictionary(); + + var prefix = string.Format("/device/{0}/", devKey); + + foreach (var item in sourceJoinMap) + { + var join = item.Value; + Parent.AddAction(string.Format("{0}{1}", prefix, item.Key), new PressAndHoldAction(b => EISC.SetBool(join, b))); + } + } + + /// /// Links feedbacks to whatever is gonna happen! /// @@ -273,16 +322,29 @@ namespace PepperDash.Essentials.Room.Cotija // Volume things EISC.SetUShortSigAction(UshortJoin.MasterVolumeLevel, u => - PostStatusMessage(new - { - masterVolumeLevel = u - })); + PostStatusMessage(new + { + volumes = new + { + master = new + { + level = u + } + } + })); EISC.SetBoolSigAction(BoolJoin.MasterVolumeIsMuted, b => - PostStatusMessage(new - { - masterVolumeMuteState = b - })); + PostStatusMessage(new + { + volumes = new + { + master = new + { + muted = b + } + } + })); + // shutdown things EISC.SetSigTrueAction(BoolJoin.ShutdownCancel, new Action(() => @@ -316,15 +378,25 @@ namespace PepperDash.Essentials.Room.Cotija var co = ConfigReader.ConfigObject; + co.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; + var version = Assembly.GetExecutingAssembly().GetName().Version; + co.Info.RuntimeInfo.AssemblyVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); + //Room - if (co.Rooms == null) - co.Rooms = new List(); - var rm = new EssentialsRoomConfig(); - if (co.Rooms.Count == 0) - { - Debug.Console(0, this, "Adding room to config"); - co.Rooms.Add(rm); - } + //if (co.Rooms == null) + // always start fresh in case simpl changed + co.Rooms = new List(); + var rm = new DeviceConfig(); + if (co.Rooms.Count == 0) + { + Debug.Console(0, this, "Adding room to config"); + co.Rooms.Add(rm); + } + else + { + Debug.Console(0, this, "Replacing Room[0] in config"); + co.Rooms[0] = rm; + } rm.Name = EISC.StringOutput[501].StringValue; rm.Key = "room1"; rm.Type = "ddvc01"; @@ -353,22 +425,30 @@ namespace PepperDash.Essentials.Room.Cotija var name = EISC.StringOutput[i + 1].StringValue; rmProps.SpeedDials.Add(new DDVC01SpeedDial { Number = num, Name = name}); } + + // This MAY need a check + rmProps.AudioCodecKey = "audioCodec"; + rmProps.VideoCodecKey = null; // "videoCodec"; + // volume control names var volCount = EISC.UShortOutput[701].UShortValue; - // use Volumes object or? - rmProps.VolumeSliderNames = new List(); - for(uint i = 701; i <= 700 + volCount; i++) - { - rmProps.VolumeSliderNames.Add(EISC.StringInput[i].StringValue); - } + //// use Volumes object or? + //rmProps.VolumeSliderNames = new List(); + //for(uint i = 701; i <= 700 + volCount; i++) + //{ + // rmProps.VolumeSliderNames.Add(EISC.StringInput[i].StringValue); + //} // There should be cotija devices in here, I think... if(co.Devices == null) co.Devices = new List(); // clear out previous DDVC devices - co.Devices.RemoveAll(d => d.Key.StartsWith("source-", StringComparison.OrdinalIgnoreCase)); + co.Devices.RemoveAll(d => + d.Key.StartsWith("source-", StringComparison.OrdinalIgnoreCase) + || d.Key.Equals("audioCodec", StringComparison.OrdinalIgnoreCase) + || d.Key.Equals("videoCodec", StringComparison.OrdinalIgnoreCase)); rmProps.SourceListKey = "default"; rm.Properties = JToken.FromObject(rmProps); @@ -387,6 +467,7 @@ namespace PepperDash.Essentials.Room.Cotija break; var icon = EISC.StringOutput[651 + i].StringValue; var key = EISC.StringOutput[671 + i].StringValue; + var type = EISC.StringOutput[701 + i].StringValue; Debug.Console(0, this, "Adding source {0} '{1}'", key, name); @@ -395,9 +476,10 @@ namespace PepperDash.Essentials.Room.Cotija Name = name, Order = (int)i + 1, SourceKey = key, + Type = eSourceListItemType.Route }; newSl.Add(key, newSLI); - + string group = "genericsource"; if (groupMap.ContainsKey(type)) { @@ -412,10 +494,50 @@ namespace PepperDash.Essentials.Room.Cotija Type = type }; co.Devices.Add(devConf); + + if (group.ToLower().StartsWith("settopbox")) // Add others here as needed + { + SetupSourceFunctions(key); + } } co.SourceLists.Add("default", newSl); + // build "audioCodec" config if we need + if (!string.IsNullOrEmpty(rmProps.AudioCodecKey)) + { + var acFavs = new List(); + for (uint i = 0; i < 4; i++) + { + if (!EISC.GetBool(BoolJoin.SpeedDialVisibleStartJoin + i)) + { + break; + } + acFavs.Add(new PepperDash.Essentials.Devices.Common.Codec.CodecActiveCallItem() + { + Name = EISC.GetString(StringJoin.SpeedDialNameStartJoin + i), + Number = EISC.GetString(StringJoin.SpeedDialNumberStartJoin + i), + Type = PepperDash.Essentials.Devices.Common.Codec.eCodecCallType.Audio + }); + } + + var acProps = new + { + favorites = acFavs + }; + + var acStr = "audioCodec"; + var acConf = new DeviceConfig() + { + Group = acStr, + Key = acStr, + Name = acStr, + Type = acStr, + Properties = JToken.FromObject(acProps) + }; + co.Devices.Add(acConf); + } + Debug.Console(0, this, "******* CONFIG FROM DDVC: \r{0}", JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented)); var handler = ConfigurationIsReady; @@ -427,17 +549,50 @@ namespace PepperDash.Essentials.Room.Cotija ConfigIsLoaded = true; } + /// + /// + /// void SendFullStatus() { if (ConfigIsLoaded) { - PostStatusMessage(new - { - isOn = EISC.BooleanOutput[BoolJoin.RoomIsOn].BoolValue, - selectedSourceKey = EISC.StringOutput[StringJoin.SelectedSourceKey].StringValue, - masterVolumeLevel = EISC.UShortOutput[UshortJoin.MasterVolumeLevel].UShortValue, - masterVolumeMuteState = EISC.BooleanOutput[BoolJoin.MasterVolumeIsMuted].BoolValue - }); + var count = EISC.UShortOutput[801].UShortValue; + + Debug.Console(1, this, "The Fader Count is : {0}", count); + + // build volumes object, serialize and put in content of method below + + var auxFaders = new List(); + + // Create auxFaders + for (uint i = 2; i <= count; i++) + { + auxFaders.Add( + new Volume(string.Format("level-{0}", i), + EISC.UShortOutput[i].UShortValue, + EISC.BooleanOutput[i].BoolValue, + EISC.StringOutput[800 + i].StringValue, + true, + "someting.png")); + } + + var volumes = new Volumes(); + + volumes.Master = new Volume("master", + EISC.UShortOutput[UshortJoin.MasterVolumeLevel].UShortValue, + EISC.BooleanOutput[BoolJoin.MasterVolumeIsMuted].BoolValue, + EISC.StringOutput[801].StringValue, + true, + "something.png"); + + volumes.AuxFaders = auxFaders; + + PostStatusMessage(new + { + isOn = EISC.BooleanOutput[BoolJoin.RoomIsOn].BoolValue, + selectedSourceKey = EISC.StringOutput[StringJoin.SelectedSourceKey].StringValue, + volumes = volumes + }); } else { @@ -448,8 +603,6 @@ namespace PepperDash.Essentials.Room.Cotija } } - - /// /// Helper for posting status message /// @@ -522,86 +675,5 @@ namespace PepperDash.Essentials.Room.Cotija EISC.StringInput[StringJoin.UserCodeToSystem].StringValue = UserCode; EISC.StringInput[StringJoin.ServerUrl].StringValue = Parent.Config.ClientAppUrl; } - - /// - /// - /// - /// - /// - void SourceChange(string oldKey, string newKey) - { - /* Example message - * { -   "type":"/room/status", -   "content": { -     "selectedSourceKey": "off", -   } - } - */ - //if (type == ChangeType.WillChange) - //{ - // // Disconnect from previous source - - // if (info != null) - // { - // var previousDev = info.SourceDevice; - - // // device type interfaces - // if (previousDev is ISetTopBoxControls) - // (previousDev as ISetTopBoxControls).UnlinkActions(Parent); - // // common interfaces - // if (previousDev is IChannel) - // (previousDev as IChannel).UnlinkActions(Parent); - // if (previousDev is IColor) - // (previousDev as IColor).UnlinkActions(Parent); - // if (previousDev is IDPad) - // (previousDev as IDPad).UnlinkActions(Parent); - // if (previousDev is IDvr) - // (previousDev as IDvr).UnlinkActions(Parent); - // if (previousDev is INumericKeypad) - // (previousDev as INumericKeypad).UnlinkActions(Parent); - // if (previousDev is IPower) - // (previousDev as IPower).UnlinkActions(Parent); - // if (previousDev is ITransport) - // (previousDev as ITransport).UnlinkActions(Parent); - // } - - - // var huddleRoom = room as EssentialsHuddleSpaceRoom; - // JObject roomStatus = new JObject(); - // roomStatus.Add("selectedSourceKey", huddleRoom.CurrentSourceInfoKey); - - // JObject message = new JObject(); - - // message.Add("type", "/room/status/"); - // message.Add("content", roomStatus); - - // Parent.PostToServer(message); - //} - //else - //{ - // if (info != null) - // { - // var dev = info.SourceDevice; - - // if (dev is ISetTopBoxControls) - // (dev as ISetTopBoxControls).LinkActions(Parent); - // if (dev is IChannel) - // (dev as IChannel).LinkActions(Parent); - // if (dev is IColor) - // (dev as IColor).LinkActions(Parent); - // if (dev is IDPad) - // (dev as IDPad).LinkActions(Parent); - // if (dev is IDvr) - // (dev as IDvr).LinkActions(Parent); - // if (dev is INumericKeypad) - // (dev as INumericKeypad).LinkActions(Parent); - // if (dev is IPower) - // (dev as IPower).LinkActions(Parent); - // if (dev is ITransport) - // (dev as ITransport).LinkActions(Parent); - // } - //} - } } } \ No newline at end of file diff --git a/PepperDashEssentials/Room/Cotija/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs b/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs similarity index 51% rename from PepperDashEssentials/Room/Cotija/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs rename to PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs index 2f3cf440..36ae6c03 100644 --- a/PepperDashEssentials/Room/Cotija/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs @@ -5,15 +5,22 @@ using System.Text; using Crestron.SimplSharp; using Newtonsoft.Json; using Newtonsoft.Json.Linq; + +using PepperDash.Core; +using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; using PepperDash.Essentials.Room.Cotija; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; namespace PepperDash.Essentials { public class CotijaEssentialsHuddleSpaceRoomBridge : CotijaBridgeBase { - public EssentialsHuddleSpaceRoom Room { get; private set; } + public EssentialsRoomBase Room { get; private set; } + + public VideoCodecBaseMessenger VCMessenger { get; private set; } /// /// @@ -31,7 +38,7 @@ namespace PepperDash.Essentials /// /// /// - public CotijaEssentialsHuddleSpaceRoomBridge(EssentialsHuddleSpaceRoom room): + public CotijaEssentialsHuddleSpaceRoomBridge(EssentialsRoomBase room): base("mobileControlBridge-essentialsHuddle", "Essentials Mobile Control Bridge-Huddle") { Room = room; @@ -51,22 +58,74 @@ namespace PepperDash.Essentials // doesn't need to know about everything. // Source Changes and room off - Parent.AddAction(string.Format(@"/room/{0}/status", Room.Key), new Action(() => Room_RoomFullStatus(Room))); - Parent.AddAction(string.Format(@"/room/{0}/source", Room.Key), new Action(c => Room.RunRouteAction(c.SourceListItem))); - Parent.AddAction(string.Format(@"/room/{0}/defaultsource", Room.Key), new Action(Room.RunDefaultRoute)); + Parent.AddAction(string.Format(@"/room/{0}/status", Room.Key), new Action(() => SendFullStatus(Room))); - Parent.AddAction(string.Format(@"/room/{0}/masterVolumeLevel", Room.Key), new Action(u => - (Room.CurrentVolumeControls as IBasicVolumeWithFeedback).SetVolume(u))); - Parent.AddAction(string.Format(@"/room/{0}/masterVolumeMuteToggle", Room.Key), new Action(() => Room.CurrentVolumeControls.MuteToggle())); + var routeRoom = Room as IRunRouteAction; + if(routeRoom != null) + Parent.AddAction(string.Format(@"/room/{0}/source", Room.Key), new Action(c => + routeRoom.RunRouteAction(c.SourceListItem))); + + var defaultRoom = Room as IRunDefaultPresentRoute; + if(defaultRoom != null) + Parent.AddAction(string.Format(@"/room/{0}/defaultsource", Room.Key), new Action(() => defaultRoom.RunDefaultPresentRoute())); + + var volumeRoom = Room as IHasCurrentVolumeControls; + if (volumeRoom != null) + { + Parent.AddAction(string.Format(@"/room/{0}/volumes/master/level", Room.Key), new Action(u => + (volumeRoom.CurrentVolumeControls as IBasicVolumeWithFeedback).SetVolume(u))); + Parent.AddAction(string.Format(@"/room/{0}/volumes/master/muteToggle", Room.Key), new Action(() => + volumeRoom.CurrentVolumeControls.MuteToggle())); + volumeRoom.CurrentVolumeDeviceChange += new EventHandler(Room_CurrentVolumeDeviceChange); + + // Registers for initial volume events, if possible + var currentVolumeDevice = volumeRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; + if (currentVolumeDevice != null) + { + currentVolumeDevice.MuteFeedback.OutputChange += MuteFeedback_OutputChange; + currentVolumeDevice.VolumeLevelFeedback.OutputChange += VolumeLevelFeedback_OutputChange; + } + } + + var sscRoom = Room as IHasCurrentSourceInfoChange; + if(sscRoom != null) + sscRoom.CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSingleSourceChange); + + var vcRoom = Room as IHasVideoCodec; + if (vcRoom != null) + { + var codec = vcRoom.VideoCodec; + var key = vcRoom.VideoCodec.Key + "-" + parent.Key; + VCMessenger = new VideoCodecBaseMessenger(key, vcRoom.VideoCodec, "/device/videoCodec"); + VCMessenger.RegisterWithAppServer(Parent); + + // May need to move this or remove this + codec.CallStatusChange += new EventHandler(codec_CallStatusChange); + + vcRoom.IsSharingFeedback.OutputChange += new EventHandler(IsSharingFeedback_OutputChange); + + //Parent.AddAction("/device/videoCodec/dial", new Action(s => codec.Dial(s))); + //Parent.AddAction("/device/videoCodec/endCall", new Action(s => + //{ + // var call = codec.ActiveCalls.FirstOrDefault(c => c.Id == s); + // if (call != null) + // { + // codec.EndCall(call); + // } + //})); + //Parent.AddAction("/device/videoCodec/endAllCalls", new Action(() => codec.EndAllCalls())); + } + + var defCallRm = Room as IRunDefaultCallRoute; + if (defCallRm != null) + { + Parent.AddAction(string.Format(@"/room/{0}/activityVideo", Room.Key), new Action(()=>defCallRm.RunDefaultCallRoute())); + } Parent.AddAction(string.Format(@"/room/{0}/shutdownStart", Room.Key), new Action(() => Room.StartShutdown(eShutdownType.Manual))); Parent.AddAction(string.Format(@"/room/{0}/shutdownEnd", Room.Key), new Action(() => Room.ShutdownPromptTimer.Finish())); Parent.AddAction(string.Format(@"/room/{0}/shutdownCancel", Room.Key), new Action(() => Room.ShutdownPromptTimer.Cancel())); - Room.CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSingleSourceChange); - - Room.CurrentVolumeDeviceChange += new EventHandler(Room_CurrentVolumeDeviceChange); - Room.OnFeedback.OutputChange += OnFeedback_OutputChange; Room.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange; Room.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange; @@ -74,20 +133,67 @@ namespace PepperDash.Essentials Room.ShutdownPromptTimer.HasStarted += ShutdownPromptTimer_HasStarted; Room.ShutdownPromptTimer.HasFinished += ShutdownPromptTimer_HasFinished; Room.ShutdownPromptTimer.WasCancelled += ShutdownPromptTimer_WasCancelled; + } - // Registers for initial volume events, if possible - var currentVolumeDevice = Room.CurrentVolumeControls; - - if (currentVolumeDevice != null) + /// + /// + /// + /// + /// + void IsSharingFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + // sharing source + string shareText; + bool isSharing; +#warning This share update needs to happen on source change as well! + var vcRoom = Room as IHasVideoCodec; + var srcInfoRoom = Room as IHasCurrentSourceInfoChange; + if (vcRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && srcInfoRoom.CurrentSourceInfo != null) { - if (currentVolumeDevice is IBasicVolumeWithFeedback) - { - var newDev = currentVolumeDevice as IBasicVolumeWithFeedback; - newDev.MuteFeedback.OutputChange += VolumeLevelFeedback_OutputChange; - newDev.VolumeLevelFeedback.OutputChange += VolumeLevelFeedback_OutputChange; - } + shareText = srcInfoRoom.CurrentSourceInfo.PreferredName; + isSharing = true; } + else + { + shareText = "None"; + isSharing = false; + } + + PostStatusMessage(new + { + share = new + { + currentShareText = shareText, + isSharing = isSharing + } + }); + } + + /// + /// Handler for codec changes + /// + void codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) + { + PostStatusMessage(new + { + calls = GetCallsMessageObject(), + //vtc = GetVtcCallsMessageObject() + }); + + } + + /// + /// Helper for posting status message + /// + /// The contents of the content object + void PostStatusMessage(object contentObject) + { + Parent.SendMessageToServer(JObject.FromObject(new + { + type = "/room/status/", + content = contentObject + })); } /// @@ -143,14 +249,12 @@ namespace PepperDash.Essentials /// /// /// - void IsWarmingUpFeedback_OutputChange(object sender, EventArgs e) + void IsWarmingUpFeedback_OutputChange(object sender, FeedbackEventArgs e) { - JObject roomStatus = new JObject(); - roomStatus.Add("isWarmingUp", (sender as BoolFeedback).BoolValue); - JObject message = new JObject(); - message.Add("type", "/room/status/"); - message.Add("content", roomStatus); - Parent.SendMessageToServer(message); + PostStatusMessage(new + { + isWarmingUp = e.BoolValue + }); } /// @@ -158,14 +262,12 @@ namespace PepperDash.Essentials /// /// /// - void IsCoolingDownFeedback_OutputChange(object sender, EventArgs e) + void IsCoolingDownFeedback_OutputChange(object sender, FeedbackEventArgs e) { - JObject roomStatus = new JObject(); - roomStatus.Add("isCoolingDown", (sender as BoolFeedback).BoolValue); - JObject message = new JObject(); - message.Add("type", "/room/status/"); - message.Add("content", roomStatus); - Parent.SendMessageToServer(message); + PostStatusMessage(new + { + isCoolingDown = e.BoolValue + }); } /// @@ -173,27 +275,12 @@ namespace PepperDash.Essentials /// /// /// - void OnFeedback_OutputChange(object sender, EventArgs e) + void OnFeedback_OutputChange(object sender, FeedbackEventArgs e) { - /* Example message - * { - "type":"/room/status", - "content": { - "isOn": false - } - } - */ - - JObject roomStatus = new JObject(); - - roomStatus.Add("isOn", (sender as BoolFeedback).BoolValue); - - JObject message = new JObject(); - - message.Add("type", "/room/status/"); - message.Add("content", roomStatus); - - Parent.SendMessageToServer(message); + PostStatusMessage(new + { + isOn = e.BoolValue + }); } void Room_CurrentVolumeDeviceChange(object sender, VolumeDeviceChangeEventArgs e) @@ -201,54 +288,53 @@ namespace PepperDash.Essentials if (e.OldDev is IBasicVolumeWithFeedback) { var oldDev = e.OldDev as IBasicVolumeWithFeedback; - - oldDev.MuteFeedback.OutputChange -= VolumeLevelFeedback_OutputChange; + oldDev.MuteFeedback.OutputChange -= MuteFeedback_OutputChange; oldDev.VolumeLevelFeedback.OutputChange -= VolumeLevelFeedback_OutputChange; } if (e.NewDev is IBasicVolumeWithFeedback) { var newDev = e.NewDev as IBasicVolumeWithFeedback; - - newDev.MuteFeedback.OutputChange += VolumeLevelFeedback_OutputChange; + newDev.MuteFeedback.OutputChange += MuteFeedback_OutputChange; newDev.VolumeLevelFeedback.OutputChange += VolumeLevelFeedback_OutputChange; } } - void VolumeLevelFeedback_OutputChange(object sender, EventArgs e) + /// + /// Event handler for mute changes + /// + void MuteFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + PostStatusMessage(new + { + volumes = new + { + master = new + { + muted = e.BoolValue + } + } + }); + } + + /// + /// Handles Volume changes on room + /// + void VolumeLevelFeedback_OutputChange(object sender, FeedbackEventArgs e) { - /* Example message - * { -   "type":"/room/status", -   "content": { -     "masterVolumeLevel": 12345, -     "masterVolumeMuteState": false -   } - } - */ - - var huddleRoom = Room as EssentialsHuddleSpaceRoom; - - if(huddleRoom.CurrentVolumeControls is IBasicVolumeWithFeedback) - { - JObject roomStatus = new JObject(); - - if (huddleRoom.CurrentVolumeControls is IBasicVolumeWithFeedback) - { - var currentVolumeConstrols = huddleRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; - roomStatus.Add("masterVolumeLevel", currentVolumeConstrols.VolumeLevelFeedback.IntValue); - roomStatus.Add("masterVolumeMuteState", currentVolumeConstrols.MuteFeedback.BoolValue); - } - - JObject message = new JObject(); - - message.Add("type", "/room/status/"); - message.Add("content", roomStatus); - - Parent.SendMessageToServer(message); - } + PostStatusMessage(new + { + volumes = new + { + master = new + { + level = e.IntValue + } + } + }); } + void Room_CurrentSingleSourceChange(EssentialsRoomBase room, PepperDash.Essentials.Core.SourceListItem info, ChangeType type) { /* Example message @@ -310,16 +396,11 @@ namespace PepperDash.Essentials if (dev is ITransport) (dev as ITransport).LinkActions(Parent); - var huddleRoom = room as EssentialsHuddleSpaceRoom; - JObject roomStatus = new JObject(); - roomStatus.Add("selectedSourceKey", huddleRoom.CurrentSourceInfoKey); - - JObject message = new JObject(); - - message.Add("type", "/room/status/"); - message.Add("content", roomStatus); - - Parent.SendMessageToServer(message); + var srcRm = room as IHasCurrentSourceInfoChange; + PostStatusMessage(new + { + selectedSourceKey = srcRm.CurrentSourceInfoKey + }); } } } @@ -328,52 +409,83 @@ namespace PepperDash.Essentials /// Posts the full status of the room to the server /// /// - void Room_RoomFullStatus(EssentialsRoomBase room) + void SendFullStatus(EssentialsRoomBase room) { - /* Example message - * { - "type":"/room/status", - "content": { - "selectedSourceKey": "off", - "isOn": false, - "masterVolumeLevel": 50, - "masterVolumeMuteState": false - } - } - */ - - JObject roomStatus = new JObject(); - - var huddleRoom = room as EssentialsHuddleSpaceRoom; - roomStatus.Add("isOn", huddleRoom.OnFeedback.BoolValue); - roomStatus.Add("selectedSourceKey", huddleRoom.CurrentSourceInfoKey); - - - if(huddleRoom.CurrentVolumeControls is IBasicVolumeWithFeedback) - { - var currentVolumeConstrols = huddleRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; - roomStatus.Add("masterVolumeLevel", currentVolumeConstrols.VolumeLevelFeedback.IntValue); - roomStatus.Add("masterVolumeMuteState", currentVolumeConstrols.MuteFeedback.BoolValue); - } - - JObject message = new JObject(); - - message.Add("type", "/room/status/"); - message.Add("content", roomStatus); - - Parent.SendMessageToServer(message); + var sourceKey = room is IHasCurrentSourceInfoChange ? (room as IHasCurrentSourceInfoChange).CurrentSourceInfoKey : null; + + var rmVc = room as IHasCurrentVolumeControls; + var volumes = new Volumes(); + if (rmVc != null) + { + var vc = rmVc.CurrentVolumeControls as IBasicVolumeWithFeedback; + if (rmVc != null) + { + volumes.Master = new Volume("master", vc.VolumeLevelFeedback.UShortValue, vc.MuteFeedback.BoolValue, "Volume", true, ""); + } + } + PostStatusMessage(new + { + calls = GetCallsMessageObject(), + isOn = room.OnFeedback.BoolValue, + selectedSourceKey = sourceKey, + vtc = GetVtcCallsMessageObject(), + 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; + } } + /// + /// + /// public class SourceSelectMessageContent { public string SourceListItem { get; set; } - //public string Destination { get; set; } - //public string SourceSelect { get; set; } } + /// + /// + /// + /// public delegate void PressAndHoldAction(bool b); } \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs b/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs new file mode 100644 index 00000000..976c4121 --- /dev/null +++ b/PepperDashEssentials/AppServer/RoomBridges/SourceDeviceMapDictionary.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Room.Cotija +{ + + /// + /// Contains all of the default joins that map to API funtions + /// + public class SourceDeviceMapDictionary : Dictionary + { + + public SourceDeviceMapDictionary(): base() + { + var dictionary = new Dictionary + { + {"preset01", 101}, + {"preset02", 102}, + {"preset03", 103}, + {"preset04", 104}, + {"preset05", 105}, + {"preset06", 106}, + {"preset07", 107}, + {"preset08", 108}, + {"preset09", 109}, + {"preset10", 110}, + {"preset11", 111}, + {"preset12", 112}, + {"preset13", 113}, + {"preset14", 114}, + {"preset15", 115}, + {"preset16", 116}, + {"preset17", 117}, + {"preset18", 118}, + {"preset19", 119}, + {"preset20", 120}, + {"preset21", 121}, + {"preset22", 122}, + {"preset23", 123}, + {"preset24", 124}, + + {"num0", 130}, + {"num1", 131}, + {"num2", 132}, + {"num3", 133}, + {"num4", 134}, + {"num5", 135}, + {"num6", 136}, + {"num7", 137}, + {"num8", 138}, + {"num9", 139}, + {"numDash", 140}, + {"numEnter", 141}, + {"chanUp", 142}, + {"chanDown", 143}, + {"lastChan", 144}, + {"exit", 145}, + {"powerToggle", 146}, + {"red", 147}, + {"green", 148}, + {"yellow", 149}, + {"blue", 150}, + {"video", 151}, + {"previous", 152}, + {"next", 153}, + {"rewind", 154}, + {"ffwd", 155}, + {"closedCaption", 156}, + {"stop", 157}, + {"pause", 158}, + {"up", 159}, + {"down", 160}, + {"left", 161}, + {"right", 162}, + {"settings", 163}, + {"info", 164}, + {"return", 165}, + {"guide", 166}, + {"reboot", 167}, + {"dvrList", 168}, + {"replay", 169}, + {"play", 170}, + {"select", 171}, + {"record", 172}, + {"menu", 173}, + {"topMenu", 174}, + {"prevTrack", 175}, + {"nextTrack", 176}, + {"powerOn", 177}, + {"powerOff", 178}, + {"dot", 179} + + }; + + foreach (var item in dictionary) + { + this.Add(item.Key, item.Value); + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Volumes.cs b/PepperDashEssentials/AppServer/Volumes.cs new file mode 100644 index 00000000..38de8ef2 --- /dev/null +++ b/PepperDashEssentials/AppServer/Volumes.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Room.Cotija +{ + public class Volumes + { + [JsonProperty("master")] + public Volume Master { get; set; } + + [JsonProperty("auxFaders")] + public List AuxFaders { get; set; } + + public Volumes() + { + AuxFaders = new List(); + } + } + + public class Volume + { + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("level")] + public ushort Level { get; set; } + + [JsonProperty("muted")] + public bool Muted { get; set; } + + [JsonProperty("label")] + public string Label { get; set; } + + [JsonProperty("hasMute")] + public bool HasMute { get; set; } + + [JsonProperty("muteIcon")] + public string MuteIcon { get; set; } + + public Volume(string key, ushort level, bool muted, string label, bool hasMute, string muteIcon) + { + Key = key; + Level = level; + Muted = muted; + Label = label; + HasMute = hasMute; + MuteIcon = muteIcon; + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/BridgeBase.cs b/PepperDashEssentials/Bridges/BridgeBase.cs index 99144df6..d5d86c93 100644 --- a/PepperDashEssentials/Bridges/BridgeBase.cs +++ b/PepperDashEssentials/Bridges/BridgeBase.cs @@ -10,8 +10,8 @@ using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Devices; -using PepperDash.Essentials.Devices.Common; using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.CrestronIO; using PepperDash.Essentials.DM; namespace PepperDash.Essentials.Bridges @@ -75,31 +75,41 @@ namespace PepperDash.Essentials.Bridges if (device != null) { - if (device is GenericComm) + if (device is PepperDash.Essentials.Core.Monitoring.SystemMonitorController) + { + (device as PepperDash.Essentials.Core.Monitoring.SystemMonitorController).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } + else if (device is GenericComm) { (device as GenericComm).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); continue; } - else if (device is DigitalLogger) - { - (device as DigitalLogger).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); - continue; - } - else if (device is DmChassisController) - { - // (device as DmChassisController).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); - continue; - } - else if (device is DmTxControllerBase) - { - // (device as DmTxControllerBase).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); - continue; - } - else if (device is DmRmcControllerBase) - { - // (device as DmRmcControllerBase).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); - continue; - } + else if (device is DmChassisController) + { + (device as DmChassisController).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } + else if (device is DmTxControllerBase) + { + (device as DmTxControllerBase).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } + else if (device is DmRmcControllerBase) + { + (device as DmRmcControllerBase).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } + else if (device is GenericRelayDevice) + { + (device as GenericRelayDevice).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } + else if (device is IDigitalInput) + { + (device as IDigitalInput).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } } } @@ -134,6 +144,7 @@ namespace PepperDash.Essentials.Bridges [JsonProperty("devices")] public List Devices { get; set; } + public class ApiDevice { [JsonProperty("deviceKey")] @@ -149,108 +160,4 @@ namespace PepperDash.Essentials.Bridges } - ///// - ///// API class for IBasicCommunication devices - ///// - //public class IBasicCommunicationApi : DeviceApiBase - //{ - // public IBasicCommunication Device { get; set; } - - // SerialFeedback TextReceivedFeedback; - - // public IBasicCommunicationApi(IBasicCommunication dev) - // { - // TextReceivedFeedback = new SerialFeedback(); - - // Device = dev; - - // SetupFeedbacks(); - - // ActionApi = new Dictionary - // { - // { "connect", new Action(Device.Connect) }, - // { "disconnect", new Action(Device.Disconnect) }, - // { "connectstate", new Action( b => ConnectByState(b) ) }, - // { "sendtext", new Action( s => Device.SendText(s) ) } - - // }; - - // FeedbackApi = new Dictionary - // { - // { "isconnected", new BoolFeedback( () => Device.IsConnected ) }, - // { "textrecieved", TextReceivedFeedback } - // }; - // } - - // /// - // /// Controls connection based on state of input - // /// - // /// - // void ConnectByState(bool state) - // { - // if (state) - // Device.Connect(); - // else - // Device.Disconnect(); - // } - - // void SetupFeedbacks() - // { - // Device.TextReceived += new EventHandler(Device_TextReceived); - - // if(Device is ISocketStatus) - // (Device as ISocketStatus).ConnectionChange += new EventHandler(IBasicCommunicationApi_ConnectionChange); - // } - - // void IBasicCommunicationApi_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) - // { - // FeedbackApi["isconnected"].FireUpdate(); - // } - - // void Device_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) - // { - // TextReceivedFeedback.FireUpdate(e.Text); - // } - //} - - - - ///// - ///// Each flavor of API is a static class with static properties and a static constructor that - ///// links up the things to do. - ///// - //public class DmChassisControllerApi : DeviceApiBase - //{ - // IntFeedback Output1Feedback; - // IntFeedback Output2Feedback; - - // public DmChassisControllerApi(DmChassisController dev) - // { - // Output1Feedback = new IntFeedback( new Func(() => 1)); - // Output2Feedback = new IntFeedback( new Func(() => 2)); - - // ActionApi = new Dictionary - // { - - // }; - - // FeedbackApi = new Dictionary - // { - // { "Output-1/fb", Output1Feedback }, - // { "Output-2/fb", Output2Feedback } - // }; - // } - - // /// - // /// Factory method - // /// - // /// - // /// - // public static DmChassisControllerApi GetActionApiForDevice(DmChassisController dev) - // { - // return new DmChassisControllerApi(dev); - // } - //} - - } \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/BridgeFactory.cs b/PepperDashEssentials/Bridges/BridgeFactory.cs new file mode 100644 index 00000000..8079aa80 --- /dev/null +++ b/PepperDashEssentials/Bridges/BridgeFactory.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Core; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Bridges; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.EthernetCommunication; + +namespace PepperDash.Essentials +{ + public class BridgeFactory + { + public static IKeyed GetDevice(DeviceConfig dc) + { + // ? why is this static JTA 2018-06-13? + + var key = dc.Key; + var name = dc.Name; + var type = dc.Type; + var properties = dc.Properties; + var propAnon = new { }; + + var typeName = dc.Type.ToLower(); + var groupName = dc.Group.ToLower(); + + //Debug.Console(2, "Name {0}, Key {1}, Type {2}, Properties {3}", name, key, type, properties.ToString()); + + if (typeName == "eiscapi") + { + return new EiscApi(dc); + } + + return null; + } + } + + public class DmBridge : Device + { + public EiscBridgeProperties Properties { get; private set; } + + public PepperDash.Essentials.DM.DmChassisController DmSwitch { get; private set; } + + public DmBridge(string key, string name, JToken properties) : base(key, name) + { + Properties = JsonConvert.DeserializeObject(properties.ToString()); + } + + public override bool CustomActivate() + { + // Create EiscApis + if (Properties.Eiscs != null) + { + foreach (var eisc in Properties.Eiscs) + { + var ApiEisc = new BridgeApiEisc(eisc.IpId, eisc.Hostname); + + ApiEisc.Eisc.SetUShortSigAction(101, u => DmSwitch.ExecuteSwitch(u,1, eRoutingSignalType.Video)); + ApiEisc.Eisc.SetUShortSigAction(102, u => DmSwitch.ExecuteSwitch(u,2, eRoutingSignalType.Video)); + } + } + + foreach (var device in DeviceManager.AllDevices) + { + if (device.Key == this.Properties.ParentDeviceKey) + { + Debug.Console(0, "deviceKey {0} Matches", device.Key); + DmSwitch = DeviceManager.GetDeviceForKey(device.Key) as PepperDash.Essentials.DM.DmChassisController; + } + else + { + Debug.Console(0, "deviceKey {0} doesn't match", device.Key); + } + } + + Debug.Console(0, "Bridge {0} Activated", this.Name); + return true; + } + } + + public class CommBridge : Device + { + public CommBridgeProperties Properties { get; private set; } + + public List CommDevices { get; private set; } + + public CommBridge(string key, string name, JToken properties) + : base(key, name) + { + Properties = JsonConvert.DeserializeObject(properties.ToString()); + } + + public override bool CustomActivate() + { + // Create EiscApis + if (Properties.Eiscs != null) + { + foreach (var eisc in Properties.Eiscs) + { + var ApiEisc = new BridgeApiEisc(eisc.IpId, eisc.Hostname); + } + } + + foreach (var deviceKey in Properties.CommDevices) + { + var device = DeviceManager.GetDeviceForKey(deviceKey); + + if (device != null) + { + Debug.Console(0, "deviceKey {0} Found in Device Manager", device.Key); + CommDevices.Add(device as IBasicCommunication); + } + else + { + Debug.Console(0, "deviceKey {0} Not Found in Device Manager", deviceKey); + } + } + + // Iterate through all the CommDevices and link up their Actions and Feedbacks + + Debug.Console(0, "Bridge {0} Activated", this.Name); + + return true; + } + } + + + public class EiscBridgeProperties + { + public string ParentDeviceKey { get; set; } + public eApiType ApiType { get; set; } + public List Eiscs { get; set; } + public string ApiOverrideFilePath { get; set; } + + public class EiscProperties + { + public string IpId { get; set; } + public string Hostname { get; set; } + } + } + + public class CommBridgeProperties : EiscBridgeProperties + { + public List CommDevices { get; set; } + } + + public enum eApiType { Eisc = 0 } + + public class BridgeApiEisc + { + public uint Ipid { get; private set; } + public ThreeSeriesTcpIpEthernetIntersystemCommunications Eisc { get; private set; } + + public BridgeApiEisc(string ipid, string hostname) + { + Ipid = (UInt32)int.Parse(ipid, System.Globalization.NumberStyles.HexNumber); + Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(Ipid, hostname, Global.ControlSystem); + Eisc.Register(); + Eisc.SigChange += Eisc_SigChange; + Debug.Console(0, "BridgeApiEisc Created at Ipid {0}", ipid); + } + void Eisc_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) + { + if (Debug.Level >= 1) + Debug.Console(1, "BridgeApiEisc change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); + var uo = args.Sig.UserObject; + if (uo is Action) + (uo as Action)(args.Sig.BoolValue); + else if (uo is Action) + (uo as Action)(args.Sig.UShortValue); + else if (uo is Action) + (uo as Action)(args.Sig.StringValue); + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs b/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs new file mode 100644 index 00000000..cccf5fb4 --- /dev/null +++ b/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.DM; + +namespace PepperDash.Essentials.Bridges +{ + public static class DmChassisControllerApiExtentions + { + public static void LinkToApi(this DmChassisController dmChassis, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as DmChassisControllerJoinMap; + + if (joinMap == null) + joinMap = new DmChassisControllerJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + Debug.Console(1, dmChassis, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + dmChassis.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline]); + + // Link up outputs + for (uint i = 1; i <= dmChassis.Chassis.NumberOfOutputs - 1; i++) + { + var ioSlot = i; + + // Control + trilist.SetUShortSigAction(joinMap.OutputVideo + ioSlot, new Action(o => dmChassis.ExecuteSwitch(o, ioSlot, eRoutingSignalType.Video))); + trilist.SetUShortSigAction(joinMap.OutputAudio + ioSlot, new Action(o => dmChassis.ExecuteSwitch(o, ioSlot, eRoutingSignalType.Audio))); + + // Feedback + dmChassis.VideoOutputFeedbacks[ioSlot].LinkInputSig(trilist.UShortInput[joinMap.OutputVideo + ioSlot]); + dmChassis.AudioOutputFeedbacks[ioSlot].LinkInputSig(trilist.UShortInput[joinMap.OutputAudio + ioSlot]); + + dmChassis.VideoInputSyncFeedbacks[ioSlot].LinkInputSig(trilist.BooleanInput[joinMap.VideoSyncStatus + ioSlot]); + + dmChassis.OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputNames + ioSlot]); + dmChassis.InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputNames + ioSlot]); + dmChassis.OutputVideoRouteNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputCurrentVideoInputNames + ioSlot]); + dmChassis.OutputAudioRouteNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputCurrentAudioInputNames + ioSlot]); + dmChassis.InputEndpointOnlineFeedbacks[ioSlot].LinkInputSig(trilist.BooleanInput[joinMap.InputEndpointOnline + ioSlot]); + dmChassis.OutputEndpointOnlineFeedbacks[ioSlot].LinkInputSig(trilist.BooleanInput[joinMap.OutputEndpointOnline + ioSlot]); + } + } + + + public class DmChassisControllerJoinMap : JoinMapBase + { + public uint IsOnline { get; set; } + public uint OutputVideo { get; set; } + public uint OutputAudio { get; set; } + public uint VideoSyncStatus { get; set; } + public uint InputNames { get; set; } + public uint OutputNames { get; set; } + public uint OutputCurrentVideoInputNames { get; set; } + public uint OutputCurrentAudioInputNames { get; set; } + public uint InputCurrentResolution { get; set; } + public uint InputEndpointOnline { get; set; } + public uint OutputEndpointOnline { get; set; } + //public uint HdcpSupport { get; set; } + //public uint HdcpSupportCapability { get; set; } + + + public DmChassisControllerJoinMap() + { + IsOnline = 11; + OutputVideo = 100; //101-299 + OutputAudio = 300; //301-499 + VideoSyncStatus = 100; //101-299 + InputNames = 100; //101-299 + OutputNames = 300; //301-499 + OutputCurrentVideoInputNames = 2000; //2001-2199 + OutputCurrentAudioInputNames = 2200; //2201-2399 + InputCurrentResolution = 2400; // 2401-2599 + InputEndpointOnline = 500; + OutputEndpointOnline = 700; + //HdcpSupport = 1000; //1001-1199 + //HdcpSupportCapability = 1200; //1201-1399 + + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + IsOnline = IsOnline + joinOffset; + OutputVideo = OutputVideo + joinOffset; + OutputAudio = OutputAudio + joinOffset; + VideoSyncStatus = VideoSyncStatus + joinOffset; + InputNames = InputNames + joinOffset; + OutputNames = OutputNames + joinOffset; + OutputCurrentVideoInputNames = OutputCurrentVideoInputNames + joinOffset; + OutputCurrentAudioInputNames = OutputCurrentAudioInputNames + joinOffset; + InputCurrentResolution = InputCurrentResolution + joinOffset; + InputEndpointOnline = InputEndpointOnline + joinOffset; + OutputEndpointOnline = OutputEndpointOnline + joinOffset; + //HdcpSupport = HdcpSupport + joinOffset; + //HdcpSupportCapability = HdcpSupportCapability + joinOffset; + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DmRmcControllerBridge.cs b/PepperDashEssentials/Bridges/DmRmcControllerBridge.cs new file mode 100644 index 00000000..318a3d0b --- /dev/null +++ b/PepperDashEssentials/Bridges/DmRmcControllerBridge.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.DM; + +namespace PepperDash.Essentials.Bridges +{ + public static class DmRmcControllerApiExtensions + { + public static void LinkToApi(this DmRmcControllerBase rmc, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as DmRmcControllerJoinMap; + + if (joinMap == null) + joinMap = new DmRmcControllerJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + Debug.Console(1, rmc, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + rmc.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline]); + if(rmc.VideoOutputResolutionFeedback != null) + rmc.VideoOutputResolutionFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentOutputResolution]); + if(rmc.EdidManufacturerFeedback != null) + rmc.EdidManufacturerFeedback.LinkInputSig(trilist.StringInput[joinMap.EdidManufacturer]); + if(rmc.EdidNameFeedback != null) + rmc.EdidNameFeedback.LinkInputSig(trilist.StringInput[joinMap.EdidName]); + if(rmc.EdidPreferredTimingFeedback != null) + rmc.EdidPreferredTimingFeedback.LinkInputSig(trilist.StringInput[joinMap.EdidPrefferedTiming]); + if(rmc.EdidSerialNumberFeedback != null) + rmc.EdidSerialNumberFeedback.LinkInputSig(trilist.StringInput[joinMap.EdidSerialNumber]); + } + + public class DmRmcControllerJoinMap : JoinMapBase + { + public uint IsOnline { get; set; } + public uint CurrentOutputResolution { get; set; } + public uint EdidManufacturer { get; set; } + public uint EdidName { get; set; } + public uint EdidPrefferedTiming { get; set; } + public uint EdidSerialNumber { get; set; } + + public DmRmcControllerJoinMap() + { + // Digital + IsOnline = 1; + + // Serial + CurrentOutputResolution = 1; + EdidManufacturer = 2; + EdidName = 3; + EdidPrefferedTiming = 4; + EdidSerialNumber = 5; + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + IsOnline = IsOnline + joinOffset; + CurrentOutputResolution = CurrentOutputResolution + joinOffset; + EdidManufacturer = EdidManufacturer + joinOffset; + EdidName = EdidName + joinOffset; + EdidPrefferedTiming = EdidPrefferedTiming + joinOffset; + EdidSerialNumber = EdidSerialNumber + joinOffset; + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DmTxControllerBridge.cs b/PepperDashEssentials/Bridges/DmTxControllerBridge.cs new file mode 100644 index 00000000..26a6a939 --- /dev/null +++ b/PepperDashEssentials/Bridges/DmTxControllerBridge.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; +using Crestron.SimplSharpPro.DM.Endpoints; +using Crestron.SimplSharpPro.DM.Endpoints.Transmitters; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.DM; + +namespace PepperDash.Essentials.Bridges +{ + public static class DmTxControllerApiExtensions + { + public static void LinkToApi(this DmTxControllerBase tx, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as DmTxControllerJoinMap; + + if (joinMap == null) + joinMap = new DmTxControllerJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + Debug.Console(1, tx, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + tx.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline]); + tx.AnyVideoInput.VideoStatus.VideoSyncFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VideoSyncStatus]); + tx.AnyVideoInput.VideoStatus.VideoResolutionFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentInputResolution]); + //tx.HdcpSupportAllFeedback.LinkInputSig(trilist.UShortInput[joinMap.HdcpSupportCapability]); + + bool hdcpTypeSimple; + + if (tx.Hardware is DmTx4kX02CBase || tx.Hardware is DmTx4kzX02CBase) + hdcpTypeSimple = false; + else + hdcpTypeSimple = true; + + if (tx is ITxRouting) + { + var txR = tx as ITxRouting; + + trilist.SetUShortSigAction(joinMap.VideoInput, + new Action(i => txR.ExecuteNumericSwitch(i, 0, eRoutingSignalType.Video))); + trilist.SetUShortSigAction(joinMap.AudioInput, + new Action(i => txR.ExecuteNumericSwitch(i, 0, eRoutingSignalType.Audio))); + + txR.VideoSourceNumericFeedback.LinkInputSig(trilist.UShortInput[joinMap.VideoInput]); + txR.AudioSourceNumericFeedback.LinkInputSig(trilist.UShortInput[joinMap.AudioInput]); + + trilist.UShortInput[joinMap.HdcpSupportCapability].UShortValue = (ushort)tx.HdcpSupportCapability; + + if(txR.InputPorts[DmPortName.HdmiIn] != null) + { + var inputPort = txR.InputPorts[DmPortName.HdmiIn]; + + if (tx.Feedbacks["HdmiInHdcpCapability"] != null) + (tx.Feedbacks["HdmiInHdcpCapability"] as IntFeedback).LinkInputSig(trilist.UShortInput[joinMap.Port1HdcpState]); + + if (inputPort.ConnectionType == eRoutingPortConnectionType.Hdmi && inputPort.Port != null) + { + var port = inputPort.Port as EndpointHdmiInput; + + SetHdcpCapabilityAction(hdcpTypeSimple, port, joinMap.Port1HdcpState, trilist); + } + } + + if (txR.InputPorts[DmPortName.HdmiIn1] != null) + { + var inputPort = txR.InputPorts[DmPortName.HdmiIn1]; + + if (tx.Feedbacks["HdmiIn1HdcpCapability"] != null) + (tx.Feedbacks["HdmiIn1HdcpCapability"] as IntFeedback).LinkInputSig(trilist.UShortInput[joinMap.Port1HdcpState]); + + if (inputPort.ConnectionType == eRoutingPortConnectionType.Hdmi && inputPort.Port != null) + { + var port = inputPort.Port as EndpointHdmiInput; + + SetHdcpCapabilityAction(hdcpTypeSimple, port, joinMap.Port1HdcpState, trilist); + } + } + + if (txR.InputPorts[DmPortName.HdmiIn2] != null) + { + var inputPort = txR.InputPorts[DmPortName.HdmiIn2]; + + if (tx.Feedbacks["HdmiIn2HdcpCapability"] != null) + (tx.Feedbacks["HdmiIn2HdcpCapability"] as IntFeedback).LinkInputSig(trilist.UShortInput[joinMap.Port1HdcpState]); + + if (inputPort.ConnectionType == eRoutingPortConnectionType.Hdmi && inputPort.Port != null) + { + var port = inputPort.Port as EndpointHdmiInput; + + SetHdcpCapabilityAction(hdcpTypeSimple, port, joinMap.Port2HdcpState, trilist); + } + } + + } + } + + static void SetHdcpCapabilityAction(bool hdcpTypeSimple, EndpointHdmiInput port, uint join, BasicTriList trilist) + { + if (hdcpTypeSimple) + { + trilist.SetUShortSigAction(join, + new Action(s => + { + if (s == 0) + { + port.HdcpSupportOff(); + } + else if (s > 0) + { + port.HdcpSupportOn(); + } + })); + } + else + { + trilist.SetUShortSigAction(join, + new Action(s => + { + port.HdcpCapability = (eHdcpCapabilityType)s; + })); + } + } + + public class DmTxControllerJoinMap : JoinMapBase + { + public uint IsOnline { get; set; } + public uint VideoSyncStatus { get; set; } + public uint CurrentInputResolution { get; set; } + public uint HdcpSupportCapability { get; set; } + public uint VideoInput { get; set; } + public uint AudioInput { get; set; } + public uint Port1HdcpState { get; set; } + public uint Port2HdcpState { get; set; } + + + public DmTxControllerJoinMap() + { + // Digital + IsOnline = 1; + VideoSyncStatus = 2; + // Serial + CurrentInputResolution = 1; + // Analog + VideoInput = 1; + AudioInput = 2; + HdcpSupportCapability = 3; + Port1HdcpState = 4; + Port2HdcpState = 5; + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + IsOnline = IsOnline + joinOffset; + VideoSyncStatus = VideoSyncStatus + joinOffset; + CurrentInputResolution = CurrentInputResolution + joinOffset; + VideoInput = VideoInput + joinOffset; + AudioInput = AudioInput + joinOffset; + HdcpSupportCapability = HdcpSupportCapability + joinOffset; + Port1HdcpState = Port1HdcpState + joinOffset; + Port2HdcpState = Port2HdcpState + joinOffset; + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/GenericRelayDeviceBridge.cs b/PepperDashEssentials/Bridges/GenericRelayDeviceBridge.cs new file mode 100644 index 00000000..3871a80e --- /dev/null +++ b/PepperDashEssentials/Bridges/GenericRelayDeviceBridge.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; + + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.CrestronIO; + +namespace PepperDash.Essentials.Bridges +{ + public static class GenericRelayDeviceApiExtensions + { + public static void LinkToApi(this GenericRelayDevice relay, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as GenericRelayControllerJoinMap; + + if (joinMap == null) + joinMap = new GenericRelayControllerJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + if (relay.RelayOutput == null) + { + Debug.Console(1, relay, "Unable to link device '{0}'. Relay is null", relay.Key); + return; + } + + Debug.Console(1, relay, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + trilist.SetBoolSigAction(joinMap.Relay, new Action(b => + { + if (b) + relay.CloseRelay(); + else + relay.OpenRelay(); + })); + + // feedback for relay state + + relay.OutputIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.Relay]); + } + + } + + public class GenericRelayControllerJoinMap : JoinMapBase + { + //Digital + public uint Relay { get; set; } + + public GenericRelayControllerJoinMap() + { + Relay = 1; + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + Relay = Relay + joinOffset; + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/IBasicCommunicationBridge.cs b/PepperDashEssentials/Bridges/IBasicCommunicationBridge.cs index 230f7806..8eeed161 100644 --- a/PepperDashEssentials/Bridges/IBasicCommunicationBridge.cs +++ b/PepperDashEssentials/Bridges/IBasicCommunicationBridge.cs @@ -63,13 +63,18 @@ namespace PepperDash.Essentials.Bridges public class IBasicCommunicationJoinMap : JoinMapBase { - // Default joins + //Digital + public uint Connect { get; set; } + public uint Connected { get; set; } + + //Analog + public uint Status { get; set; } + + // Serial public uint TextReceived { get; set; } public uint SendText { get; set; } public uint SetPortConfig { get; set; } - public uint Connect { get; set; } - public uint Connected { get; set; } - public uint Status { get; set; } + public IBasicCommunicationJoinMap() { @@ -94,38 +99,5 @@ namespace PepperDash.Essentials.Bridges } } } - ///// - ///// - ///// - //public static class DmChassisControllerApiExtensions - //{ - // public static void LinkToApi(this PepperDash.Essentials.DM.DmChassisController chassis, - // BasicTriList trilist, Dictionary map, uint joinstart) - // { - // uint joinOffset = joinstart - 1; - - // uint videoSelectOffset = 0 + joinOffset; - // uint audioSelectOffset = 40 + joinOffset; - - - // // loop chassis number of inupts - // for (uint i = 1; i <= chassis.Chassis.NumberOfOutputs; i++) - // { - // trilist.SetUShortSigAction(videoSelectOffset + i, new Action(u => chassis.ExecuteSwitch(u, i, eRoutingSignalType.Video))); - // trilist.SetUShortSigAction(audioSelectOffset + i, new Action(u => chassis.ExecuteSwitch(u, i, eRoutingSignalType.Audio))); - // } - - // // wire up output change detection (try to add feedbacks or something to DMChassisController?? - - // // names? - - // // HDCP? - - - // } - //} - - - } \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/IDigitalInputBridge.cs b/PepperDashEssentials/Bridges/IDigitalInputBridge.cs new file mode 100644 index 00000000..c33329dd --- /dev/null +++ b/PepperDashEssentials/Bridges/IDigitalInputBridge.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; +using PepperDash.Essentials.Core.CrestronIO; + +namespace PepperDash.Essentials.Bridges +{ + public static class IDigitalInputApiExtenstions + { + public static void LinkToApi(this IDigitalInput input, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as IDigitalInputApiJoinMap; + + if (joinMap == null) + joinMap = new IDigitalInputApiJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + try + { + Debug.Console(1, input as Device, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + // Link feedback for input state + input.InputStateFeedback.LinkInputSig(trilist.BooleanInput[joinMap.InputState]); + } + catch (Exception e) + { + Debug.Console(1, input as Device, "Unable to link device '{0}'. Input is null", (input as Device).Key); + Debug.Console(1, input as Device, "Error: {0}", e); + return; + } + } + + + } + + public class IDigitalInputApiJoinMap : JoinMapBase + { + //Digital + public uint InputState { get; set; } + + public IDigitalInputApiJoinMap() + { + InputState = 1; + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + InputState = InputState + joinOffset; + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/SystemMonitorBridge.cs b/PepperDashEssentials/Bridges/SystemMonitorBridge.cs new file mode 100644 index 00000000..3702792f --- /dev/null +++ b/PepperDashEssentials/Bridges/SystemMonitorBridge.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.Diagnostics; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Monitoring; + +namespace PepperDash.Essentials.Bridges +{ + public static class SystemMonitorBridge + { + public static void LinkToApi(this SystemMonitorController systemMonitorController, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as SystemMonitorJoinMap; + + if (joinMap == null) + joinMap = new SystemMonitorJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + //Debug.Console(1, systemMonitorController, "Linking API starting at join: {0}", joinStart); + + systemMonitorController.TimeZoneFeedback.LinkInputSig(trilist.UShortInput[joinMap.TimeZone]); + //trilist.SetUShortSigAction(joinMap.TimeZone, new Action(u => systemMonitorController.SetTimeZone(u))); + systemMonitorController.TimeZoneTextFeedback.LinkInputSig(trilist.StringInput[joinMap.TimeZoneName]); + + systemMonitorController.IOControllerVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.IOControllerVersion]); + systemMonitorController.SnmpVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.SnmpAppVersion]); + systemMonitorController.BACnetAppVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.BACnetAppVersion]); + systemMonitorController.ControllerVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.ControllerVersion]); + + + // iterate the program status feedback collection and map all the joins + var programSlotJoinStart = joinMap.ProgramStartJoin; + + foreach (var p in systemMonitorController.ProgramStatusFeedbackCollection) + { + + // TODO: link feedbacks for each program slot + var programNumber = p.Value.Program.Number; + + //Debug.Console(1, systemMonitorController, "Linking API for Program Slot: {0} starting at join: {1}", programNumber, programSlotJoinStart); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramStart, new Action + (b => SystemMonitor.ProgramCollection[programNumber].OperatingState = eProgramOperatingState.Start)); + p.Value.ProgramStartedFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramStart]); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramStop, new Action + (b => SystemMonitor.ProgramCollection[programNumber].OperatingState = eProgramOperatingState.Stop)); + p.Value.ProgramStoppedFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramStop]); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramRegister, new Action + (b => SystemMonitor.ProgramCollection[programNumber].RegistrationState = eProgramRegistrationState.Register)); + p.Value.ProgramRegisteredFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramRegister]); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramUnregister, new Action + (b => SystemMonitor.ProgramCollection[programNumber].RegistrationState = eProgramRegistrationState.Unregister)); + p.Value.ProgramUnregisteredFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramUnregister]); + + programSlotJoinStart = programSlotJoinStart + joinMap.ProgramOffsetJoin; + } + + } + + + } + + public class SystemMonitorJoinMap : JoinMapBase + { + /// + /// Offset to indicate where the range of iterated program joins will start + /// + public uint ProgramStartJoin { get; set; } + + /// + /// Offset between each program join set + /// + public uint ProgramOffsetJoin { get; set; } + + //Digital + public uint ProgramStart { get; set; } + public uint ProgramStop { get; set; } + public uint ProgramRegister { get; set; } + public uint ProgramUnregister { get; set; } + + //Analog + public uint TimeZone { get; set; } + + //Serial + public uint TimeZoneName { get; set; } + public uint IOControllerVersion { get; set; } + public uint SnmpAppVersion { get; set; } + public uint BACnetAppVersion { get; set; } + public uint ControllerVersion { get; set; } + + public uint ProgramName { get; set; } + public uint ProgramCompiledTime { get; set; } + public uint ProgramCrestronDatabaseVersion { get; set; } + public uint ProgramEnvironmentVersion { get; set; } + public uint AggregatedProgramInfo { get; set; } + + public SystemMonitorJoinMap() + { + TimeZone = 1; + + TimeZoneName = 1; + IOControllerVersion = 2; + SnmpAppVersion = 3; + BACnetAppVersion = 4; + ControllerVersion = 5; + + + ProgramStartJoin = 10; + + ProgramOffsetJoin = 5; + + // Offset in groups of 5 joins + ProgramStart = 1; + ProgramStop = 2; + ProgramRegister = 3; + ProgramUnregister = 4; + + ProgramName = 1; + ProgramCompiledTime = 2; + ProgramCrestronDatabaseVersion = 3; + ProgramEnvironmentVersion = 4; + AggregatedProgramInfo = 5; + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + TimeZone = TimeZone + joinOffset; + + TimeZoneName = TimeZoneName + joinOffset; + IOControllerVersion = IOControllerVersion + joinOffset; + SnmpAppVersion = SnmpAppVersion + joinOffset; + BACnetAppVersion = BACnetAppVersion + joinOffset; + ControllerVersion = ControllerVersion + joinOffset; + + // Sets the initial join value where the iterated program joins will begin + ProgramStartJoin = ProgramStartJoin + joinOffset; + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Config/ConfigReader.cs b/PepperDashEssentials/Config/ConfigReader.cs deleted file mode 100644 index 5c517a13..00000000 --- a/PepperDashEssentials/Config/ConfigReader.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials -{ - /// - /// Loads the ConfigObject from the file - /// - public class ConfigReader - { - public static EssentialsConfig ConfigObject { get; private set; } - - public static bool LoadConfig2() - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading unmerged system/template portal configuration file."); - try - { - var filePath = Global.FilePathPrefix + "configurationFile.json"; - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to load config file: '{0}'", filePath); - - if (!File.Exists(filePath)) - { - Debug.Console(0, Debug.ErrorLogLevel.Error, - "ERROR: Configuration file not present. Please load file to {0} and reset program", filePath); - return false; - } - - using (StreamReader fs = new StreamReader(filePath)) - { - var doubleObj = JObject.Parse(fs.ReadToEnd()); - ConfigObject = MergeConfigs(doubleObj).ToObject(); - - // Extract SystemUrl and TemplateUrl into final config output - - if (doubleObj["system_url"] != null) - { - ConfigObject.SystemUrl = doubleObj["system_url"].Value(); - } - - if (doubleObj["template_url"] != null) - { - ConfigObject.TemplateUrl= doubleObj["template_url"].Value(); - } - } - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Successfully Loaded Merged Config"); - - return true; - } - catch (Exception e) - { - Debug.Console(0, Debug.ErrorLogLevel.Error, "ERROR: Config load failed: \r{0}", e); - return false; - } - } - - - static JObject MergeConfigs(JObject doubleConfig) - { - var system = JObject.FromObject(doubleConfig["system"]); - var template = JObject.FromObject(doubleConfig["template"]); - var merged = new JObject(); - - // Put together top-level objects - // skip any objects that don't have template objects - - if (system["info"] != null) - merged.Add("info", Merge(template["info"], system["info"])); - else - merged.Add("info", template["info"]); - - merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray, - system["devices"] as JArray, "uid")); - - if (template["rooms"] != null) - { - if (system["rooms"] == null) - merged.Add("rooms", template["rooms"]); - else - merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray, - system["rooms"] as JArray, "key")); - } - - if (template["sourceLists"] != null) - { - if (system["sourceLists"] == null) - merged.Add("sourceLists", template["sourceLists"]); - else - merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"])); - } - - // Template tie lines take precdence. Config tool doesn't do them at system - // level anyway... - if (template["tieLines"] != null) - merged.Add("tieLines", template["tieLines"]); - //else if (system["tieLines"] != null) - // merged.Add("tieLines", system["tieLines"]); - //else - // merged.Add("tieLines", new JArray()); - - Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged); - return merged; - } - - /// - /// Merges the contents of a base and a delta array, matching the entries on a top-level property - /// given by propertyName. Returns a merge of them. Items in the delta array that do not have - /// a matched item in base array will not be merged. - /// - static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName) - { - var result = new JArray(); - if (a2 == null) - result = a1; - else if (a1 != null) - { - for (int i = 0; i < a1.Count(); i++) - { - var a1Dev = a1[i]; - // Try to get a system device and if found, merge it onto template - var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value("uid") == tmplDev.Value("uid")); - if (a2Match != null) - { - var mergedItem = Merge(a1Dev, a2Match);// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match)); - result.Add(mergedItem); - } - else - result.Add(a1Dev); - } - } - return result; - } - - - /// - /// Helper for using with JTokens. Converts to JObject - /// - static JObject Merge(JToken t1, JToken t2) - { - return Merge(JObject.FromObject(t1), JObject.FromObject(t2)); - } - - /// - /// Merge b ONTO a - /// - /// - /// - static JObject Merge(JObject o1, JObject o2) - { - foreach (var o2Prop in o2) - { - var o1Value = o1[o2Prop.Key]; - if (o1Value == null) - o1.Add(o2Prop.Key, o2Prop.Value); - else - { - JToken replacement = null; - if (o2Prop.Value.HasValues && o1Value.HasValues) // Drill down - replacement = Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Prop.Value)); - else - replacement = o2Prop.Value; - o1[o2Prop.Key].Replace(replacement); - } - } - return o1; - } - - /// - /// Returns the group for a given device key in config - /// - /// - /// - public static string GetGroupForDeviceKey(string key) - { - var dev = ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); - return dev == null ? null : dev.Group; - } - - } -} \ No newline at end of file diff --git a/PepperDashEssentials/Config/EssentialsConfig.cs b/PepperDashEssentials/Config/EssentialsConfig.cs deleted file mode 100644 index 71c2be9f..00000000 --- a/PepperDashEssentials/Config/EssentialsConfig.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -using Crestron.SimplSharp.CrestronIO; -using Newtonsoft.Json; - -using PepperDash.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Room.Config; - -namespace PepperDash.Essentials -{ - /// - /// Loads the ConfigObject from the file - /// - public class EssentialsConfig : BasicConfig - { - [JsonProperty("system_url")] - public string SystemUrl { get; set; } - - [JsonProperty("template_url")] - public string TemplateUrl { get; set; } - - - //public CotijaConfig Cotija { get; private set; } - - [JsonProperty("systemUuid")] - public string SystemUuid - { - get - { - var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*"); - - string uuid = result.Groups[1].Value; - - return uuid; - } - } - - [JsonProperty("templateUuid")] - public string TemplateUuid - { - get - { - var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*"); - - string uuid = result.Groups[1].Value; - - return uuid; - } - } - - [JsonProperty("rooms")] - public List Rooms { get; set; } - } - - /// - /// - /// - public class SystemTemplateConfigs - { - public EssentialsConfig System { get; set; } - - public EssentialsConfig Template { get; set; } - } -} \ No newline at end of file diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index a607ee5b..a63ceee6 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -4,11 +4,15 @@ using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.CrestronThread; +using Crestron.SimplSharpPro.Diagnostics; + using PepperDash.Core; using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Devices.Common; using PepperDash.Essentials.DM; using PepperDash.Essentials.Fusion; +using PepperDash.Essentials.Room.Config; using PepperDash.Essentials.Room.Cotija; namespace PepperDash.Essentials @@ -30,11 +34,19 @@ namespace PepperDash.Essentials /// public override void InitializeSystem() { + SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; + DeterminePlatform(); //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 => + { + Debug.Console(0, Debug.ErrorLogLevel.Notice, "CONSOLE MESSAGE: {0}", s); + }, "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => { foreach (var tl in TieLineCollection.Default) @@ -94,7 +106,7 @@ namespace PepperDash.Essentials { filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on XiO Edge Server", versionString); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on Virtual Control Server", versionString); } Global.SetFilePathPrefix(filePathPrefix); @@ -137,12 +149,18 @@ namespace PepperDash.Essentials "------------------------------------------------\r" + "------------------------------------------------"); } + } catch (Exception e) { Debug.Console(0, "FATAL INITIALIZE ERROR. System is in an inconsistent state:\r{0}", e); + + } + // Notify the + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; + } /// @@ -194,6 +212,7 @@ namespace PepperDash.Essentials void Load() { LoadDevices(); + LinkSystemMonitorToAppServer(); LoadTieLines(); LoadRooms(); LoadLogoServer(); @@ -201,6 +220,26 @@ namespace PepperDash.Essentials DeviceManager.ActivateAll(); } + void LinkSystemMonitorToAppServer() + { + var sysMon = DeviceManager.GetDeviceForKey("systemMonitor") as PepperDash.Essentials.Core.Monitoring.SystemMonitorController; + + var appServer = DeviceManager.GetDeviceForKey("appServer") as CotijaSystemController; + + + if (sysMon != null && appServer != null) + { + var key = sysMon.Key + "-" + appServer.Key; + var messenger = new PepperDash.Essentials.AppServer.Messengers.SystemMonitorMessenger + (key, sysMon, "/device/systemMonitor"); + + messenger.RegisterWithAppServer(appServer); + + DeviceManager.AddDevice(messenger); + + + } + } /// /// Reads all devices from config and adds them to DeviceManager @@ -211,6 +250,9 @@ namespace PepperDash.Essentials // Build the processor wrapper class // DeviceManager.AddDevice(new PepperDash.Essentials.Core.Devices.CrestronProcessor("processor")); + // Add global System Monitor device + DeviceManager.AddDevice(new PepperDash.Essentials.Core.Monitoring.SystemMonitorController("systemMonitor")); + foreach (var devConf in ConfigReader.ConfigObject.Devices) { @@ -228,10 +270,15 @@ namespace PepperDash.Essentials continue; } - // Try local factory first + // Try local factories first var newDev = DeviceFactory.GetDevice(devConf); + if (newDev == null) + newDev = BridgeFactory.GetDevice(devConf); + // Then associated library factories + if (newDev == null) + newDev = PepperDash.Essentials.Core.DeviceFactory.GetDevice(devConf); if (newDev == null) newDev = PepperDash.Essentials.Devices.Common.DeviceFactory.GetDevice(devConf); if (newDev == null) @@ -254,6 +301,7 @@ namespace PepperDash.Essentials } + /// /// Helper method to load tie lines. This should run after devices have loaded /// @@ -293,7 +341,7 @@ namespace PepperDash.Essentials foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) { - var room = roomConfig.GetRoomObject(); + var room = EssentialsRoomConfigHelper.GetRoomObject(roomConfig) as EssentialsRoomBase; if (room != null) { if (room is EssentialsHuddleSpaceRoom) @@ -318,10 +366,16 @@ 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); + AddBridgePostActivationHelper(bridge); // Lets things happen later when all devices are present + DeviceManager.AddDevice(bridge); } else { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is NOT EssentialsHuddleSpaceRoom, attempting to add to DeviceManager w/o Fusion"); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is NOT EssentialsRoom, attempting to add to DeviceManager w/o Fusion"); DeviceManager.AddDevice(room); } @@ -345,7 +399,8 @@ namespace PepperDash.Essentials var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; if (parent == null) { - Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); + Debug.Console(0, bridge, "ERROR: Cannot connect app server room bridge. System controller not present"); + return; } Debug.Console(0, bridge, "Linking to parent controller"); bridge.AddParent(parent); diff --git a/PepperDashEssentials/ControlSystem.cs.orig b/PepperDashEssentials/ControlSystem.cs.orig deleted file mode 100644 index bd9590c4..00000000 --- a/PepperDashEssentials/ControlSystem.cs.orig +++ /dev/null @@ -1,367 +0,0 @@ -using System; -using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.CrestronThread; -using PepperDash.Core; -using PepperDash.Core.PortalSync; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common; -using PepperDash.Essentials.DM; -using PepperDash.Essentials.Fusion; -using PepperDash.Essentials.Room.Cotija; - -namespace PepperDash.Essentials -{ - public class ControlSystem : CrestronControlSystem - { - PepperDashPortalSyncClient PortalSync; - HttpLogoServer LogoServer; - - public ControlSystem() - : base() - { - Thread.MaxNumberOfUserThreads = 400; - Global.ControlSystem = this; - DeviceManager.Initialize(this); - } - - /// - /// Git 'er goin' - /// - public override void InitializeSystem() - { -<<<<<<< HEAD - DeterminePlatform(); - - //CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Loads configuration file", - // ConsoleAccessLevelEnum.AccessOperator); -======= - CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Loads configuration file", - ConsoleAccessLevelEnum.AccessOperator); ->>>>>>> 600b9f11ff1bbc186f7c2a2945955731b3523b3c - - CrestronConsole.AddNewConsoleCommand(s => - { - foreach (var tl in TieLineCollection.Default) - CrestronConsole.ConsoleCommandResponse(" {0}\r", tl); - }, - "listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => - { - CrestronConsole.ConsoleCommandResponse - ("Current running configuration. This is the merged system and template configuration"); - CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject - (ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented)); - }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => - { - CrestronConsole.ConsoleCommandResponse("This system can be found at the following URLs:\r" + - "System URL: {0}\r" + - "Template URL: {1}", ConfigReader.ConfigObject.SystemUrl, ConfigReader.ConfigObject.TemplateUrl); - }, "portalinfo", "Shows portal URLS from configuration", ConsoleAccessLevelEnum.AccessOperator); - - //GoWithLoad(); - } - - /// - /// Determines if the program is running on a processor (appliance) or server (XiO Edge). - /// - /// Sets Global.FilePathPrefix based on platform - /// - 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(); -#warning ^ For use with beta Include4.dat for XiO Edge - directoryPrefix = ""; - - if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) - { - filePathPrefix = directoryPrefix + dirSeparator + "NVRAM" - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on 3-series Appliance", versionString); - } - else - { - filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on XiO Edge Server", versionString); - } - - Global.SetFilePathPrefix(filePathPrefix); - } - - /// - /// Do it, yo - /// - public void GoWithLoad() - { - try - { - CrestronConsole.AddNewConsoleCommand(EnablePortalSync, "portalsync", "Loads Portal Sync", - ConsoleAccessLevelEnum.AccessOperator); - - //PortalSync = new PepperDashPortalSyncClient(); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials load from configuration"); - - var filesReady = SetupFilesystem(); - if (filesReady) - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Folder structure verified. Loading config..."); - if (!ConfigReader.LoadConfig2()) - return; - - Load(); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Essentials load complete\r" + - "-------------------------------------------------------------"); - } - else - { - Debug.Console(0, - "------------------------------------------------\r" + - "------------------------------------------------\r" + - "------------------------------------------------\r" + - "Essentials file structure setup completed.\r" + - "Please load config, sgd and ir files and\r" + - "restart program.\r" + - "------------------------------------------------\r" + - "------------------------------------------------\r" + - "------------------------------------------------"); - } - } - catch (Exception e) - { - Debug.Console(0, "FATAL INITIALIZE ERROR. System is in an inconsistent state:\r{0}", e); - } - - } - - /// - /// Verifies filesystem is set up. IR, SGD, and program1 folders - /// - bool SetupFilesystem() - { - Debug.Console(0, "Verifying and/or creating folder structure"); - var configDir = Global.FilePathPrefix; - var configExists = Directory.Exists(configDir); - if (!configExists) - Directory.Create(configDir); - - var irDir = Global.FilePathPrefix + "ir"; - if (!Directory.Exists(irDir)) - Directory.Create(irDir); - - var sgdDir = Global.FilePathPrefix + "sgd"; - if (!Directory.Exists(sgdDir)) - Directory.Create(sgdDir); - - return configExists; - } - - public void EnablePortalSync(string s) - { - if (s.ToLower() == "enable") - { - CrestronConsole.ConsoleCommandResponse("Portal Sync features enabled"); - PortalSync = new PepperDashPortalSyncClient(); - } - } - - public void TearDown() - { - Debug.Console(0, "Tearing down existing system"); - DeviceManager.DeactivateAll(); - - TieLineCollection.Default.Clear(); - - foreach (var key in DeviceManager.GetDevices()) - DeviceManager.RemoveDevice(key); - - Debug.Console(0, "Tear down COMPLETE"); - } - - /// - /// - /// - void Load() - { - LoadDevices(); - LoadTieLines(); - LoadRooms(); - LoadLogoServer(); - - DeviceManager.ActivateAll(); - } - - - /// - /// Reads all devices from config and adds them to DeviceManager - /// - public void LoadDevices() - { - foreach (var devConf in ConfigReader.ConfigObject.Devices) - { - - try - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Creating device '{0}'", devConf.Key); - // Skip this to prevent unnecessary warnings - if (devConf.Key == "processor") - continue; - - // Try local factory first - var newDev = DeviceFactory.GetDevice(devConf); - - // Then associated library factories - if (newDev == null) - newDev = PepperDash.Essentials.Devices.Common.DeviceFactory.GetDevice(devConf); - if (newDev == null) - newDev = PepperDash.Essentials.DM.DeviceFactory.GetDevice(devConf); - if (newDev == null) - newDev = PepperDash.Essentials.Devices.Displays.DisplayDeviceFactory.GetDevice(devConf); - - if (newDev != null) - DeviceManager.AddDevice(newDev); - else - Debug.Console(0, Debug.ErrorLogLevel.Notice, "ERROR: Cannot load unknown device type '{0}', key '{1}'.", devConf.Type, devConf.Key); - } - catch (Exception e) - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "ERROR: Creating device {0}. Skipping device. \r{1}", devConf.Key, e); - } - } - Debug.Console(0, Debug.ErrorLogLevel.Notice, "All Devices Loaded."); - - } - - /// - /// Helper method to load tie lines. This should run after devices have loaded - /// - public void LoadTieLines() - { - // In the future, we can't necessarily just clear here because devices - // might be making their own internal sources/tie lines - - var tlc = TieLineCollection.Default; - //tlc.Clear(); - if (ConfigReader.ConfigObject.TieLines == null) - { - return; - } - - foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines) - { - var newTL = tieLineConfig.GetTieLine(); - if (newTL != null) - tlc.Add(newTL); - } - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "All Tie Lines Loaded."); - - } - - /// - /// Reads all rooms from config and adds them to DeviceManager - /// - public void LoadRooms() - { - if (ConfigReader.ConfigObject.Rooms == null) - { - Debug.Console(0, Debug.ErrorLogLevel.Warning, "WARNING: Configuration contains no rooms"); - return; - } - - foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) - { - var room = roomConfig.GetRoomObject(); - if (room != null) - { - if (room is EssentialsHuddleSpaceRoom) - { - 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)); - - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Cotija Bridge..."); - // Cotija bridge - var bridge = new CotijaEssentialsHuddleSpaceRoomBridge(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..."); - } - else if (room is EssentialsHuddleVtc1Room) - { - DeviceManager.AddDevice(room); - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is EssentialsHuddleVtc1Room, attempting to add to DeviceManager with Fusion"); - DeviceManager.AddDevice(new EssentialsHuddleVtc1FusionController((EssentialsHuddleVtc1Room)room, 0xf1)); - } - else - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is NOT EssentialsHuddleSpaceRoom, attempting to add to DeviceManager w/o Fusion"); - DeviceManager.AddDevice(room); - } - - } - else - Debug.Console(0, Debug.ErrorLogLevel.Notice, "WARNING: Cannot create room from config, key '{0}'", roomConfig.Key); - } - - Debug.Console(0, Debug.ErrorLogLevel.Notice, "All Rooms Loaded."); - - } - - /// - /// Helps add the post activation steps that link bridges to main controller - /// - /// - void AddBridgePostActivationHelper(CotijaBridgeBase bridge) - { - bridge.AddPostActivationAction(() => - { - var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; - if (parent == null) - { - Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); - } - Debug.Console(0, bridge, "Linking to parent controller"); - bridge.AddParent(parent); - parent.AddBridge(bridge); - }); - } - - /// - /// Fires up a logo server if not already running - /// - void LoadLogoServer() - { - try - { - LogoServer = new HttpLogoServer(8080, Global.FilePathPrefix + "html" + Global.DirectorySeparator + "logo"); - } - catch (Exception) - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "NOTICE: Logo server cannot be started. Likely already running in another program"); - } - } - } -} diff --git a/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs similarity index 91% rename from PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index a463fa50..e7623975 100644 --- a/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -17,6 +17,7 @@ using Newtonsoft.Json.Linq; 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; @@ -30,6 +31,10 @@ namespace PepperDash.Essentials.Fusion //public event EventHandler MeetingEndWarning; //public event EventHandler NextMeetingBeginWarning; + public event EventHandler RoomInfoChange; + + public FusionCustomPropertiesBridge CustomPropertiesBridge = new FusionCustomPropertiesBridge(); + protected FusionRoom FusionRoom; protected EssentialsRoomBase Room; Dictionary SourceToFeedbackSigs = @@ -334,6 +339,8 @@ namespace PepperDash.Essentials.Fusion 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;"; + SetUpEthernetValues(); + GetProcessorEthernetValues(); GetSystemInfo(); @@ -363,7 +370,7 @@ namespace PepperDash.Essentials.Fusion systemReboot.OutputSig.SetSigFalseAction(() => CrestronConsole.SendControlSystemCommand("reboot", ref response)); } - protected void GetProcessorEthernetValues() + protected void SetUpEthernetValues() { Ip1 = FusionRoom.CreateOffsetStringSig(50, "Info - Processor - IP 1", eSigIoMask.InputSigOnly); Ip2 = FusionRoom.CreateOffsetStringSig(51, "Info - Processor - IP 2", eSigIoMask.InputSigOnly); @@ -376,8 +383,10 @@ namespace PepperDash.Essentials.Fusion Mac2 = FusionRoom.CreateOffsetStringSig(58, "Info - Processor - MAC 2", eSigIoMask.InputSigOnly); NetMask1 = FusionRoom.CreateOffsetStringSig(59, "Info - Processor - Net Mask 1", eSigIoMask.InputSigOnly); NetMask2 = FusionRoom.CreateOffsetStringSig(60, "Info - Processor - Net Mask 2", eSigIoMask.InputSigOnly); + } - // Interface =0 + protected void GetProcessorEthernetValues() + { Ip1.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); Gateway.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0); Hostname.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); @@ -420,6 +429,16 @@ namespace PepperDash.Essentials.Fusion } + protected void GetCustomProperties() + { + if (FusionRoom.IsOnline) + { + string fusionRoomCustomPropertiesRequest = @"RoomConfigurationRequest"; + + FusionRoom.ExtenderFusionRoomDataReservedSigs.RoomConfigQuery.StringValue = fusionRoomCustomPropertiesRequest; + } + } + void GetTouchpanelInfo() { // TODO Get IP and Project Name from TP @@ -465,6 +484,7 @@ namespace PepperDash.Essentials.Fusion FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQuery.StringValue = actionRequest; + GetCustomProperties(); // Request current Fusion Server Time RequestLocalDateTime(null); @@ -748,6 +768,82 @@ namespace PepperDash.Essentials.Fusion Debug.Console(1, this, "Error parsing LocalDateTimeQueryResponse: {0}", e); } } + else if (args.Sig == FusionRoom.ExtenderFusionRoomDataReservedSigs.RoomConfigResponse) + { + // Room info response with custom properties + + string roomConfigResponseArgs = args.Sig.StringValue.Replace("&", "and"); + + Debug.Console(2, this, "Fusion Response: \n {0}", roomConfigResponseArgs); + + try + { + XmlDocument roomConfigResponse = new XmlDocument(); + + roomConfigResponse.LoadXml(roomConfigResponseArgs); + + var requestRoomConfiguration = roomConfigResponse["RoomConfigurationResponse"]; + + if (requestRoomConfiguration != null) + { + RoomInformation roomInformation = new RoomInformation(); + + foreach (XmlElement e in roomConfigResponse.FirstChild.ChildNodes) + { + if (e.Name == "RoomInformation") + { + XmlReader roomInfo = new XmlReader(e.OuterXml); + + roomInformation = CrestronXMLSerialization.DeSerializeObject(roomInfo); + } + else if (e.Name == "CustomFields") + { + foreach (XmlElement el in e) + { + FusionCustomProperty customProperty = new FusionCustomProperty(); + + if (el.Name == "CustomField") + { + customProperty.ID = el.Attributes["ID"].Value; + } + + foreach (XmlElement elm in el) + { + if (elm.Name == "CustomFieldName") + { + customProperty.CustomFieldName = elm.InnerText; + } + if (elm.Name == "CustomFieldType") + { + customProperty.CustomFieldType = elm.InnerText; + } + if (elm.Name == "CustomFieldValue") + { + customProperty.CustomFieldValue = elm.InnerText; + } + } + + roomInformation.FusionCustomProperties.Add(customProperty); + } + } + } + + var handler = RoomInfoChange; + if (handler != null) + handler(this, new EventArgs()); + + CustomPropertiesBridge.EvaluateRoomInfo(Room.Key, roomInformation); + } + } + catch (Exception e) + { + Debug.Console(1, this, "Error parsing Custom Properties response: {0}", e); + } + //PrintRoomInfo(); + //getRoomInfoBusy = false; + //_DynFusion.API.EISC.BooleanInput[Constants.GetRoomInfo].BoolValue = getRoomInfoBusy; + } + } /// @@ -1461,5 +1557,39 @@ namespace PepperDash.Essentials.Fusion } } - + public class RoomInformation + { + public string ID { get; set; } + public string Name { get; set; } + public string Location { get; set; } + public string Description { get; set; } + public string TimeZone { get; set; } + public string WebcamURL { get; set; } + public string BacklogMsg { get; set; } + public string SubErrorMsg { get; set; } + public string EmailInfo { get; set; } + public List FusionCustomProperties { get; set; } + + public RoomInformation() + { + FusionCustomProperties = new List(); + } + } + public class FusionCustomProperty + { + public string ID { get; set; } + public string CustomFieldName { get; set; } + public string CustomFieldType { get; set; } + public string CustomFieldValue { get; set; } + + public FusionCustomProperty() + { + + } + + public FusionCustomProperty(string id) + { + ID = id; + } + } } \ No newline at end of file diff --git a/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs similarity index 97% rename from PepperDashEssentials/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs index 214e3537..c79d49d7 100644 --- a/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs +++ b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs @@ -13,6 +13,7 @@ using Crestron.SimplSharpPro.Fusion; 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; @@ -186,14 +187,7 @@ namespace PepperDash.Essentials.Fusion FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction((Room as EssentialsHuddleVtc1Room).PowerOnToDefaultOrLastSource); FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() => (Room as EssentialsHuddleVtc1Room).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;"; - - GetProcessorEthernetValues(); - - GetSystemInfo(); - - GetProcessorInfo(); + CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; } diff --git a/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionCustomPropertiesBridge.cs b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionCustomPropertiesBridge.cs new file mode 100644 index 00000000..b5704b40 --- /dev/null +++ b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionCustomPropertiesBridge.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +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.Room.Behaviours; + +namespace PepperDash.Essentials.Fusion +{ + /// + /// Handles mapping Fusion Custom Property values to system properties + /// + public class FusionCustomPropertiesBridge + { + + /// + /// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed + /// + /// + public void EvaluateRoomInfo(string roomKey, RoomInformation roomInfo) + { + try + { + var reconfigurableDevices = DeviceManager.AllDevices.Where(d => d is ReconfigurableDevice); + + foreach (var device in reconfigurableDevices) + { + // Get the current device config so new values can be overwritten over existing + var deviceConfig = (device as ReconfigurableDevice).Config; + + if (device is RoomOnToDefaultSourceWhenOccupied) + { + Debug.Console(1, "Mapping Room on via Occupancy values from Fusion"); + + var devProps = JsonConvert.DeserializeObject(deviceConfig.Properties.ToString()); + + var enableFeature = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupied")); + if (enableFeature != null) + devProps.EnableRoomOnWhenOccupied = bool.Parse(enableFeature.CustomFieldValue); + + var enableTime = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomOnWhenOccupiedStartTime")); + if (enableTime != null) + devProps.OccupancyStartTime = enableTime.CustomFieldValue; + + var disableTime = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomOnWhenOccupiedEndTime")); + if (disableTime != null) + devProps.OccupancyEndTime = disableTime.CustomFieldValue; + + var enableSunday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedSun")); + if (enableSunday != null) + devProps.EnableSunday = bool.Parse(enableSunday.CustomFieldValue); + + var enableMonday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedMon")); + if (enableMonday != null) + devProps.EnableMonday = bool.Parse(enableMonday.CustomFieldValue); + + var enableTuesday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedTue")); + if (enableTuesday != null) + devProps.EnableTuesday = bool.Parse(enableTuesday.CustomFieldValue); + + var enableWednesday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedWed")); + if (enableWednesday != null) + devProps.EnableWednesday = bool.Parse(enableWednesday.CustomFieldValue); + + var enableThursday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedThu")); + if (enableThursday != null) + devProps.EnableThursday = bool.Parse(enableThursday.CustomFieldValue); + + var enableFriday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedFri")); + if (enableFriday != null) + devProps.EnableFriday = bool.Parse(enableFriday.CustomFieldValue); + + var enableSaturday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedSat")); + if (enableSaturday != null) + devProps.EnableSaturday = bool.Parse(enableSaturday.CustomFieldValue); + + deviceConfig.Properties = JToken.FromObject(devProps); + } + else if (device is EssentialsRoomBase) + { + // Set the room name + if (!string.IsNullOrEmpty(roomInfo.Name)) + { + Debug.Console(1, "Current Room Name: {0}. New Room Name: {1}", deviceConfig.Name, roomInfo.Name); + // Set the name in config + deviceConfig.Name = roomInfo.Name; + + Debug.Console(1, "Room Name Successfully Changed."); + } + + // Set the help message + var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage")); + if (helpMessage != null) + { + //Debug.Console(1, "Current Help Message: {0}. New Help Message: {1}", deviceConfig.Properties["help"]["message"].Value(ToString()), helpMessage.CustomFieldValue); + deviceConfig.Properties["helpMessage"] = (string)helpMessage.CustomFieldValue; + } + } + + // Set the config on the device + (device as ReconfigurableDevice).SetConfig(deviceConfig); + } + + + } + catch (Exception e) + { + Debug.Console(1, "FusionCustomPropetiesBridge: Error mapping properties: {0}", e); + } + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/OTHER/Fusion/FusionEventHandlers.cs b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionEventHandlers.cs similarity index 100% rename from PepperDashEssentials/OTHER/Fusion/FusionEventHandlers.cs rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionEventHandlers.cs diff --git a/PepperDashEssentials/OTHER/Fusion/FusionProcessorQueries.cs b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionProcessorQueries.cs similarity index 100% rename from PepperDashEssentials/OTHER/Fusion/FusionProcessorQueries.cs rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionProcessorQueries.cs diff --git a/PepperDashEssentials/OTHER/Fusion/FusionRviDataClasses.cs b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionRviDataClasses.cs similarity index 100% rename from PepperDashEssentials/OTHER/Fusion/FusionRviDataClasses.cs rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionRviDataClasses.cs diff --git a/PepperDashEssentials/OTHER/Fusion/FusionSystemController.cs.orig b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionSystemController.cs.orig similarity index 100% rename from PepperDashEssentials/OTHER/Fusion/FusionSystemController.cs.orig rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/FusionSystemController.cs.orig diff --git a/PepperDashEssentials/Factory/DeviceFactory.cs b/PepperDashEssentials/Factory/DeviceFactory.cs index 16afc536..86612747 100644 --- a/PepperDashEssentials/Factory/DeviceFactory.cs +++ b/PepperDashEssentials/Factory/DeviceFactory.cs @@ -1,92 +1,97 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials -{ - public class DeviceFactory - { - public static IKeyed GetDevice(DeviceConfig dc) - { - var key = dc.Key; - var name = dc.Name; - var type = dc.Type; - var properties = dc.Properties; - - var typeName = dc.Type.ToLower(); - - if (typeName == "amplifier") - { - return new Amplifier(dc.Key, dc.Name); - } - else if (dc.Group.ToLower() == "touchpanel") // typeName.StartsWith("tsw")) - { - return UiDeviceFactory.GetUiDevice(dc); - } - - else if (typeName == "mockdisplay") - { - return new MockDisplay(key, name); - } - - else if (typeName == "generic") - { - return new Device(key, name); - } - - // MOVE into something else??? - else if (typeName == "basicirdisplay") - { - var ir = IRPortHelper.GetIrPort(properties); - if (ir != null) - return new BasicIrDisplay(key, name, ir.Port, ir.FileName); - } - - else if (typeName == "commmock") - { - var comm = CommFactory.CreateCommForDevice(dc); - var props = JsonConvert.DeserializeObject( - properties.ToString()); - return new ConsoleCommMockDevice(key, name, props, comm); - } - - else if (typeName == "appserver") - { - var props = JsonConvert.DeserializeObject(properties.ToString()); - return new CotijaSystemController(key, name, props); - } - - else if (typeName == "mobilecontrolbridge-ddvc01") - { - var comm = CommFactory.GetControlPropertiesConfig(dc); - - var bridge = new PepperDash.Essentials.Room.Cotija.CotijaDdvc01RoomBridge(key, name, comm.IpIdInt); - bridge.AddPreActivationAction(() => - { - var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; - if (parent == null) - { - Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); - } - Debug.Console(0, bridge, "Linking to parent controller"); - bridge.AddParent(parent); - parent.AddBridge(bridge); - }); - - return bridge; - } - - return null; - } - } - -} +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharpPro; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials +{ + public class DeviceFactory + { + public static IKeyed GetDevice(DeviceConfig dc) + { + var key = dc.Key; + var name = dc.Name; + var type = dc.Type; + var properties = dc.Properties; + + var typeName = dc.Type.ToLower(); + + if (typeName == "amplifier") + { + return new Amplifier(dc.Key, dc.Name); + } + else if (dc.Group.ToLower() == "touchpanel") // typeName.StartsWith("tsw")) + { + return UiDeviceFactory.GetUiDevice(dc); + } + + else if (typeName == "mockdisplay") + { + return new MockDisplay(key, name); + } + + else if (typeName == "generic") + { + return new Device(key, name); + } + + // MOVE into something else??? + else if (typeName == "basicirdisplay") + { + var ir = IRPortHelper.GetIrPort(properties); + if (ir != null) + return new BasicIrDisplay(key, name, ir.Port, ir.FileName); + } + + else if (typeName == "commmock") + { + var comm = CommFactory.CreateCommForDevice(dc); + var props = JsonConvert.DeserializeObject( + properties.ToString()); + return new ConsoleCommMockDevice(key, name, props, comm); + } + + else if (typeName == "appserver") + { + var props = JsonConvert.DeserializeObject(properties.ToString()); + return new CotijaSystemController(key, name, props); + } + + else if (typeName == "mobilecontrolbridge-ddvc01") + { + var comm = CommFactory.GetControlPropertiesConfig(dc); + + var bridge = new PepperDash.Essentials.Room.Cotija.CotijaDdvc01RoomBridge(key, name, comm.IpIdInt); + bridge.AddPreActivationAction(() => + { + var parent = DeviceManager.AllDevices.FirstOrDefault(d => d.Key == "appServer") as CotijaSystemController; + if (parent == null) + { + Debug.Console(0, bridge, "ERROR: Cannot connect bridge. System controller not present"); + } + Debug.Console(0, bridge, "Linking to parent controller"); + bridge.AddParent(parent); + parent.AddBridge(bridge); + }); + + return bridge; + } + + else if (typeName == "roomonwhenoccupancydetectedfeature") + { + return new Room.Behaviours.RoomOnToDefaultSourceWhenOccupied(dc); + } + + return null; + } + } + +} diff --git a/PepperDashEssentials/Factory/UiDeviceFactory.cs b/PepperDashEssentials/Factory/UiDeviceFactory.cs index a4387d1a..5499aa0c 100644 --- a/PepperDashEssentials/Factory/UiDeviceFactory.cs +++ b/PepperDashEssentials/Factory/UiDeviceFactory.cs @@ -49,12 +49,12 @@ namespace PepperDash.Essentials avDriver.CurrentRoom = room as EssentialsHuddleSpaceRoom; // Environment Driver - if (avDriver.CurrentRoom.Config.Environment != null && avDriver.CurrentRoom.Config.Environment.DeviceKeys.Count > 0) + if (avDriver.CurrentRoom.PropertiesConfig.Environment != null && avDriver.CurrentRoom.PropertiesConfig.Environment.DeviceKeys.Count > 0) { Debug.Console(0, panelController, "Adding environment driver"); mainDriver.EnvironmentDriver = new EssentialsEnvironmentDriver(mainDriver, props); - mainDriver.EnvironmentDriver.GetDevicesFromConfig(avDriver.CurrentRoom.Config.Environment); + mainDriver.EnvironmentDriver.GetDevicesFromConfig(avDriver.CurrentRoom.PropertiesConfig.Environment); } mainDriver.HeaderDriver.SetupHeaderButtons(avDriver, avDriver.CurrentRoom); @@ -118,12 +118,12 @@ namespace PepperDash.Essentials avDriver.CurrentRoom = room as EssentialsHuddleVtc1Room; // Environment Driver - if (avDriver.CurrentRoom.Config.Environment != null && avDriver.CurrentRoom.Config.Environment.DeviceKeys.Count > 0) + if (avDriver.CurrentRoom.PropertiesConfig.Environment != null && avDriver.CurrentRoom.PropertiesConfig.Environment.DeviceKeys.Count > 0) { Debug.Console(0, panelController, "Adding environment driver"); mainDriver.EnvironmentDriver = new EssentialsEnvironmentDriver(mainDriver, props); - mainDriver.EnvironmentDriver.GetDevicesFromConfig(avDriver.CurrentRoom.Config.Environment); + mainDriver.EnvironmentDriver.GetDevicesFromConfig(avDriver.CurrentRoom.PropertiesConfig.Environment); } mainDriver.HeaderDriver.SetupHeaderButtons(avDriver, avDriver.CurrentRoom); diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj index 89c751a8..6957f8c5 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -99,20 +99,30 @@ False ..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll + + False + ..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpTimerEventInterface.dll + + + + + - - - - - + + + + + + + @@ -127,43 +137,43 @@ - - - - - - - - + + + + + - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -188,7 +198,7 @@ - + @@ -201,7 +211,7 @@ - + diff --git a/PepperDashEssentials/Properties/AssemblyInfo.cs b/PepperDashEssentials/Properties/AssemblyInfo.cs index 7541bd8a..1f037bfb 100644 --- a/PepperDashEssentials/Properties/AssemblyInfo.cs +++ b/PepperDashEssentials/Properties/AssemblyInfo.cs @@ -3,6 +3,6 @@ [assembly: AssemblyTitle("PepperDashEssentials")] [assembly: AssemblyCompany("PepperDash Technology Corp")] [assembly: AssemblyProduct("PepperDashEssentials")] -[assembly: AssemblyCopyright("Copyright © PepperDash Technology Corp 2017")] -[assembly: AssemblyVersion("1.2.4.*")] +[assembly: AssemblyCopyright("Copyright © PepperDash Technology Corp 2018")] +[assembly: AssemblyVersion("1.3.1.*")] diff --git a/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs new file mode 100644 index 00000000..b12fd2e5 --- /dev/null +++ b/PepperDashEssentials/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs @@ -0,0 +1,526 @@ +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; } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs b/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs index 83303582..16e49933 100644 --- a/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs @@ -20,5 +20,7 @@ namespace PepperDash.Essentials.Room.Config 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/EssentialsRoomConfig.cs b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs index 246bb42b..4ceb481d 100644 --- a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs @@ -1,334 +1,284 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Crestron.SimplSharp; -using Newtonsoft.Json; - -using PepperDash.Core; -using PepperDash.Essentials; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials.Room.Config -{ - public class EssentialsRoomConfig : DeviceConfig - { - /// - /// Returns a room object from this config data - /// - /// - public Device GetRoomObject() - { - var typeName = Type.ToLower(); - if (typeName == "huddle") - { - var props = JsonConvert.DeserializeObject - (this.Properties.ToString()); - var disp = DeviceManager.GetDeviceForKey(props.DefaultDisplayKey) as IRoutingSinkWithSwitching; - var audio = DeviceManager.GetDeviceForKey(props.DefaultAudioKey) as IRoutingSinkNoSwitching; - var huddle = new EssentialsHuddleSpaceRoom(Key, Name, disp, audio, props); - - if (props.Occupancy != null) - huddle.SetRoomOccupancy(DeviceManager.GetDeviceForKey(props.Occupancy.DeviceKey) as - PepperDash.Essentials.Devices.Common.Occupancy.IOccupancyStatusProvider, props.Occupancy.TimoutMinutes); - huddle.LogoUrl = props.Logo.GetUrl(); - huddle.SourceListKey = props.SourceListKey; - huddle.DefaultSourceItem = props.DefaultSourceItem; - huddle.DefaultVolume = (ushort)(props.Volumes.Master.Level * 65535 / 100); - return huddle; - } - else if (typeName == "presentation") - { - var props = JsonConvert.DeserializeObject - (this.Properties.ToString()); - var displaysDict = new Dictionary(); - uint i = 1; - foreach (var dispKey in props.DisplayKeys) // read in the ordered displays list - { - var disp = DeviceManager.GetDeviceForKey(dispKey) as IRoutingSinkWithSwitching; - displaysDict.Add(i++, disp); - } - - // Get the master volume control - IBasicVolumeWithFeedback masterVolumeControlDev = props.Volumes.Master.GetDevice(); - - - var presRoom = new EssentialsPresentationRoom(Key, Name, displaysDict, masterVolumeControlDev, props); - return presRoom; - } - else if (typeName == "huddlevtc1") - { - var props = JsonConvert.DeserializeObject - (this.Properties.ToString()); - var disp = DeviceManager.GetDeviceForKey(props.DefaultDisplayKey) as IRoutingSinkWithSwitching; - - var codec = DeviceManager.GetDeviceForKey(props.VideoCodecKey) as - PepperDash.Essentials.Devices.Common.VideoCodec.VideoCodecBase; - - var rm = new EssentialsHuddleVtc1Room(Key, Name, disp, codec, codec, props); - // Add Occupancy object from config - - if (props.Occupancy != null) - rm.SetRoomOccupancy(DeviceManager.GetDeviceForKey(props.Occupancy.DeviceKey) as - PepperDash.Essentials.Devices.Common.Occupancy.IOccupancyStatusProvider, props.Occupancy.TimoutMinutes); - rm.LogoUrl = props.Logo.GetUrl(); - rm.SourceListKey = props.SourceListKey; - rm.DefaultSourceItem = props.DefaultSourceItem; - rm.DefaultVolume = (ushort)(props.Volumes.Master.Level * 65535 / 100); - - rm.MicrophonePrivacy = GetMicrophonePrivacy(props, rm); // Get Microphone Privacy object, if any - - rm.Emergency = GetEmergency(props, rm); // Get emergency object, if any - - return rm; - } - else if (typeName == "ddvc01Bridge") - { - return new Device(Key, Name); // placeholder device that does nothing. - } - - return null; - } - - /// - /// Gets and operating, standalone emergegncy object that can be plugged into a room. - /// Returns null if there is no emergency defined - /// - EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room) - { - // This emergency - var emergency = props.Emergency; - if (emergency != null) - { - //switch on emergency type here. Right now only contact and shutdown - var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); - DeviceManager.AddDevice(e); - } - return null; - } - - /// - /// - /// - /// - /// - /// - PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController GetMicrophonePrivacy( - EssentialsRoomPropertiesConfig props, EssentialsHuddleVtc1Room room) - { - var microphonePrivacy = props.MicrophonePrivacy; - if (microphonePrivacy == null) - { - Debug.Console(0, "ERROR: Cannot create microphone privacy with null properties"); - return null; - } - // Get the MicrophonePrivacy device from the device manager - var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as - PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController); - // Set this room as the IPrivacy device - if (mP == null) - { - Debug.Console(0, "ERROR: Selected device {0} is not MicrophonePrivacyController", props.MicrophonePrivacy.DeviceKey); - return null; - } - mP.SetPrivacyDevice(room); - - var behaviour = props.MicrophonePrivacy.Behaviour.ToLower(); - - if (behaviour == null) - { - Debug.Console(0, "WARNING: No behaviour defined for MicrophonePrivacyController"); - return null; - } - if (behaviour == "trackroomstate") - { - // Tie LED enable to room power state - room.OnFeedback.OutputChange += (o, a) => - { - if (room.OnFeedback.BoolValue) - mP.EnableLeds = true; - else - mP.EnableLeds = false; - }; - - mP.EnableLeds = room.OnFeedback.BoolValue; - } - else if (behaviour == "trackcallstate") - { - // Tie LED enable to room power state - room.InCallFeedback.OutputChange += (o, a) => - { - if (room.InCallFeedback.BoolValue) - mP.EnableLeds = true; - else - mP.EnableLeds = false; - }; - - mP.EnableLeds = room.InCallFeedback.BoolValue; - } - - return mP; - } - - } - - /// - /// - /// - public class EssentialsRoomPropertiesConfig - { - [JsonProperty("addresses")] - public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("emergency")] - public EssentialsRoomEmergencyConfig Emergency { get; set; } - - [JsonProperty("help")] - public EssentialsHelpPropertiesConfig Help { get; set; } - - [JsonProperty("helpMessage")] - public string HelpMessage { get; set; } - - [JsonProperty("environment")] - public EssentialsEnvironmentPropertiesConfig Environment { get; set; } - - [JsonProperty("logo")] - public EssentialsLogoPropertiesConfig Logo { get; set; } - - [JsonProperty("microphonePrivacy")] - public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; } - - [JsonProperty("occupancy")] - public EssentialsRoomOccSensorConfig Occupancy { get; set; } - - [JsonProperty("oneButtonMeeting")] - public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } - - [JsonProperty("shutdownVacancySeconds")] - public int ShutdownVacancySeconds { get; set; } - - [JsonProperty("shutdownPromptSeconds")] - public int ShutdownPromptSeconds { get; set; } - - [JsonProperty("tech")] - public EssentialsRoomTechConfig Tech { get; set; } - - [JsonProperty("volumes")] - public EssentialsRoomVolumesConfig Volumes { get; set; } - - [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")] - public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } - } - - public class EssentialsEnvironmentPropertiesConfig - { - public bool Enabled { get; set; } - - [JsonProperty("deviceKeys")] - public List DeviceKeys { get; set; } - - public EssentialsEnvironmentPropertiesConfig() - { - DeviceKeys = new List(); - } - - } - - public class EssentialsRoomMicrophonePrivacyConfig - { - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } - - [JsonProperty("behaviour")] - public string Behaviour { get; set; } - } - - /// - /// Properties for the help text box - /// - public class EssentialsHelpPropertiesConfig - { - [JsonProperty("message")] - public string Message { get; set; } - - [JsonProperty("showCallButton")] - public bool ShowCallButton { get; set; } - - /// - /// Defaults to "Call Help Desk" - /// - [JsonProperty("callButtonText")] - public string CallButtonText { get; set; } - - public EssentialsHelpPropertiesConfig() - { - CallButtonText = "Call Help Desk"; - } - } - - /// - /// - /// - public class EssentialsOneButtonMeetingPropertiesConfig - { - [JsonProperty("enable")] - public bool Enable { get; set; } - } - - public class EssentialsRoomAddressPropertiesConfig - { - [JsonProperty("phoneNumber")] - public string PhoneNumber { get; set; } - - [JsonProperty("sipAddress")] - public string SipAddress { get; set; } - } - - - /// - /// Properties for the room's logo on panels - /// - public class EssentialsLogoPropertiesConfig - { - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - /// - /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo - /// - public string GetUrl() - { - if (Type == "url") - return Url; - if (Type == "system") - return string.Format("http://{0}:8080/logo.png", - CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); - return null; - } - } - - /// - /// Represents occupancy sensor(s) setup for a room - /// - public class EssentialsRoomOccSensorConfig - { - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } - - [JsonProperty("timoutMinutes")] - public int TimoutMinutes { get; set; } - } - - public class EssentialsRoomTechConfig - { - [JsonProperty("password")] - public string Password { get; set; } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Crestron.SimplSharp; +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Room.Config +{ + public class EssentialsRoomConfigHelper + { + /// + /// Returns a room object from this config data + /// + /// + public static Device GetRoomObject(DeviceConfig roomConfig) + { + var typeName = roomConfig.Type.ToLower(); + if (typeName == "huddle") + { + var huddle = new EssentialsHuddleSpaceRoom(roomConfig); + + return huddle; + } + else if (typeName == "huddlevtc1") + { + var rm = new EssentialsHuddleVtc1Room(roomConfig); + + return rm; + } + else if (typeName == "ddvc01Bridge") + { + return new Device(roomConfig.Key, roomConfig.Name); // placeholder device that does nothing. + } + + return null; + } + + /// + /// Gets and operating, standalone emergegncy object that can be plugged into a room. + /// Returns null if there is no emergency defined + /// + public static EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room) + { + // This emergency + var emergency = props.Emergency; + if (emergency != null) + { + //switch on emergency type here. Right now only contact and shutdown + var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); + DeviceManager.AddDevice(e); + } + return null; + } + + /// + /// + /// + /// + /// + /// + public static PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController GetMicrophonePrivacy( + EssentialsRoomPropertiesConfig props, EssentialsHuddleVtc1Room room) + { + var microphonePrivacy = props.MicrophonePrivacy; + if (microphonePrivacy == null) + { + Debug.Console(0, "ERROR: Cannot create microphone privacy with null properties"); + return null; + } + // Get the MicrophonePrivacy device from the device manager + var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as + PepperDash.Essentials.Devices.Common.Microphones.MicrophonePrivacyController); + // Set this room as the IPrivacy device + if (mP == null) + { + Debug.Console(0, "ERROR: Selected device {0} is not MicrophonePrivacyController", props.MicrophonePrivacy.DeviceKey); + return null; + } + mP.SetPrivacyDevice(room); + + var behaviour = props.MicrophonePrivacy.Behaviour.ToLower(); + + if (behaviour == null) + { + Debug.Console(0, "WARNING: No behaviour defined for MicrophonePrivacyController"); + return null; + } + if (behaviour == "trackroomstate") + { + // Tie LED enable to room power state + room.OnFeedback.OutputChange += (o, a) => + { + if (room.OnFeedback.BoolValue) + mP.EnableLeds = true; + else + mP.EnableLeds = false; + }; + + mP.EnableLeds = room.OnFeedback.BoolValue; + } + else if (behaviour == "trackcallstate") + { + // Tie LED enable to room power state + room.InCallFeedback.OutputChange += (o, a) => + { + if (room.InCallFeedback.BoolValue) + mP.EnableLeds = true; + else + mP.EnableLeds = false; + }; + + mP.EnableLeds = room.InCallFeedback.BoolValue; + } + + return mP; + } + + } + + /// + /// + /// + public class EssentialsRoomPropertiesConfig + { + [JsonProperty("addresses")] + public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("emergency")] + public EssentialsRoomEmergencyConfig Emergency { get; set; } + + [JsonProperty("help")] + public EssentialsHelpPropertiesConfig Help { get; set; } + + [JsonProperty("helpMessage")] + public string HelpMessage { get; set; } + + [JsonProperty("environment")] + public EssentialsEnvironmentPropertiesConfig Environment { get; set; } + + [JsonProperty("logo")] + public EssentialsLogoPropertiesConfig Logo { get; set; } + + [JsonProperty("microphonePrivacy")] + public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; } + + [JsonProperty("occupancy")] + public EssentialsRoomOccSensorConfig Occupancy { get; set; } + + [JsonProperty("oneButtonMeeting")] + public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } + + [JsonProperty("shutdownVacancySeconds")] + public int ShutdownVacancySeconds { get; set; } + + [JsonProperty("shutdownPromptSeconds")] + public int ShutdownPromptSeconds { get; set; } + + [JsonProperty("tech")] + public EssentialsRoomTechConfig Tech { get; set; } + + [JsonProperty("volumes")] + public EssentialsRoomVolumesConfig Volumes { get; set; } + + [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")] + public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } + } + + public class EssentialsEnvironmentPropertiesConfig + { + public bool Enabled { get; set; } + + [JsonProperty("deviceKeys")] + public List DeviceKeys { get; set; } + + public EssentialsEnvironmentPropertiesConfig() + { + DeviceKeys = new List(); + } + + } + + public class EssentialsRoomMicrophonePrivacyConfig + { + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + [JsonProperty("behaviour")] + public string Behaviour { get; set; } + } + + /// + /// Properties for the help text box + /// + public class EssentialsHelpPropertiesConfig + { + [JsonProperty("message")] + public string Message { get; set; } + + [JsonProperty("showCallButton")] + public bool ShowCallButton { get; set; } + + /// + /// Defaults to "Call Help Desk" + /// + [JsonProperty("callButtonText")] + public string CallButtonText { get; set; } + + public EssentialsHelpPropertiesConfig() + { + CallButtonText = "Call Help Desk"; + } + } + + /// + /// + /// + public class EssentialsOneButtonMeetingPropertiesConfig + { + [JsonProperty("enable")] + public bool Enable { get; set; } + } + + public class EssentialsRoomAddressPropertiesConfig + { + [JsonProperty("phoneNumber")] + public string PhoneNumber { get; set; } + + [JsonProperty("sipAddress")] + public string SipAddress { get; set; } + } + + + /// + /// Properties for the room's logo on panels + /// + public class EssentialsLogoPropertiesConfig + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + /// + /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo + /// + public string GetUrl() + { + if (Type == "url") + return Url; + if (Type == "system") + return string.Format("http://{0}:8080/logo.png", + CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); + return null; + } + } + + /// + /// Represents occupancy sensor(s) setup for a room + /// + public class EssentialsRoomOccSensorConfig + { + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + [JsonProperty("timeoutMinutes")] + public int TimeoutMinutes { get; set; } + } + + public class EssentialsRoomTechConfig + { + [JsonProperty("password")] + public string Password { get; set; } + } } \ No newline at end of file diff --git a/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs.orig b/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs.orig deleted file mode 100644 index 0dc8b651..00000000 --- a/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs.orig +++ /dev/null @@ -1,694 +0,0 @@ -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.Room.Cotija; - -namespace PepperDash.Essentials -{ - public class CotijaSystemController : Device - { - WebSocketClient WSClient; - - /// - /// 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(); - - CTimer ServerHeartbeatCheckTimer; - - long ServerHeartbeatInterval = 20000; - - CTimer ServerReconnectTimer; - - long ServerReconnectInterval = 5000; - - 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; - 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); - - } - - /// - /// 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"); - RegisterSystemToServer(); - } - } - - /// - /// - /// - /// - /// - void bridge_ConfigurationIsReady(object sender, EventArgs e) - { - Debug.Console(1, this, "Bridge ready. Registering"); - // send the configuration object to the server - RegisterSystemToServer(); - } - - /// - /// - /// - /// - void ReconnectToServerTimerCallback(object o) - { - RegisterSystemToServer(); - } - - /// - /// Verifies system connection with servers - /// - /// - void AuthorizeSystem(string code) - { - 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."); - 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"); - - CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: - Server address: {0} - System Name: {1} - System UUID: {2} - System User code: {3} - Connected?: {4}", url, name, SystemUuid, - code, conn); - } - - /// - /// Registers the room with the server - /// - /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" - void RegisterSystemToServer() - { - var ready = RegisterLockEvent.Wait(20000); - if (!ready) - { - Debug.Console(1, this, "RegisterSystemToServer failed to enter after 20 seconds. Ignoring"); - return; - } - RegisterLockEvent.Reset(); - - try - { - 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); - confObject.Info.RuntimeInfo.OsVersion = Crestron.SimplSharp.CrestronEnvironment.OSVersion.Firmware; - - string postBody = JsonConvert.SerializeObject(confObject); - SystemUuid = confObject.SystemUuid; - - if (string.IsNullOrEmpty(postBody)) - { - Debug.Console(1, this, "ERROR: Config body is empty. Cannot register with server."); - } - else - { - var regClient = new HttpClient(); - regClient.Verbose = true; - regClient.KeepAlive = true; - - string url = string.Format("http://{0}/api/system/join/{1}", Config.ServerUrl, SystemUuid); - Debug.Console(1, this, "Joining server at {0}", url); - - HttpClientRequest request = new HttpClientRequest(); - request.Url.Parse(url); - request.RequestType = RequestType.Post; - request.Header.SetHeaderValue("Content-Type", "application/json"); - request.ContentString = postBody; - - var err = regClient.DispatchAsync(request, RegistrationConnectionCallback); - } - - } - catch (Exception e) - { - Debug.Console(0, this, "ERROR: Initilizing Room: {0}", e); - RegisterLockEvent.Set(); - StartReconnectTimer(); - } - - } - - /// - /// 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 }); - Debug.Console(1, this, "Message TX: {0}", message); - var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); - WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - //WSClient.SendAsync(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - } -<<<<<<< HEAD - -======= - ->>>>>>> feature/ecs-684 - } - - /// - /// Disconnects the SSE Client and stops the heartbeat timer - /// - /// - void DisconnectStreamClient(string command) - { - //if(SseClient != null) - // SseClient.Disconnect(); - - if (WSClient != null && WSClient.Connected) - WSClient.Disconnect(); - - if (ServerHeartbeatCheckTimer != null) - { - ServerHeartbeatCheckTimer.Stop(); - - ServerHeartbeatCheckTimer = null; - } - } - - /// - /// The callback that fires when we get a response from our registration attempt - /// - /// - /// - void RegistrationConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err) - { - CheckHttpDebug(resp, err); - Debug.Console(1, this, "RegistrationConnectionCallback: {0}", err); - try - { - if (resp != null && resp.Code == 200) - { - if(ServerReconnectTimer != null) - { - ServerReconnectTimer.Stop(); - ServerReconnectTimer = null; - } - - // Success here! - ConnectStreamClient(); - } - else - { - if (resp != null) - Debug.Console(1, this, "Response from server: {0}\n{1}", resp.Code, err); - else - { - Debug.Console(1, this, "Null response received from server."); - } - StartReconnectTimer(); - } - } - catch (Exception e) - { - Debug.Console(1, this, "Error Initializing Stream Client: {0}", e); - StartReconnectTimer(); - } - RegisterLockEvent.Set(); - } - - /// - /// 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; - } - StartReconnectTimer(); - } - - /// - /// - /// - /// - /// - void StartReconnectTimer() - { - // Start the reconnect timer - if (ServerReconnectTimer == null) - { - ServerReconnectTimer = new CTimer(ReconnectToServerTimerCallback, null, ServerReconnectInterval, ServerReconnectInterval); - Debug.Console(1, this, "Reconnect Timer Started."); - } - ServerReconnectTimer.Reset(ServerReconnectInterval, ServerReconnectInterval); - } - - /// - /// - /// - /// - /// - void ResetOrStartHearbeatTimer() - { - if (ServerHeartbeatCheckTimer == null) - { - ServerHeartbeatCheckTimer = new CTimer(HeartbeatExpiredTimerCallback, null, ServerHeartbeatInterval, ServerHeartbeatInterval); - - Debug.Console(1, this, "Heartbeat Timer Started."); - } - - ServerHeartbeatCheckTimer.Reset(ServerHeartbeatInterval, ServerHeartbeatInterval); - } - - - /// - /// Connects the SSE Client - /// - /// - void ConnectStreamClient() - { - Debug.Console(0, this, "Initializing Stream client to server."); - - if (WSClient == null) - { - WSClient = new WebSocketClient(); - } - WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); - WSClient.Connect(); - Debug.Console(0, this, "Websocket connected"); - WSClient.ReceiveCallBack = WebsocketReceiveCallback; - //WSClient.SendCallBack = WebsocketSendCallback; - WSClient.ReceiveAsync(); - } - - /// - /// Resets reconnect timer and updates usercode - /// - /// - void HandleHeartBeat(JToken content) - { - 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) - { - Debug.Console(0, this, "------ Begin HTTP Debug ---------------------------------------"); - Debug.Console(0, this, "HTTP Response URL: {0}", r.ResponseUrl.ToString()); - Debug.Console(0, this, "HTTP Response 'error' {0}", e); - Debug.Console(0, this, "HTTP Response code: {0}", r.Code); - Debug.Console(0, this, "HTTP Response content: \r{0}", r.ContentString); - Debug.Console(0, this, "------ End HTTP Debug -----------------------------------------"); - } - } - - /// - /// - /// - /// - /// - /// - /// - int WebsocketReceiveCallback(byte[] data, uint length, WebSocketClient.WEBSOCKET_PACKET_TYPES opcode, - WebSocketClient.WEBSOCKET_RESULT_CODES err) - { - var rx = System.Text.Encoding.UTF8.GetString(data, 0, (int)length); - if(rx.Length > 0) - ParseStreamRx(rx); - WSClient.ReceiveAsync(); - return 1; - } - - /// - /// Callback to catch possible errors in sending via the websocket - /// - /// - /// - int WebsocketSendCallback(Crestron.SimplSharp.CrestronWebSocketClient.WebSocketClient.WEBSOCKET_RESULT_CODES result) - { - Debug.Console(1, this, "SendCallback result: {0}", result); - - return 1; - } - - /// - /// - /// - /// - /// - void ParseStreamRx(string message) - { - if(string.IsNullOrEmpty(message)) - return; - - Debug.Console(1, this, "Message RX: '{0}'", message); - try - { - var messageObj = JObject.Parse(message); - - var type = messageObj["type"].Value(); - - if (type == "hello") - { - ResetOrStartHearbeatTimer(); - } - else if (type == "/system/heartbeat") - { - HandleHeartBeat(messageObj["content"]); - } - else if (type == "close") - { - WSClient.Disconnect(); - - ServerHeartbeatCheckTimer.Stop(); - // Start the reconnect timer - StartReconnectTimer(); - } - 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/Room/Cotija/Interfaces.cs b/PepperDashEssentials/Room/Cotija/Interfaces.cs deleted file mode 100644 index 1aab82fb..00000000 --- a/PepperDashEssentials/Room/Cotija/Interfaces.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Essentials.Room.Cotija -{ - public interface IDelayedConfiguration - { - event EventHandler ConfigurationIsReady; - } -} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs index 5493fd9d..c1db1bee 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs @@ -1,462 +1,505 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Room.Config; - -namespace PepperDash.Essentials -{ - public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IHasCurrentSourceInfoChange - { - public event EventHandler CurrentVolumeDeviceChange; - public event SourceInfoChangeHandler CurrentSingleSourceChange; - - protected override Func OnFeedbackFunc - { - get - { - return () => - { - var disp = DefaultDisplay as DisplayBase; - var val = CurrentSourceInfo != null - && CurrentSourceInfo.Type == eSourceListItemType.Route - && disp != null; - //&& disp.PowerIsOnFeedback.BoolValue; - return val; - }; - } - } - /// - /// - /// - protected override Func IsWarmingFeedbackFunc - { - get - { - return () => - { - var disp = DefaultDisplay as DisplayBase; - if (disp != null) - return disp.IsWarmingUpFeedback.BoolValue; - else - return false; - }; - } - } - /// - /// - /// - protected override Func IsCoolingFeedbackFunc - { - get - { - return () => - { - var disp = DefaultDisplay as DisplayBase; - if (disp != null) - return disp.IsCoolingDownFeedback.BoolValue; - else - return false; - }; - } - } - - public EssentialsRoomPropertiesConfig Config { get; private set; } - - public IRoutingSinkWithSwitching DefaultDisplay { get; private set; } - public IRoutingSinkNoSwitching DefaultAudioDevice { get; private set; } - public IBasicVolumeControls DefaultVolumeControls { 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; } - - public ushort DefaultVolume { get; set; } - - /// - /// If room is off, enables power on to last source. Default true - /// - public bool EnablePowerOnToLastSource { get; set; } - string LastSourceKey; - - /// - /// - /// - 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; - - /// - /// The SourceListItem last run - containing names and icons - /// - public SourceListItem CurrentSourceInfo - { - get { return _CurrentSourceInfo; } - private set - { - if (value == _CurrentSourceInfo) return; - - var handler = CurrentSingleSourceChange; - // 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); - - _CurrentSourceInfo = value; - - // add to in-use tracking - if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) - (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); - if (handler != null) - handler(this, _CurrentSourceInfo, ChangeType.DidChange); - } - } - SourceListItem _CurrentSourceInfo; - - public string CurrentSourceInfoKey { get; private set; } - - /// - /// - /// - /// - /// - public EssentialsHuddleSpaceRoom(string key, string name, IRoutingSinkWithSwitching defaultDisplay, - IRoutingSinkNoSwitching defaultAudio, EssentialsRoomPropertiesConfig config) - : base(key, name) - { - Config = config; - DefaultDisplay = defaultDisplay; - DefaultAudioDevice = defaultAudio; - if (defaultAudio is IBasicVolumeControls) - DefaultVolumeControls = defaultAudio as IBasicVolumeControls; - else if (defaultAudio is IHasVolumeDevice) - DefaultVolumeControls = (defaultAudio as IHasVolumeDevice).VolumeDevice; - CurrentVolumeControls = DefaultVolumeControls; - - var disp = DefaultDisplay as DisplayBase; - if (disp != null) - { - // Link power, warming, cooling to display - disp.PowerIsOnFeedback.OutputChange += (o, a) => - { - if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) - { - if (!disp.PowerIsOnFeedback.BoolValue) - CurrentSourceInfo = null; - OnFeedback.FireUpdate(); - } - }; - - disp.IsWarmingUpFeedback.OutputChange += (o, a) => - { - IsWarmingUpFeedback.FireUpdate(); - if (!IsWarmingUpFeedback.BoolValue) - (DefaultDisplay as IBasicVolumeWithFeedback).SetVolume(DefaultVolume); - }; - disp.IsCoolingDownFeedback.OutputChange += (o, a) => - { - IsCoolingDownFeedback.FireUpdate(); - }; - } - - SourceListKey = "default"; - EnablePowerOnToLastSource = true; - } - - - /// - /// - /// - protected override void EndShutdown() - { - SetDefaultLevels(); - - RunDefaultRoute(); - - CrestronEnvironment.Sleep(1000); - - RunRouteAction("roomOff"); - } - - /// - /// Routes the default source item, if any - /// - public void RunDefaultRoute() - { - //if (DefaultSourceItem != null && !OnFeedback.BoolValue) - RunRouteAction(DefaultSourceItem); - } - - /// - /// - /// - /// - 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 => - { - 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; - } - - var item = dict[routeKey]; - //Debug.Console(2, this, "Action {0} has {1} steps", - // item.SourceKey, item.RouteList.Count); - - // End usage timer on last source - if (!string.IsNullOrEmpty(LastSourceKey)) - { - var lastSource = dict[LastSourceKey].SourceDevice; - - try - { - if (lastSource != null && lastSource is IUsageTracking) - (lastSource as IUsageTracking).UsageTracker.EndDeviceUsage(); - } - catch (Exception e) - { - Debug.Console(1, this, "*#* EXCEPTION in end usage tracking (257):\r{0}", e); - } - } - - // Let's run it - if (routeKey.ToLower() != "roomoff") - { - LastSourceKey = routeKey; - } - else - { - CurrentSourceInfoKey = null; - } - - foreach (var route in item.RouteList) - { - // 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); - - //var tempAudio = new SourceRouteListItem - //{ - // DestinationKey = "$defaultAudio", - // SourceKey = route.SourceKey, - // Type = eRoutingSignalType.Audio - //}; - //DoRoute(tempAudio); - //continue; -- not sure why this was here - } - else - DoRoute(route); - } - - // Start usage timer on routed source - if (item.SourceDevice is IUsageTracking) - { - (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage(); - } - - - - - // 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; - else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) - volDev = DefaultDisplay as IBasicVolumeControls; - // 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") - { - CurrentSourceInfoKey = routeKey; - CurrentSourceInfo = null; - } - else if (item.SourceKey != null) - { - CurrentSourceInfoKey = routeKey; - CurrentSourceInfo = item; - } - // And finally, set the "control". This will trigger event - //CurrentControlDevice = DeviceManager.GetDeviceForKey(item.SourceKey) as Device; - - OnFeedback.FireUpdate(); - - // report back when done - if (successCallback != null) - successCallback(); - - }, 0); // end of CTimer - } - - /// - /// Will power the room on with the last-used source - /// - public void PowerOnToDefaultOrLastSource() - { - if (!EnablePowerOnToLastSource || LastSourceKey == null) - return; - RunRouteAction(LastSourceKey); - } - - /// - /// 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); - } - - /// - /// - /// - /// - /// - bool DoRoute(SourceRouteListItem route) - { - IRoutingSinkNoSwitching dest = null; - - if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) - dest = DefaultAudioDevice; - else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) - dest = DefaultDisplay; - 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); - } - return true; - } - - public override void RoomVacatedForTimeoutPeriod(object o) - { - //Implement this - } - - /// - /// 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"); - } - } +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.Config; +using PepperDash.Essentials.Room.Config; + +namespace PepperDash.Essentials +{ + public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IHasCurrentSourceInfoChange, IRunRouteAction, IRunDefaultPresentRoute + { + public event EventHandler CurrentVolumeDeviceChange; + public event SourceInfoChangeHandler CurrentSingleSourceChange; + + protected override Func OnFeedbackFunc + { + get + { + return () => + { + var disp = DefaultDisplay as DisplayBase; + var val = CurrentSourceInfo != null + && CurrentSourceInfo.Type == eSourceListItemType.Route + && disp != null; + //&& disp.PowerIsOnFeedback.BoolValue; + return val; + }; + } + } + /// + /// + /// + protected override Func IsWarmingFeedbackFunc + { + get + { + return () => + { + var disp = DefaultDisplay as DisplayBase; + if (disp != null) + return disp.IsWarmingUpFeedback.BoolValue; + else + return false; + }; + } + } + /// + /// + /// + protected override Func IsCoolingFeedbackFunc + { + get + { + return () => + { + var disp = DefaultDisplay as DisplayBase; + if (disp != null) + return disp.IsCoolingDownFeedback.BoolValue; + else + return false; + }; + } + } + + public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; } + + public IRoutingSinkWithSwitching DefaultDisplay { get; private set; } + public IRoutingSinkNoSwitching DefaultAudioDevice { get; private set; } + public IBasicVolumeControls DefaultVolumeControls { 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; } + + public ushort DefaultVolume { get; set; } + + /// + /// If room is off, enables power on to last source. Default true + /// + public bool EnablePowerOnToLastSource { get; set; } + string LastSourceKey; + + /// + /// + /// + 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; + + /// + /// The SourceListItem last run - containing names and icons + /// + public SourceListItem CurrentSourceInfo + { + get { return _CurrentSourceInfo; } + private set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSingleSourceChange; + // 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); + + _CurrentSourceInfo = value; + + // add to in-use tracking + if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) + (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); + if (handler != null) + handler(this, _CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + + public string CurrentSourceInfoKey { get; private set; } + + public EssentialsHuddleSpaceRoom(DeviceConfig config) + : base(config) + { + try + { + PropertiesConfig = JsonConvert.DeserializeObject + (config.Properties.ToString()); + DefaultDisplay = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching; + + + DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IRoutingSinkWithSwitching; + + 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 disp = DefaultDisplay as DisplayBase; + if (disp != null) + { + // Link power, warming, cooling to display + disp.PowerIsOnFeedback.OutputChange += (o, a) => + { + if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) + { + if (!disp.PowerIsOnFeedback.BoolValue) + CurrentSourceInfo = null; + OnFeedback.FireUpdate(); + } + }; + + disp.IsWarmingUpFeedback.OutputChange += (o, a) => + { + IsWarmingUpFeedback.FireUpdate(); + if (!IsWarmingUpFeedback.BoolValue) + (DefaultDisplay as IBasicVolumeWithFeedback).SetVolume(DefaultVolume); + }; + disp.IsCoolingDownFeedback.OutputChange += (o, a) => + { + IsCoolingDownFeedback.FireUpdate(); + }; + } + + SourceListKey = "default"; + EnablePowerOnToLastSource = true; + } + + protected override void CustomSetConfig(DeviceConfig config) + { + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + if (newPropertiesConfig != null) + PropertiesConfig = newPropertiesConfig; + + ConfigWriter.UpdateRoomConfig(config); + } + + /// + /// + /// + protected override void EndShutdown() + { + SetDefaultLevels(); + + RunDefaultPresentRoute(); + + CrestronEnvironment.Sleep(1000); + + RunRouteAction("roomOff"); + } + + /// + /// Routes the default source item, if any + /// + public override bool RunDefaultPresentRoute() + { + if (DefaultSourceItem == null) + { + Debug.Console(0, this, "Unable to run default present route, DefaultSourceItem is null."); + return false; + } + + RunRouteAction(DefaultSourceItem); + return true; + } + + public override bool CustomActivate() + { + // 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); + + 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(); + } + + /// + /// + /// + /// + 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 => + { + 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; + } + + var item = dict[routeKey]; + //Debug.Console(2, this, "Action {0} has {1} steps", + // item.SourceKey, item.RouteList.Count); + + // End usage timer on last source + if (!string.IsNullOrEmpty(LastSourceKey)) + { + var lastSource = dict[LastSourceKey].SourceDevice; + + try + { + if (lastSource != null && lastSource is IUsageTracking) + (lastSource as IUsageTracking).UsageTracker.EndDeviceUsage(); + } + catch (Exception e) + { + Debug.Console(1, this, "*#* EXCEPTION in end usage tracking (257):\r{0}", e); + } + } + + // Let's run it + if (routeKey.ToLower() != "roomoff") + { + LastSourceKey = routeKey; + } + else + { + CurrentSourceInfoKey = null; + } + + foreach (var route in item.RouteList) + { + // 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); + + //var tempAudio = new SourceRouteListItem + //{ + // DestinationKey = "$defaultAudio", + // SourceKey = route.SourceKey, + // Type = eRoutingSignalType.Audio + //}; + //DoRoute(tempAudio); + //continue; -- not sure why this was here + } + else + DoRoute(route); + } + + // Start usage timer on routed source + if (item.SourceDevice is IUsageTracking) + { + (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage(); + } + + + + + // 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; + else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) + volDev = DefaultDisplay as IBasicVolumeControls; + // 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") + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = null; + } + else if (item.SourceKey != null) + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = item; + } + // And finally, set the "control". This will trigger event + //CurrentControlDevice = DeviceManager.GetDeviceForKey(item.SourceKey) as Device; + + OnFeedback.FireUpdate(); + + // report back when done + if (successCallback != null) + successCallback(); + + }, 0); // end of CTimer + } + + /// + /// Will power the room on with the last-used source + /// + public override void PowerOnToDefaultOrLastSource() + { + if (!EnablePowerOnToLastSource || LastSourceKey == null) + return; + RunRouteAction(LastSourceKey); + } + + /// + /// 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); + } + + /// + /// + /// + /// + /// + bool DoRoute(SourceRouteListItem route) + { + IRoutingSinkNoSwitching dest = null; + + if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) + dest = DefaultAudioDevice; + else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) + dest = DefaultDisplay; + 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); + } + return true; + } + + public override void RoomVacatedForTimeoutPeriod(object o) + { + //Implement this + } + + /// + /// 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"); + } + } } \ No newline at end of file diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs index 60027142..72b0d7f7 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs @@ -1,572 +1,619 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Room.Config; -using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials -{ - public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange, IPrivacy, IHasCurrentVolumeControls - { - public event EventHandler CurrentVolumeDeviceChange; - public event SourceInfoChangeHandler CurrentSingleSourceChange; - - - //************************ - // Call-related stuff - - public BoolFeedback InCallFeedback { get; private set; } - - /// - /// Make this more specific - /// - public List ActiveCalls { 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; } - - //************************ - - - protected override Func OnFeedbackFunc - { - get - { - return () => - { - var disp = DefaultDisplay as DisplayBase; - var val = CurrentSourceInfo != null - && CurrentSourceInfo.Type == eSourceListItemType.Route - && disp != null; - //&& disp.PowerIsOnFeedback.BoolValue; - return val; - }; - } - } - /// - /// - /// - protected override Func IsWarmingFeedbackFunc - { - get - { - return () => - { - var disp = DefaultDisplay as DisplayBase; - if (disp != null) - return disp.IsWarmingUpFeedback.BoolValue; - else - return false; - }; - } - } - /// - /// - /// - protected override Func IsCoolingFeedbackFunc - { - get - { - return () => - { - var disp = DefaultDisplay as DisplayBase; - if (disp != null) - return disp.IsCoolingDownFeedback.BoolValue; - else - return false; - }; - } - } - - public EssentialsHuddleVtc1PropertiesConfig Config { get; private set; } - - public IRoutingSinkWithSwitching DefaultDisplay { get; private set; } - public IBasicVolumeControls DefaultAudioDevice { get; private set; } - public IBasicVolumeControls DefaultVolumeControls { get; private set; } - - public VideoCodecBase VideoCodec { 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; } - - 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; - - /// - /// The SourceListItem last run - containing names and icons - /// - public SourceListItem CurrentSourceInfo - { - get { return _CurrentSourceInfo; } - private set - { - if (value == _CurrentSourceInfo) return; - - var handler = CurrentSingleSourceChange; - // 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); - - _CurrentSourceInfo = value; - - // add to in-use tracking - if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) - (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); - if (handler != null) - handler(this, _CurrentSourceInfo, ChangeType.DidChange); - } - } - SourceListItem _CurrentSourceInfo; - - public string CurrentSourceInfoKey { get; private set; } - - /// - /// "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 EssentialsHuddleVtc1Room(string key, string name, IRoutingSinkWithSwitching defaultDisplay, - IBasicVolumeControls defaultAudio, VideoCodecBase codec, EssentialsHuddleVtc1PropertiesConfig config) - : base(key, name) - { - if (codec == null) - throw new ArgumentNullException("codec cannot be null"); - Config = config; - DefaultDisplay = defaultDisplay; - VideoCodec = codec; - DefaultAudioDevice = defaultAudio; - - if (defaultAudio is IBasicVolumeControls) - DefaultVolumeControls = defaultAudio as IBasicVolumeControls; - else if (defaultAudio is IHasVolumeDevice) - DefaultVolumeControls = (defaultAudio as IHasVolumeDevice).VolumeDevice; - CurrentVolumeControls = DefaultVolumeControls; - - - var disp = DefaultDisplay as DisplayBase; - if (disp != null) - { - // Link power, warming, cooling to display - disp.PowerIsOnFeedback.OutputChange += (o, a) => - { - if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) - { - if (!disp.PowerIsOnFeedback.BoolValue) - 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(); - }; - } - - InCallFeedback = new BoolFeedback(() => VideoCodec.IsInCall); - VideoCodec.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; - } - - - /// - /// - /// - 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 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); - - // 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; - else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) - volDev = DefaultDisplay as IBasicVolumeControls; - // 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") - { - CurrentSourceInfoKey = routeKey; - CurrentSourceInfo = null; - } - else if (item.SourceKey != null) - { - 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) - { - // 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); - } - else - DoRoute(route); - } - - /// - /// - /// - /// - /// - bool DoRoute(SourceRouteListItem route) - { - IRoutingSinkNoSwitching dest = null; - - if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) - dest = DefaultAudioDevice as IRoutingSinkNoSwitching; - else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) - dest = DefaultDisplay; - 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); - } - 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 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 - } +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.Config; +using PepperDash.Essentials.Room.Config; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace PepperDash.Essentials +{ + public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange, + IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec + { + public event EventHandler CurrentVolumeDeviceChange; + public event SourceInfoChangeHandler CurrentSingleSourceChange; + + + //************************ + // Call-related stuff + + public BoolFeedback InCallFeedback { get; private set; } + + /// + /// Make this more specific + /// + public List ActiveCalls { 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; } + + //************************ + + + protected override Func OnFeedbackFunc + { + get + { + return () => + { + var disp = DefaultDisplay as DisplayBase; + var val = CurrentSourceInfo != null + && CurrentSourceInfo.Type == eSourceListItemType.Route + && disp != null; + //&& disp.PowerIsOnFeedback.BoolValue; + return val; + }; + } + } + /// + /// + /// + protected override Func IsWarmingFeedbackFunc + { + get + { + return () => + { + var disp = DefaultDisplay as DisplayBase; + if (disp != null) + return disp.IsWarmingUpFeedback.BoolValue; + else + return false; + }; + } + } + /// + /// + /// + protected override Func IsCoolingFeedbackFunc + { + get + { + return () => + { + var disp = DefaultDisplay as DisplayBase; + if (disp != null) + return disp.IsCoolingDownFeedback.BoolValue; + else + return false; + }; + } + } + + public EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; private set; } + + public IRoutingSinkWithSwitching DefaultDisplay { get; private set; } + public IBasicVolumeControls DefaultAudioDevice { get; private set; } + public IBasicVolumeControls DefaultVolumeControls { get; private set; } + + public VideoCodecBase VideoCodec { 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; } + + 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; + + /// + /// The SourceListItem last run - containing names and icons + /// + public SourceListItem CurrentSourceInfo + { + get { return _CurrentSourceInfo; } + private set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSingleSourceChange; + // 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); + + _CurrentSourceInfo = value; + + // add to in-use tracking + if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) + (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); + if (handler != null) + handler(this, _CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + + public string CurrentSourceInfoKey { get; private set; } + + /// + /// "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 EssentialsHuddleVtc1Room(DeviceConfig config) + : base(config) + { + try + { + PropertiesConfig = JsonConvert.DeserializeObject + (config.Properties.ToString()); + DefaultDisplay = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching; + + VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as + PepperDash.Essentials.Devices.Common.VideoCodec.VideoCodecBase; + if (VideoCodec == null) + throw new ArgumentNullException("codec cannot be null"); + + 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 disp = DefaultDisplay as DisplayBase; + if (disp != null) + { + // Link power, warming, cooling to display + disp.PowerIsOnFeedback.OutputChange += (o, a) => + { + if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) + { + if (!disp.PowerIsOnFeedback.BoolValue) + 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(); + }; + } + + InCallFeedback = new BoolFeedback(() => VideoCodec.IsInCall); + VideoCodec.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; + } + + 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 + PepperDash.Essentials.Devices.Common.Occupancy.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); + + // Get Microphone Privacy object, if any + this.MicrophonePrivacy = EssentialsRoomConfigHelper.GetMicrophonePrivacy(PropertiesConfig, this); + + // Get emergency object, if any + this.Emergency = EssentialsRoomConfigHelper.GetEmergency(PropertiesConfig, this); + + 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); + + // 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; + else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) + volDev = DefaultDisplay as IBasicVolumeControls; + // 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") + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = null; + } + else if (item.SourceKey != null) + { + 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) + { + // 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); + } + else + DoRoute(route); + } + + /// + /// + /// + /// + /// + bool DoRoute(SourceRouteListItem route) + { + IRoutingSinkNoSwitching dest = null; + + if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) + dest = DefaultAudioDevice as IRoutingSinkNoSwitching; + else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) + dest = DefaultDisplay; + 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); + } + 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/EssentialsPresentationRoom.cs b/PepperDashEssentials/Room/Types/EssentialsPresentationRoom.cs index 86de8977..c21c265f 100644 --- a/PepperDashEssentials/Room/Types/EssentialsPresentationRoom.cs +++ b/PepperDashEssentials/Room/Types/EssentialsPresentationRoom.cs @@ -1,437 +1,437 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using Crestron.SimplSharp; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Room.Config; +//using PepperDash.Core; +//using PepperDash.Essentials.Core; +//using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials -{ - public class EssentialsPresentationRoom : EssentialsRoomBase, IHasCurrentSourceInfoChange - { - public event EventHandler CurrentVolumeDeviceChange; - public event SourceInfoChangeHandler CurrentSingleSourceChange; - public event SourceInfoChangeHandler CurrentDisplay1SourceChange; - public event SourceInfoChangeHandler CurrentDisplay2SourceChange; +//namespace PepperDash.Essentials +//{ +// public class EssentialsPresentationRoom : EssentialsRoomBase, IHasCurrentSourceInfoChange +// { +// public event EventHandler CurrentVolumeDeviceChange; +// public event SourceInfoChangeHandler CurrentSingleSourceChange; +// public event SourceInfoChangeHandler CurrentDisplay1SourceChange; +// public event SourceInfoChangeHandler CurrentDisplay2SourceChange; - protected override Func OnFeedbackFunc { get { - return () => (CurrentSingleSourceInfo != null - && CurrentSingleSourceInfo.Type != eSourceListItemType.Off) - || (Display1SourceInfo != null - && Display1SourceInfo.Type != eSourceListItemType.Off) - || (Display2SourceInfo != null - && Display2SourceInfo.Type != eSourceListItemType.Off); } } +// protected override Func OnFeedbackFunc { get { +// return () => (CurrentSingleSourceInfo != null +// && CurrentSingleSourceInfo.Type != eSourceListItemType.Off) +// || (Display1SourceInfo != null +// && Display1SourceInfo.Type != eSourceListItemType.Off) +// || (Display2SourceInfo != null +// && Display2SourceInfo.Type != eSourceListItemType.Off); } } - protected override Func IsWarmingFeedbackFunc { get { return () =>false;; } } - protected override Func IsCoolingFeedbackFunc { get { return () => false; } } +// protected override Func IsWarmingFeedbackFunc { get { return () =>false;; } } +// protected override Func IsCoolingFeedbackFunc { get { return () => false; } } - public EssentialsPresentationRoomPropertiesConfig Config { get; private set; } +// public EssentialsPresentationRoomPropertiesConfig Config { get; private set; } - public Dictionary Displays { get; private set; } +// public Dictionary Displays { get; private set; } - public IRoutingSinkNoSwitching DefaultAudioDevice { get; private set; } - public IBasicVolumeControls DefaultVolumeControls { get; private set; } +// public IRoutingSinkNoSwitching DefaultAudioDevice { get; private set; } +// public IBasicVolumeControls DefaultVolumeControls { get; private set; } - /// - /// The config name of the source list - /// - public string SourceListKey { get; set; } +// /// +// /// The config name of the source list +// /// +// public string SourceListKey { get; set; } - /// - /// If room is off, enables power on to last source. Default true - /// - public bool EnablePowerOnToLastSource { get; set; } - string LastSourceKey; +// /// +// /// If room is off, enables power on to last source. Default true +// /// +// public bool EnablePowerOnToLastSource { get; set; } +// string LastSourceKey; - public enum eVideoRoutingMode - { - SelectSourceSelectDisplay, SourceToAllDisplays - } +// public enum eVideoRoutingMode +// { +// SelectSourceSelectDisplay, SourceToAllDisplays +// } - public eVideoRoutingMode VideoRoutingMode { get; set; } +// public eVideoRoutingMode VideoRoutingMode { get; set; } - public enum eAudioRoutingMode - { - AudioFollowsLastVideo, SelectAudioFromDisplay - } +// public enum eAudioRoutingMode +// { +// AudioFollowsLastVideo, SelectAudioFromDisplay +// } - /// - /// - /// - public IBasicVolumeControls CurrentVolumeControls - { - get { return _CurrentAudioDevice; } - set - { - if (value == _CurrentAudioDevice) return; +// /// +// /// +// /// +// 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; +// 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; - /// - /// The SourceListItem last run - containing names and icons. The complex setter is - /// to add/remove this room to the source's InUseTracking, if it is capable - /// - public SourceListItem CurrentSingleSourceInfo - { - get { return _CurrentSingleSourceInfo; } - private set - { - if (value == _CurrentSingleSourceInfo) return; +// /// +// /// The SourceListItem last run - containing names and icons. The complex setter is +// /// to add/remove this room to the source's InUseTracking, if it is capable +// /// +// public SourceListItem CurrentSingleSourceInfo +// { +// get { return _CurrentSingleSourceInfo; } +// private set +// { +// if (value == _CurrentSingleSourceInfo) return; - var handler = CurrentSingleSourceChange; - // remove from in-use tracker, if so equipped - if(_CurrentSingleSourceInfo != null && _CurrentSingleSourceInfo.SourceDevice is IInUseTracking) - (_CurrentSingleSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); +// var handler = CurrentSingleSourceChange; +// // remove from in-use tracker, if so equipped +// if(_CurrentSingleSourceInfo != null && _CurrentSingleSourceInfo.SourceDevice is IInUseTracking) +// (_CurrentSingleSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); - if (handler != null) - handler(this, _CurrentSingleSourceInfo, ChangeType.WillChange); +// if (handler != null) +// handler(this, _CurrentSingleSourceInfo, ChangeType.WillChange); - _CurrentSingleSourceInfo = value; +// _CurrentSingleSourceInfo = value; - // add to in-use tracking - if (_CurrentSingleSourceInfo != null && _CurrentSingleSourceInfo.SourceDevice is IInUseTracking) - (_CurrentSingleSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); - if (handler != null) - handler(this, _CurrentSingleSourceInfo, ChangeType.DidChange); - } - } - SourceListItem _CurrentSingleSourceInfo; +// // add to in-use tracking +// if (_CurrentSingleSourceInfo != null && _CurrentSingleSourceInfo.SourceDevice is IInUseTracking) +// (_CurrentSingleSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); +// if (handler != null) +// handler(this, _CurrentSingleSourceInfo, ChangeType.DidChange); +// } +// } +// SourceListItem _CurrentSingleSourceInfo; - public SourceListItem Display1SourceInfo - { - get { return _Display1SourceInfo; } - set - { - if (value == _Display1SourceInfo) return; +// public SourceListItem Display1SourceInfo +// { +// get { return _Display1SourceInfo; } +// set +// { +// if (value == _Display1SourceInfo) return; - var handler = CurrentDisplay1SourceChange; - if (handler != null) - handler(this, _Display1SourceInfo, ChangeType.WillChange); +// var handler = CurrentDisplay1SourceChange; +// if (handler != null) +// handler(this, _Display1SourceInfo, ChangeType.WillChange); - _Display1SourceInfo = value; +// _Display1SourceInfo = value; - if (handler != null) - handler(this, _Display1SourceInfo, ChangeType.DidChange); - } - } - SourceListItem _Display1SourceInfo; +// if (handler != null) +// handler(this, _Display1SourceInfo, ChangeType.DidChange); +// } +// } +// SourceListItem _Display1SourceInfo; - public SourceListItem Display2SourceInfo - { - get { return _Display2SourceInfo; } - set - { - if (value == _Display2SourceInfo) return; +// public SourceListItem Display2SourceInfo +// { +// get { return _Display2SourceInfo; } +// set +// { +// if (value == _Display2SourceInfo) return; - var handler = CurrentDisplay2SourceChange; - if (handler != null) - handler(this, _Display2SourceInfo, ChangeType.WillChange); +// var handler = CurrentDisplay2SourceChange; +// if (handler != null) +// handler(this, _Display2SourceInfo, ChangeType.WillChange); - _Display2SourceInfo = value; +// _Display2SourceInfo = value; - if (handler != null) - handler(this, _Display2SourceInfo, ChangeType.DidChange); - } - } - SourceListItem _Display2SourceInfo; +// if (handler != null) +// handler(this, _Display2SourceInfo, ChangeType.DidChange); +// } +// } +// SourceListItem _Display2SourceInfo; - /// - /// If an audio dialer is available for this room - /// - public bool HasAudioDialer { get { return false; } } - /// - /// - /// - /// - /// - public EssentialsPresentationRoom(string key, string name, - Dictionary displays, - IBasicVolumeWithFeedback defaultVolume, EssentialsPresentationRoomPropertiesConfig config) - : base(key, name) - { - Config = config; - Displays = displays; +// /// +// /// If an audio dialer is available for this room +// /// +// public bool HasAudioDialer { get { return false; } } +// /// +// /// +// /// +// /// +// /// +// public EssentialsPresentationRoom(string key, string name, +// Dictionary displays, +// IBasicVolumeWithFeedback defaultVolume, EssentialsPresentationRoomPropertiesConfig config) +// : base(key, name) +// { +// Config = config; +// Displays = displays; - DefaultVolumeControls = defaultVolume; - CurrentVolumeControls = defaultVolume; +// DefaultVolumeControls = defaultVolume; +// CurrentVolumeControls = defaultVolume; - //DefaultAudioDevice = defaultAudio; - //if (defaultAudio is IBasicVolumeControls) - // DefaultVolumeControls = defaultAudio as IBasicVolumeControls; - //else if (defaultAudio is IHasVolumeDevice) - // DefaultVolumeControls = (defaultAudio as IHasVolumeDevice).VolumeDevice; +// //DefaultAudioDevice = defaultAudio; +// //if (defaultAudio is IBasicVolumeControls) +// // DefaultVolumeControls = defaultAudio as IBasicVolumeControls; +// //else if (defaultAudio is IHasVolumeDevice) +// // DefaultVolumeControls = (defaultAudio as IHasVolumeDevice).VolumeDevice; - SourceListKey = "default"; - EnablePowerOnToLastSource = true; - } +// SourceListKey = "default"; +// EnablePowerOnToLastSource = true; +// } - /// - /// Run the same source to all destinations - /// - /// - public void RouteSourceToAllDestinations(SourceListItem sourceItem) - { - if (Config.Volumes.Master != null) - { - var audioDev = DeviceManager.GetDeviceForKey(Config.Volumes.Master.DeviceKey); - if (audioDev is IBasicVolumeWithFeedback) - { +// /// +// /// Run the same source to all destinations +// /// +// /// +// public void RouteSourceToAllDestinations(SourceListItem sourceItem) +// { +// if (Config.Volumes.Master != null) +// { +// var audioDev = DeviceManager.GetDeviceForKey(Config.Volumes.Master.DeviceKey); +// if (audioDev is IBasicVolumeWithFeedback) +// { - } - } +// } +// } - foreach (var display in Displays.Values) - { - if (sourceItem != null) - DoVideoRoute(sourceItem.SourceKey, display.Key); - else - DoVideoRoute("$off", display.Key); - } - Display1SourceInfo = sourceItem; - Display2SourceInfo = sourceItem; - CurrentSingleSourceInfo = sourceItem; - OnFeedback.FireUpdate(); - } +// foreach (var display in Displays.Values) +// { +// if (sourceItem != null) +// DoVideoRoute(sourceItem.SourceKey, display.Key); +// else +// DoVideoRoute("$off", display.Key); +// } +// Display1SourceInfo = sourceItem; +// Display2SourceInfo = sourceItem; +// CurrentSingleSourceInfo = sourceItem; +// OnFeedback.FireUpdate(); +// } - public void SourceToDisplay1(SourceListItem sourceItem) - { - DoVideoRoute(sourceItem.SourceKey, Displays[1].Key); - Display1SourceInfo = sourceItem; - OnFeedback.FireUpdate(); - } +// public void SourceToDisplay1(SourceListItem sourceItem) +// { +// DoVideoRoute(sourceItem.SourceKey, Displays[1].Key); +// Display1SourceInfo = sourceItem; +// OnFeedback.FireUpdate(); +// } - public void SourceToDisplay2(SourceListItem sourceItem) - { - DoVideoRoute(sourceItem.SourceKey, Displays[2].Key); - Display2SourceInfo = sourceItem; - OnFeedback.FireUpdate(); - } +// public void SourceToDisplay2(SourceListItem sourceItem) +// { +// DoVideoRoute(sourceItem.SourceKey, Displays[2].Key); +// Display2SourceInfo = sourceItem; +// OnFeedback.FireUpdate(); +// } - /// - /// Basic source -> destination routing - /// - void DoVideoRoute(string sourceKey, string destinationKey) - { - new CTimer(o => - { - var dest = DeviceManager.GetDeviceForKey(destinationKey) as IRoutingSinkNoSwitching; - if (dest == null) - { - Debug.Console(1, this, "Cannot route. Destination '{0}' not found", destinationKey); - return; - } - // off is special case - if (sourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) - { - dest.ReleaseRoute(); - if (dest is IPower) - (dest as IPower).PowerOff(); - return; - } +// /// +// /// Basic source -> destination routing +// /// +// void DoVideoRoute(string sourceKey, string destinationKey) +// { +// new CTimer(o => +// { +// var dest = DeviceManager.GetDeviceForKey(destinationKey) as IRoutingSinkNoSwitching; +// if (dest == null) +// { +// Debug.Console(1, this, "Cannot route. Destination '{0}' not found", destinationKey); +// return; +// } +// // off is special case +// if (sourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) +// { +// dest.ReleaseRoute(); +// if (dest is IPower) +// (dest as IPower).PowerOff(); +// return; +// } - var source = DeviceManager.GetDeviceForKey(sourceKey) as IRoutingOutputs; - if (source == null) - { - Debug.Console(1, this, "Cannot route. Source '{0}' not found", sourceKey); - return; - } - dest.ReleaseAndMakeRoute(source, eRoutingSignalType.Video); - }, 0); - } +// var source = DeviceManager.GetDeviceForKey(sourceKey) as IRoutingOutputs; +// if (source == null) +// { +// Debug.Console(1, this, "Cannot route. Source '{0}' not found", sourceKey); +// return; +// } +// dest.ReleaseAndMakeRoute(source, eRoutingSignalType.Video); +// }, 0); +// } - /// - /// - /// - protected override void EndShutdown() - { - RunRouteAction("roomoff"); - } +// /// +// /// +// /// +// protected override void EndShutdown() +// { +// RunRouteAction("roomoff"); +// } - /// - /// - /// - /// - public void RunRouteAction(string routeKey) - { - RunRouteAction(routeKey, null); - } +// /// +// /// +// /// +// /// +// 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 => - { - Debug.Console(1, this, "Run room 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; - } +// /// +// /// 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 => +// { +// Debug.Console(1, this, "Run room 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; - } +// // 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; +// } - var item = dict[routeKey]; - //Debug.Console(2, this, "Action {0} has {1} steps", - // item.SourceKey, item.RouteList.Count); +// var item = dict[routeKey]; +// //Debug.Console(2, this, "Action {0} has {1} steps", +// // item.SourceKey, item.RouteList.Count); - // Let's run it - if (routeKey.ToLower() != "roomoff") - LastSourceKey = routeKey; +// // Let's run it +// if (routeKey.ToLower() != "roomoff") +// LastSourceKey = routeKey; - foreach (var route in item.RouteList) - { - // if there is a $defaultAll on route, run two separate - if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase)) - { - var tempAudio = new SourceRouteListItem - { - DestinationKey = "$defaultDisplay", - SourceKey = route.SourceKey, - Type = eRoutingSignalType.Video - }; - DoRoute(tempAudio); +// foreach (var route in item.RouteList) +// { +// // if there is a $defaultAll on route, run two separate +// if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase)) +// { +// var tempAudio = new SourceRouteListItem +// { +// DestinationKey = "$defaultDisplay", +// SourceKey = route.SourceKey, +// Type = eRoutingSignalType.Video +// }; +// DoRoute(tempAudio); - var tempVideo = new SourceRouteListItem - { - DestinationKey = "$defaultAudio", - SourceKey = route.SourceKey, - Type = eRoutingSignalType.Audio - }; - DoRoute(tempVideo); - continue; - } - else - DoRoute(route); - } +// var tempVideo = new SourceRouteListItem +// { +// DestinationKey = "$defaultAudio", +// SourceKey = route.SourceKey, +// Type = eRoutingSignalType.Audio +// }; +// DoRoute(tempVideo); +// continue; +// } +// else +// DoRoute(route); +// } - // Set volume control on room, 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; - //else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) - // volDev = DefaultDisplay as IBasicVolumeControls; - // 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; - } - CurrentVolumeControls = volDev; +// // Set volume control on room, 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; +// //else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) +// // volDev = DefaultDisplay as IBasicVolumeControls; +// // 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; +// } +// CurrentVolumeControls = volDev; - // store the name and UI info for routes - if (item.SourceKey != null) - CurrentSingleSourceInfo = item; - // And finally, set the "control". This will trigger event - //CurrentControlDevice = DeviceManager.GetDeviceForKey(item.SourceKey) as Device; +// // store the name and UI info for routes +// if (item.SourceKey != null) +// CurrentSingleSourceInfo = item; +// // And finally, set the "control". This will trigger event +// //CurrentControlDevice = DeviceManager.GetDeviceForKey(item.SourceKey) as Device; - OnFeedback.FireUpdate(); +// OnFeedback.FireUpdate(); - // report back when done - if (successCallback != null) - successCallback(); - }, 0); // end of CTimer - } +// // report back when done +// if (successCallback != null) +// successCallback(); +// }, 0); // end of CTimer +// } - /// - /// Will power the room on with the last-used source - /// - public void PowerOnToDefaultOrLastSource() - { - if (!EnablePowerOnToLastSource || LastSourceKey == null) - return; - RunRouteAction(LastSourceKey); - } +// /// +// /// Will power the room on with the last-used source +// /// +// public void PowerOnToDefaultOrLastSource() +// { +// if (!EnablePowerOnToLastSource || LastSourceKey == null) +// return; +// RunRouteAction(LastSourceKey); +// } - /// - /// Does what it says - /// - public override void SetDefaultLevels() - { - Debug.Console(0, this, "SetDefaultLevels not implemented"); - } +// /// +// /// Does what it says +// /// +// public override void SetDefaultLevels() +// { +// Debug.Console(0, this, "SetDefaultLevels not implemented"); +// } - /// - /// - /// - /// - /// - bool DoRoute(SourceRouteListItem route) - { - IRoutingSinkNoSwitching dest = null; +// /// +// /// +// /// +// /// +// /// +// bool DoRoute(SourceRouteListItem route) +// { +// IRoutingSinkNoSwitching dest = null; - if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) - dest = DefaultAudioDevice; - //else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) - // dest = DefaultDisplay; - else - dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching; +// if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) +// dest = DefaultAudioDevice; +// //else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) +// // dest = DefaultDisplay; +// 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 (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); - } - return true; - } +// 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); +// } +// return true; +// } - public override void RoomVacatedForTimeoutPeriod(object o) - { - //Implement this - } +// public override void RoomVacatedForTimeoutPeriod(object o) +// { +// //Implement this +// } - } -} \ No newline at end of file +// } +//} \ No newline at end of file diff --git a/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs b/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs index 6251c067..fc177b51 100644 --- a/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs +++ b/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs @@ -1,272 +1,297 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Occupancy; - -namespace PepperDash.Essentials -{ - /// - /// - /// - public interface IHasCurrentSourceInfoChange - { - event SourceInfoChangeHandler CurrentSingleSourceChange; - } - - /// - /// - /// - public abstract class EssentialsRoomBase : Device - { - /// - /// - /// - public BoolFeedback OnFeedback { get; private set; } - - 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(string key, string name) : base(key, name) - { - // 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); - } - - 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(); - } - - 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."); - } - - /// - /// 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; - - RoomOccupancy = statusProvider; - - RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; - } - - 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(); - } - } - - //void SwapVolumeDevices(IBasicVolumeControls currentDevice, IBasicVolumeControls newDevice) - //{ - - //} - - /// - /// 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; +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 + } } \ No newline at end of file diff --git a/PepperDashEssentials/UIDrivers/Essentials/EssentialsHeaderDriver.cs b/PepperDashEssentials/UIDrivers/Essentials/EssentialsHeaderDriver.cs index 1a51fb44..68d4cbcb 100644 --- a/PepperDashEssentials/UIDrivers/Essentials/EssentialsHeaderDriver.cs +++ b/PepperDashEssentials/UIDrivers/Essentials/EssentialsHeaderDriver.cs @@ -76,7 +76,7 @@ namespace PepperDash.Essentials avDriver.PopupInterlock.HideAndClear()); } - void SetUpHelpButton(EssentialsRoomPropertiesConfig roomConf) + public void SetUpHelpButton(EssentialsRoomPropertiesConfig roomConf) { // Help roomConf and popup if (roomConf.Help != null) @@ -107,7 +107,7 @@ namespace PepperDash.Essentials var room = DeviceManager.GetDeviceForKey(Config.DefaultRoomKey) as EssentialsHuddleSpaceRoom; if (room != null) - message = room.Config.HelpMessage; + message = room.PropertiesConfig.HelpMessage; else message = "Sorry, no help message available. No room connected."; //TriList.StringInput[UIStringJoin.HelpMessage].StringValue = message; @@ -227,7 +227,7 @@ namespace PepperDash.Essentials TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, true); - var roomConf = currentRoom.Config; + var roomConf = currentRoom.PropertiesConfig; // Register for the PopupInterlock IsShowsFeedback event to tie the header carets subpage visiblity to it Parent.AvDriver.PopupInterlock.StatusChanged -= PopupInterlock_StatusChanged; @@ -289,7 +289,7 @@ namespace PepperDash.Essentials TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, true); - var roomConf = currentRoom.Config; + var roomConf = currentRoom.PropertiesConfig; // Register for the PopupInterlock IsShowsFeedback event to tie the header carets subpage visiblity to it Parent.AvDriver.PopupInterlock.StatusChanged -= PopupInterlock_StatusChanged; diff --git a/PepperDashEssentials/UIDrivers/Essentials/EssentialsPresentationPanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/Essentials/EssentialsPresentationPanelAvFunctionsDriver.cs index e7e13020..edda29e0 100644 --- a/PepperDashEssentials/UIDrivers/Essentials/EssentialsPresentationPanelAvFunctionsDriver.cs +++ b/PepperDashEssentials/UIDrivers/Essentials/EssentialsPresentationPanelAvFunctionsDriver.cs @@ -1,1048 +1,1048 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using Crestron.SimplSharp; +//using Crestron.SimplSharpPro; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.SmartObjects; -using PepperDash.Essentials.Core.PageManagers; +//using PepperDash.Core; +//using PepperDash.Essentials.Core; +//using PepperDash.Essentials.Core.Config; +//using PepperDash.Essentials.Core.SmartObjects; +//using PepperDash.Essentials.Core.PageManagers; -namespace PepperDash.Essentials -{ - /// - /// - /// - public class EssentialsPresentationPanelAvFunctionsDriver : PanelDriverBase - { - /// - /// Smart Object 3200 - /// - SubpageReferenceList SourcesSrl; +//namespace PepperDash.Essentials +//{ +// /// +// /// +// /// +// public class EssentialsPresentationPanelAvFunctionsDriver : PanelDriverBase +// { +// /// +// /// Smart Object 3200 +// /// +// SubpageReferenceList SourcesSrl; - /// - /// For tracking feedback on last selected - /// - BoolInputSig LastSelectedSourceSig; +// /// +// /// For tracking feedback on last selected +// /// +// BoolInputSig LastSelectedSourceSig; - /// - /// The source that has been selected and is awaiting assignment to a display - /// - SourceListItem PendingSource; +// /// +// /// The source that has been selected and is awaiting assignment to a display +// /// +// SourceListItem PendingSource; - bool IsSharingModeAdvanced; +// bool IsSharingModeAdvanced; - CrestronTouchpanelPropertiesConfig Config; +// CrestronTouchpanelPropertiesConfig Config; - public enum UiDisplayMode - { - PresentationMode, AudioSetup - } +// public enum UiDisplayMode +// { +// PresentationMode, AudioSetup +// } - /// - /// Whether volume ramping from this panel will show the volume - /// gauge popup. - /// - public bool ShowVolumeGauge { get; set; } +// /// +// /// Whether volume ramping from this panel will show the volume +// /// gauge popup. +// /// +// public bool ShowVolumeGauge { get; set; } - /// - /// The amount of time that the volume buttons stays on screen, in ms - /// - public uint VolumeButtonPopupTimeout - { - get { return VolumeButtonsPopupFeedback.TimeoutMs; } - set { VolumeButtonsPopupFeedback.TimeoutMs = value; } - } +// /// +// /// The amount of time that the volume buttons stays on screen, in ms +// /// +// public uint VolumeButtonPopupTimeout +// { +// get { return VolumeButtonsPopupFeedback.TimeoutMs; } +// set { VolumeButtonsPopupFeedback.TimeoutMs = value; } +// } - /// - /// The amount of time that the volume gauge stays on screen, in ms - /// - public uint VolumeGaugePopupTimeout - { - get { return VolumeGaugeFeedback.TimeoutMs; } - set { VolumeGaugeFeedback.TimeoutMs = value; } - } - - /// - /// - /// - public uint PowerOffTimeout { get; set; } - - /// - /// - /// - public string DefaultRoomKey - { - get { return _DefaultRoomKey; } - set - { - _DefaultRoomKey = value; - CurrentRoom = DeviceManager.GetDeviceForKey(value) as EssentialsPresentationRoom; - } - } - string _DefaultRoomKey; - - /// - /// - /// - public EssentialsPresentationRoom CurrentRoom - { - get { return _CurrentRoom; } - set - { - SetCurrentRoom(value); - } - } - EssentialsPresentationRoom _CurrentRoom; - - /// - /// For hitting feedback - /// - BoolInputSig ShareButtonSig; - BoolInputSig EndMeetingButtonSig; - - /// - /// Controls the extended period that the volume gauge shows on-screen, - /// as triggered by Volume up/down operations - /// - BoolFeedbackPulseExtender VolumeGaugeFeedback; - - /// - /// Controls the period that the volume buttons show on non-hard-button - /// interfaces - /// - BoolFeedbackPulseExtender VolumeButtonsPopupFeedback; - - /// - /// The parent driver for this - /// - PanelDriverBase Parent; - - ///// - ///// Driver that manages advanced sharing features - ///// - //DualDisplaySimpleOrAdvancedRouting DualDisplayUiDriver; - - /// - /// All children attached to this driver. For hiding and showing as a group. - /// - List ChildDrivers = new List(); - - List CurrentDisplayModeSigsInUse = new List(); - - /// - /// Smart Object 15022 - /// - SubpageReferenceList ActivityFooterSrl; - - /// - /// Tracks which audio page group the UI is in - /// - UiDisplayMode CurrentDisplayMode; - - /// - /// The AV page mangagers that have been used, to keep them alive for later - /// - Dictionary PageManagers = new Dictionary(); - - /// - /// Current page manager running for a source - /// - PageManager CurrentSourcePageManager; - - /// - /// Will auto-timeout a power off - /// - CTimer PowerOffTimer; - - /// - /// Constructor - /// - public EssentialsPresentationPanelAvFunctionsDriver(PanelDriverBase parent, - CrestronTouchpanelPropertiesConfig config) - : base(parent.TriList) - { - Config = config; - Parent = parent; - - ActivityFooterSrl = new SubpageReferenceList(TriList, 15022, 3, 3, 3); - //SetupActivityFooterWhenRoomOff(); - - ShowVolumeGauge = true; - - // One-second pulse extender for volume gauge - VolumeGaugeFeedback = new BoolFeedbackPulseExtender(1500); - VolumeGaugeFeedback.Feedback - .LinkInputSig(TriList.BooleanInput[UIBoolJoin.VolumeGaugePopupVisible]); - - VolumeButtonsPopupFeedback = new BoolFeedbackPulseExtender(4000); - VolumeButtonsPopupFeedback.Feedback - .LinkInputSig(TriList.BooleanInput[UIBoolJoin.VolumeButtonPopupVisible]); - - PowerOffTimeout = 30000; - - SourcesSrl = new SubpageReferenceList(TriList, 3200, 3, 3, 3); - - TriList.StringInput[UIStringJoin.StartActivityText].StringValue = - "Tap an activity to begin"; - - // Sharing mode things - TriList.SetSigFalseAction(UIBoolJoin.ToggleSharingModePress, ToggleSharingModePressed); - - TriList.SetSigFalseAction(UIBoolJoin.Display1AudioButtonPressAndFb, Display1AudioPress); - TriList.SetSigFalseAction(UIBoolJoin.Display1ControlButtonPress, Display1ControlPress); - TriList.SetSigTrueAction(UIBoolJoin.Display1SelectPressAndFb, Display1Press); - - TriList.SetSigFalseAction(UIBoolJoin.Display2AudioButtonPressAndFb, Display2AudioPress); - TriList.SetSigFalseAction(UIBoolJoin.Display2ControlButtonPress, Display2ControlPress); - TriList.SetSigTrueAction(UIBoolJoin.Display2SelectPressAndFb, Display2Press); - } - - /// - /// - /// - public override void Show() - { - TriList.BooleanInput[UIBoolJoin.TopBarHabaneroVisible].BoolValue = true; - TriList.BooleanInput[UIBoolJoin.ActivityFooterVisible].BoolValue = true; - - // Default to showing rooms/sources now. - ShowMode(UiDisplayMode.PresentationMode); - - // Attach actions - TriList.SetSigFalseAction(UIBoolJoin.VolumeButtonPopupPress, VolumeButtonsTogglePress); - - //Interlocked modals - TriList.SetSigFalseAction(UIBoolJoin.InterlockedModalClosePress, HideCurrentInterlockedModal); - TriList.SetSigFalseAction(UIBoolJoin.HelpPress, () => - { - string message = null; - var room = DeviceManager.GetDeviceForKey(Config.DefaultRoomKey) - as EssentialsPresentationRoom; - if (room != null) - message = room.Config.HelpMessage; - else - message = "Sorry, no help message available. No room connected."; - TriList.StringInput[UIStringJoin.HelpMessage].StringValue = message; - ShowInterlockedModal(UIBoolJoin.HelpPageVisible); - }); - - TriList.SetSigFalseAction(UIBoolJoin.HeaderRoomButtonPress, () => - ShowInterlockedModal(UIBoolJoin.RoomHeaderPageVisible)); - - TriList.SetSigFalseAction(UIBoolJoin.FIXFIX_HeaderGearButtonPress_FIXFIX, () => - ShowInterlockedModal(UIBoolJoin.VolumesPageVisible)); - - // power-related functions - // Note: some of these are not directly-related to the huddle space UI, but are held over - // in case - TriList.SetSigFalseAction(UIBoolJoin.ShowPowerOffPress, PowerButtonPressed); - TriList.SetSigFalseAction(UIBoolJoin.PowerOffCancelPress, CancelPowerOff); - TriList.SetSigFalseAction(UIBoolJoin.PowerOffConfirmPress, FinishPowerOff); - TriList.SetSigFalseAction(UIBoolJoin.PowerOffMorePress, () => - { - CancelPowerOffTimer(); - TriList.BooleanInput[UIBoolJoin.PowerOffStep1Visible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.PowerOffStep2Visible].BoolValue = true; - }); - TriList.SetSigFalseAction(UIBoolJoin.AllRoomsOffPress, () => - { - EssentialsHuddleSpaceRoom.AllRoomsOff(); - CancelPowerOff(); - }); - - SetupActivityFooterWhenRoomOff(); - - base.Show(); - } - - /// - /// - /// - public override void Hide() - { - var tl = TriList.BooleanInput; - HideAndClearCurrentDisplayModeSigsInUse(); - tl[UIBoolJoin.TopBarHabaneroVisible].BoolValue = false; - tl[UIBoolJoin.ActivityFooterVisible].BoolValue = false; - tl[UIBoolJoin.StartPageVisible].BoolValue = false; - tl[UIBoolJoin.TapToBeginVisible].BoolValue = false; - tl[UIBoolJoin.ToggleSharingModeVisible].BoolValue = false; - tl[UIBoolJoin.SourceStagingBarVisible].BoolValue = false; - if (IsSharingModeAdvanced) - tl[UIBoolJoin.DualDisplayPageVisible].BoolValue = false; - else - tl[UIBoolJoin.SelectASourceVisible].BoolValue = false; - - VolumeButtonsPopupFeedback.ClearNow(); - CancelPowerOff(); - - base.Hide(); - } - - /// - /// - /// - void ShowCurrentSharingMode() - { - var tlb = TriList.BooleanInput; - tlb[UIBoolJoin.ToggleSharingModeVisible].BoolValue = true; - tlb[UIBoolJoin.SourceStagingBarVisible].BoolValue = true; - if (IsSharingModeAdvanced) - { - tlb[UIBoolJoin.DualDisplayPageVisible].BoolValue = true; - TriList.StringInput[UIStringJoin.Display1TitleLabel].StringValue = - (CurrentRoom.Displays[1] as IKeyName).Name; - TriList.StringInput[UIStringJoin.Display2TitleLabel].StringValue = - (CurrentRoom.Displays[2] as IKeyName).Name; - } - else - tlb[UIBoolJoin.SelectASourceVisible].BoolValue = true; - } - - /// - /// - /// - void HideCurrentSharingMode() - { - var tl = TriList.BooleanInput; - tl[UIBoolJoin.ToggleSharingModeVisible].BoolValue = false; - tl[UIBoolJoin.SourceStagingBarVisible].BoolValue = false; - tl[UIBoolJoin.DualDisplayPageVisible].BoolValue = false; - tl[UIBoolJoin.SelectASourceVisible].BoolValue = false; - } - - /// - /// Shows the various "modes" that this driver controls. Presentation, Setup page - /// - /// - public void ShowMode(UiDisplayMode mode) - { - //Clear whatever is showing now. - HideAndClearCurrentDisplayModeSigsInUse(); - CurrentDisplayMode = mode; - switch (mode) - { - case UiDisplayMode.PresentationMode: - // show start page or staging... - if (CurrentRoom.OnFeedback.BoolValue) - { - //TriList.BooleanInput[UIBoolJoin.StagingPageVisible].BoolValue = true; - TriList.BooleanInput[UIBoolJoin.TapToBeginVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; - } - else - { - TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = true; - TriList.BooleanInput[UIBoolJoin.TapToBeginVisible].BoolValue = true; - TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; - } - // Date/time - if (Config.ShowDate && Config.ShowTime) - { - TriList.BooleanInput[UIBoolJoin.DateAndTimeVisible].BoolValue = true; - TriList.BooleanInput[UIBoolJoin.DateOnlyVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.TimeOnlyVisible].BoolValue = false; - } - else - { - TriList.BooleanInput[UIBoolJoin.DateAndTimeVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.DateOnlyVisible].BoolValue = Config.ShowDate; - TriList.BooleanInput[UIBoolJoin.TimeOnlyVisible].BoolValue = Config.ShowTime; - } - - //TriList.SetSigFalseAction(UIBoolJoin.ToggleSharingModePress, ToggleSharingModePressed); - - ShowCurrentDisplayModeSigsInUse(); - break; - } - } - - /// - /// - /// - void SetupSourceList() - { - // get the source list config and set up the source list - var config = ConfigReader.ConfigObject.SourceLists; - if (config.ContainsKey(CurrentRoom.SourceListKey)) - { - var srcList = config[CurrentRoom.SourceListKey] - .Values.ToList().OrderBy(s => s.Order); - // Setup sources list - uint i = 1; // counter for UI list - foreach (var srcConfig in srcList) - { - if (!srcConfig.IncludeInSourceList) // Skip sources marked this way - continue; - - var sourceKey = srcConfig.SourceKey; - var actualSource = DeviceManager.GetDeviceForKey(sourceKey) as Device; - if (actualSource == null) - { - Debug.Console(0, "Cannot assign missing source '{0}' to source UI list", - srcConfig.SourceKey); - continue; - } - var localSrcItem = srcConfig; // lambda scope below - var localIndex = i; - SourcesSrl.GetBoolFeedbackSig(i, 1).UserObject = new Action(b => - { - if (b) return; - if (LastSelectedSourceSig != null) - LastSelectedSourceSig.BoolValue = false; - LastSelectedSourceSig = SourcesSrl.BoolInputSig(localIndex, 1); - LastSelectedSourceSig.BoolValue = true; - if (IsSharingModeAdvanced) - { - PendingSource = localSrcItem; - } - else - { - CurrentRoom.RouteSourceToAllDestinations(localSrcItem); - } - }); - SourcesSrl.StringInputSig(i, 1).StringValue = srcConfig.PreferredName; - i++; - } - var count = (ushort)(i-1); - SourcesSrl.Count = count; - TriList.BooleanInput[UIBoolJoin.StagingPageAdditionalArrowsVisible].BoolValue = - count >= Config.SourcesOverflowCount; - - _CurrentRoom.CurrentDisplay1SourceChange += _CurrentRoom_CurrentDisplay1SourceChange; - _CurrentRoom.CurrentDisplay2SourceChange += _CurrentRoom_CurrentDisplay2SourceChange; - } - } - - /// - /// - /// - void ToggleSharingModePressed() - { - if (CurrentSourcePageManager != null) - CurrentSourcePageManager.Hide(); - HideCurrentSharingMode(); - IsSharingModeAdvanced = !IsSharingModeAdvanced; - TriList.BooleanInput[UIBoolJoin.ToggleSharingModePress].BoolValue = IsSharingModeAdvanced; - ShowCurrentSharingMode(); - } - - ///// - ///// - ///// - //void EnableAppropriateDisplayButtons() - //{ - // if (LastSelectedSourceSig != null) - // LastSelectedSourceSig.BoolValue = false; - //} - - public void Display1Press() - { - CurrentRoom.SourceToDisplay1(PendingSource); - } - - public void Display1AudioPress() - { - - } - - - public void Display1ControlPress() - { - var uiDev = CurrentRoom.Display1SourceInfo.SourceDevice as IUiDisplayInfo; - ShowSource(uiDev); - } - - public void Display2Press() - { - CurrentRoom.SourceToDisplay2(PendingSource); - } - - public void Display2AudioPress() - { - - } - - public void Display2ControlPress() - { - var uiDev = CurrentRoom.Display2SourceInfo.SourceDevice as IUiDisplayInfo; - ShowSource(uiDev); - } - - /// - /// When the room is off, set the footer SRL - /// - void SetupActivityFooterWhenRoomOff() - { - ActivityFooterSrl.Clear(); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, 0, - b => { if (!b) ShareButtonPressed(); })); - // only show phone call when there's a dialer present - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, 1, - b => { })); - ActivityFooterSrl.Count = (ushort)(CurrentRoom.HasAudioDialer ? 2 : 1); - TriList.UShortInput[UIUshortJoin.PresentationStagingCaretMode].UShortValue = - (ushort)(CurrentRoom.HasAudioDialer ? 1 : 0); - } - - /// - /// Sets up the footer SRL for when the room is on - /// - void SetupActivityFooterWhenRoomOn() - { - ActivityFooterSrl.Clear(); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, - 0, null)); - if (CurrentRoom.HasAudioDialer) - { - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, - 1, b => { })); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(3, ActivityFooterSrl, - 3, b => { if (!b) PowerButtonPressed(); })); - ActivityFooterSrl.Count = 3; - TriList.UShortInput[UIUshortJoin.PresentationStagingCaretMode].UShortValue = 2; - EndMeetingButtonSig = ActivityFooterSrl.BoolInputSig(3, 1); - } - else - { - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, - 3, b => { if (!b) PowerButtonPressed(); })); - ActivityFooterSrl.Count = 2; - TriList.UShortInput[UIUshortJoin.PresentationStagingCaretMode].UShortValue = 1; - EndMeetingButtonSig = ActivityFooterSrl.BoolInputSig(2, 1); - } - } - - /// - /// Attached to activity list share button - /// - void ShareButtonPressed() - { - ShareButtonSig = ActivityFooterSrl.BoolInputSig(1, 1); - if (!_CurrentRoom.OnFeedback.BoolValue) - { - ShareButtonSig.BoolValue = true; - TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = false; - ShowCurrentSharingMode(); - } - } - - uint CurrentInterlockedModalJoin; - - void ShowInterlockedModal(uint join) - { - if (CurrentInterlockedModalJoin == join) - HideCurrentInterlockedModal(); - else - { - TriList.BooleanInput[UIBoolJoin.HelpPageVisible].BoolValue = join == UIBoolJoin.HelpPageVisible; - TriList.BooleanInput[UIBoolJoin.RoomHeaderPageVisible].BoolValue = join == UIBoolJoin.RoomHeaderPageVisible; - TriList.BooleanInput[UIBoolJoin.VolumesPageVisible].BoolValue = join == UIBoolJoin.VolumesPageVisible; - CurrentInterlockedModalJoin = join; - } - } - - void HideCurrentInterlockedModal() - { - TriList.BooleanInput[CurrentInterlockedModalJoin].BoolValue = false; - CurrentInterlockedModalJoin = 0; - } - - /// - /// Shows all sigs that are in CurrentDisplayModeSigsInUse - /// - void ShowCurrentDisplayModeSigsInUse() - { - foreach (var sig in CurrentDisplayModeSigsInUse) - sig.BoolValue = true; - } - - /// - /// Hides all CurrentDisplayModeSigsInUse sigs and clears the array - /// - void HideAndClearCurrentDisplayModeSigsInUse() - { - foreach (var sig in CurrentDisplayModeSigsInUse) - sig.BoolValue = false; - CurrentDisplayModeSigsInUse.Clear(); - } - - /// - /// Send the UI back depending on location, not used in huddle UI - /// - public override void BackButtonPressed() - { - switch (CurrentDisplayMode) - { - case UiDisplayMode.PresentationMode: - //CancelReturnToSourceTimer(); - BackToHome(); - break; - } - } - - /// - /// - /// - void BackToHome() - { - Hide(); - Parent.Show(); - } - - /// - /// Loads the appropriate Sigs into CurrentDisplayModeSigsInUse and shows them - /// - void ShowCurrentSource() - { - if (CurrentRoom.CurrentSingleSourceInfo == null) - return; - var uiDev = CurrentRoom.CurrentSingleSourceInfo.SourceDevice as IUiDisplayInfo; - ShowSource(uiDev); - } - - void ShowSource(IUiDisplayInfo uiDev) - { - PageManager pm = null; - // If we need a page manager, get an appropriate one - if (uiDev != null) - { - TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; - if (IsSharingModeAdvanced) - { - TriList.BooleanInput[UIBoolJoin.SourceBackgroundOverlayVisible].BoolValue = true; - TriList.SetSigFalseAction(UIBoolJoin.SourceBackgroundOverlayClosePress, new Action(() => - { - TriList.BooleanInput[UIBoolJoin.SourceBackgroundOverlayVisible].BoolValue = false; - if (CurrentSourcePageManager != null) - CurrentSourcePageManager.Hide(); - })); - } - - - // Got an existing page manager, get it - if (PageManagers.ContainsKey(uiDev)) - pm = PageManagers[uiDev]; - // Otherwise make an apporiate one - else if (uiDev is ISetTopBoxControls) - pm = new SetTopBoxThreePanelPageManager(uiDev as ISetTopBoxControls, TriList); - else if (uiDev is IDiscPlayerControls) - pm = new DiscPlayerMediumPageManager(uiDev as IDiscPlayerControls, TriList); - else - pm = new DefaultPageManager(uiDev, TriList); - PageManagers[uiDev] = pm; - CurrentSourcePageManager = pm; - pm.Show(); - } - } - - /// - /// - /// - public void PowerButtonPressed() - { - if (!CurrentRoom.OnFeedback.BoolValue) - return; - EndMeetingButtonSig.BoolValue = true; - ShareButtonSig.BoolValue = false; - // Timeout or button 1 press will shut down - var modal = new ModalDialog(TriList); - uint time = 60000; - uint seconds = time / 1000; - var message = string.Format("Meeting will end in {0} seconds", seconds); - modal.PresentModalDialog(2, "End Meeting", "Power", message, - "End Meeting Now", "Cancel", true, true, - but => - { - EndMeetingButtonSig.BoolValue = false; - if (but != 2) - { - CurrentRoom.RouteSourceToAllDestinations(null); - } - else - ShareButtonSig.BoolValue = true; // restore Share fb - }); - } - - void CancelPowerOffTimer() - { - if (PowerOffTimer != null) - { - PowerOffTimer.Stop(); - PowerOffTimer = null; - } - } - - /// - /// Runs the power off function on the current room - /// - public void FinishPowerOff() - { - if (CurrentRoom == null) - return; - CurrentRoom.RunRouteAction("roomOff"); - CancelPowerOff(); - } - - /// - /// Hides power off pages and stops timer - /// - void CancelPowerOff() - { - CancelPowerOffTimer(); - TriList.BooleanInput[UIBoolJoin.PowerOffStep1Visible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.PowerOffStep2Visible].BoolValue = false; - } - - /// - /// - /// - void VolumeButtonsTogglePress() - { - if (VolumeButtonsPopupFeedback.BoolValue) - VolumeButtonsPopupFeedback.ClearNow(); - else - { - // Trigger the popup - VolumeButtonsPopupFeedback.BoolValue = true; - VolumeButtonsPopupFeedback.BoolValue = false; - } - } - - /// - /// - /// - /// - public void VolumeUpPress(bool state) - { - // extend timeouts - if (ShowVolumeGauge) - VolumeGaugeFeedback.BoolValue = state; - VolumeButtonsPopupFeedback.BoolValue = state; - if (CurrentRoom.CurrentVolumeControls != null) - CurrentRoom.CurrentVolumeControls.VolumeUp(state); - } - - /// - /// - /// - /// - public void VolumeDownPress(bool state) - { - // extend timeouts - if (ShowVolumeGauge) - VolumeGaugeFeedback.BoolValue = state; - VolumeButtonsPopupFeedback.BoolValue = state; - if (CurrentRoom.CurrentVolumeControls != null) - CurrentRoom.CurrentVolumeControls.VolumeDown(state); - } - - /// - /// Helper for property setter. Sets the panel to the given room, latching up all functionality - /// - void SetCurrentRoom(EssentialsPresentationRoom room) - { - if (_CurrentRoom == room) return; - if (_CurrentRoom != null) - { - // Disconnect current room - _CurrentRoom.OnFeedback.OutputChange -= _CurrentRoom_OnFeedback_OutputChange; - _CurrentRoom.CurrentVolumeDeviceChange -= this._CurrentRoom_CurrentAudioDeviceChange; - ClearAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange -= this._CurrentRoom_SourceInfoChange; - DisconnectSource(_CurrentRoom.CurrentSingleSourceInfo); - } - _CurrentRoom = room; - - if (_CurrentRoom != null) - { - if (IsSharingModeAdvanced) - {} // add stuff here - else - SetupSourceList(); - TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = _CurrentRoom.Name; - - // Link up all the change events from the room - _CurrentRoom.OnFeedback.OutputChange += _CurrentRoom_OnFeedback_OutputChange; - _CurrentRoom.CurrentVolumeDeviceChange += _CurrentRoom_CurrentAudioDeviceChange; - RefreshAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange += _CurrentRoom_SourceInfoChange; - RefreshSourceInfo(); - } - else - { - // Clear sigs that need to be - TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = "Select a room"; - } - } - - /// - /// Hides source for provided source info - /// - /// - void DisconnectSource(SourceListItem previousInfo) - { - if (previousInfo == null) return; - - // Hide whatever is showing - if (IsVisible) - { - if (CurrentSourcePageManager != null) - { - CurrentSourcePageManager.Hide(); - CurrentSourcePageManager = null; - } - } - - if (previousInfo == null) return; - var previousDev = previousInfo.SourceDevice; - - // device type interfaces - if (previousDev is ISetTopBoxControls) - (previousDev as ISetTopBoxControls).UnlinkButtons(TriList); - // common interfaces - if (previousDev is IChannel) - (previousDev as IChannel).UnlinkButtons(TriList); - if (previousDev is IColor) - (previousDev as IColor).UnlinkButtons(TriList); - if (previousDev is IDPad) - (previousDev as IDPad).UnlinkButtons(TriList); - if (previousDev is IDvr) - (previousDev as IDvr).UnlinkButtons(TriList); - if (previousDev is INumericKeypad) - (previousDev as INumericKeypad).UnlinkButtons(TriList); - if (previousDev is IPower) - (previousDev as IPower).UnlinkButtons(TriList); - if (previousDev is ITransport) - (previousDev as ITransport).UnlinkButtons(TriList); - //if (previousDev is IRadio) - // (previousDev as IRadio).UnlinkButtons(this); - } - - /// - /// Refreshes and shows the room's current source - /// - void RefreshSourceInfo() - { - var routeInfo = CurrentRoom.CurrentSingleSourceInfo; - // This will show off popup too - if (this.IsVisible) - ShowCurrentSource(); - - if (routeInfo == null)// || !CurrentRoom.OnFeedback.BoolValue) - { - // Check for power off and insert "Room is off" - TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "Room is off"; - TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Power"; - this.Hide(); - Parent.Show(); - return; - } - else if (CurrentRoom.CurrentSingleSourceInfo != null) - { - TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = routeInfo.PreferredName; - TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = routeInfo.Icon; // defaults to "blank" - } - else - { - TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "---"; - TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Blank"; - } +// /// +// /// The amount of time that the volume gauge stays on screen, in ms +// /// +// public uint VolumeGaugePopupTimeout +// { +// get { return VolumeGaugeFeedback.TimeoutMs; } +// set { VolumeGaugeFeedback.TimeoutMs = value; } +// } + +// /// +// /// +// /// +// public uint PowerOffTimeout { get; set; } + +// /// +// /// +// /// +// public string DefaultRoomKey +// { +// get { return _DefaultRoomKey; } +// set +// { +// _DefaultRoomKey = value; +// CurrentRoom = DeviceManager.GetDeviceForKey(value) as EssentialsPresentationRoom; +// } +// } +// string _DefaultRoomKey; + +// /// +// /// +// /// +// public EssentialsPresentationRoom CurrentRoom +// { +// get { return _CurrentRoom; } +// set +// { +// SetCurrentRoom(value); +// } +// } +// EssentialsPresentationRoom _CurrentRoom; + +// /// +// /// For hitting feedback +// /// +// BoolInputSig ShareButtonSig; +// BoolInputSig EndMeetingButtonSig; + +// /// +// /// Controls the extended period that the volume gauge shows on-screen, +// /// as triggered by Volume up/down operations +// /// +// BoolFeedbackPulseExtender VolumeGaugeFeedback; + +// /// +// /// Controls the period that the volume buttons show on non-hard-button +// /// interfaces +// /// +// BoolFeedbackPulseExtender VolumeButtonsPopupFeedback; + +// /// +// /// The parent driver for this +// /// +// PanelDriverBase Parent; + +// ///// +// ///// Driver that manages advanced sharing features +// ///// +// //DualDisplaySimpleOrAdvancedRouting DualDisplayUiDriver; + +// /// +// /// All children attached to this driver. For hiding and showing as a group. +// /// +// List ChildDrivers = new List(); + +// List CurrentDisplayModeSigsInUse = new List(); + +// /// +// /// Smart Object 15022 +// /// +// SubpageReferenceList ActivityFooterSrl; + +// /// +// /// Tracks which audio page group the UI is in +// /// +// UiDisplayMode CurrentDisplayMode; + +// /// +// /// The AV page mangagers that have been used, to keep them alive for later +// /// +// Dictionary PageManagers = new Dictionary(); + +// /// +// /// Current page manager running for a source +// /// +// PageManager CurrentSourcePageManager; + +// /// +// /// Will auto-timeout a power off +// /// +// CTimer PowerOffTimer; + +// /// +// /// Constructor +// /// +// public EssentialsPresentationPanelAvFunctionsDriver(PanelDriverBase parent, +// CrestronTouchpanelPropertiesConfig config) +// : base(parent.TriList) +// { +// Config = config; +// Parent = parent; + +// ActivityFooterSrl = new SubpageReferenceList(TriList, 15022, 3, 3, 3); +// //SetupActivityFooterWhenRoomOff(); + +// ShowVolumeGauge = true; + +// // One-second pulse extender for volume gauge +// VolumeGaugeFeedback = new BoolFeedbackPulseExtender(1500); +// VolumeGaugeFeedback.Feedback +// .LinkInputSig(TriList.BooleanInput[UIBoolJoin.VolumeGaugePopupVisible]); + +// VolumeButtonsPopupFeedback = new BoolFeedbackPulseExtender(4000); +// VolumeButtonsPopupFeedback.Feedback +// .LinkInputSig(TriList.BooleanInput[UIBoolJoin.VolumeButtonPopupVisible]); + +// PowerOffTimeout = 30000; + +// SourcesSrl = new SubpageReferenceList(TriList, 3200, 3, 3, 3); + +// TriList.StringInput[UIStringJoin.StartActivityText].StringValue = +// "Tap an activity to begin"; + +// // Sharing mode things +// TriList.SetSigFalseAction(UIBoolJoin.ToggleSharingModePress, ToggleSharingModePressed); + +// TriList.SetSigFalseAction(UIBoolJoin.Display1AudioButtonPressAndFb, Display1AudioPress); +// TriList.SetSigFalseAction(UIBoolJoin.Display1ControlButtonPress, Display1ControlPress); +// TriList.SetSigTrueAction(UIBoolJoin.Display1SelectPressAndFb, Display1Press); + +// TriList.SetSigFalseAction(UIBoolJoin.Display2AudioButtonPressAndFb, Display2AudioPress); +// TriList.SetSigFalseAction(UIBoolJoin.Display2ControlButtonPress, Display2ControlPress); +// TriList.SetSigTrueAction(UIBoolJoin.Display2SelectPressAndFb, Display2Press); +// } + +// /// +// /// +// /// +// public override void Show() +// { +// TriList.BooleanInput[UIBoolJoin.TopBarHabaneroVisible].BoolValue = true; +// TriList.BooleanInput[UIBoolJoin.ActivityFooterVisible].BoolValue = true; + +// // Default to showing rooms/sources now. +// ShowMode(UiDisplayMode.PresentationMode); + +// // Attach actions +// TriList.SetSigFalseAction(UIBoolJoin.VolumeButtonPopupPress, VolumeButtonsTogglePress); + +// //Interlocked modals +// TriList.SetSigFalseAction(UIBoolJoin.InterlockedModalClosePress, HideCurrentInterlockedModal); +// TriList.SetSigFalseAction(UIBoolJoin.HelpPress, () => +// { +// string message = null; +// var room = DeviceManager.GetDeviceForKey(Config.DefaultRoomKey) +// as EssentialsPresentationRoom; +// if (room != null) +// message = room.Config.HelpMessage; +// else +// message = "Sorry, no help message available. No room connected."; +// TriList.StringInput[UIStringJoin.HelpMessage].StringValue = message; +// ShowInterlockedModal(UIBoolJoin.HelpPageVisible); +// }); + +// TriList.SetSigFalseAction(UIBoolJoin.HeaderRoomButtonPress, () => +// ShowInterlockedModal(UIBoolJoin.RoomHeaderPageVisible)); + +// TriList.SetSigFalseAction(UIBoolJoin.FIXFIX_HeaderGearButtonPress_FIXFIX, () => +// ShowInterlockedModal(UIBoolJoin.VolumesPageVisible)); + +// // power-related functions +// // Note: some of these are not directly-related to the huddle space UI, but are held over +// // in case +// TriList.SetSigFalseAction(UIBoolJoin.ShowPowerOffPress, PowerButtonPressed); +// TriList.SetSigFalseAction(UIBoolJoin.PowerOffCancelPress, CancelPowerOff); +// TriList.SetSigFalseAction(UIBoolJoin.PowerOffConfirmPress, FinishPowerOff); +// TriList.SetSigFalseAction(UIBoolJoin.PowerOffMorePress, () => +// { +// CancelPowerOffTimer(); +// TriList.BooleanInput[UIBoolJoin.PowerOffStep1Visible].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.PowerOffStep2Visible].BoolValue = true; +// }); +// TriList.SetSigFalseAction(UIBoolJoin.AllRoomsOffPress, () => +// { +// EssentialsHuddleSpaceRoom.AllRoomsOff(); +// CancelPowerOff(); +// }); + +// SetupActivityFooterWhenRoomOff(); + +// base.Show(); +// } + +// /// +// /// +// /// +// public override void Hide() +// { +// var tl = TriList.BooleanInput; +// HideAndClearCurrentDisplayModeSigsInUse(); +// tl[UIBoolJoin.TopBarHabaneroVisible].BoolValue = false; +// tl[UIBoolJoin.ActivityFooterVisible].BoolValue = false; +// tl[UIBoolJoin.StartPageVisible].BoolValue = false; +// tl[UIBoolJoin.TapToBeginVisible].BoolValue = false; +// tl[UIBoolJoin.ToggleSharingModeVisible].BoolValue = false; +// tl[UIBoolJoin.SourceStagingBarVisible].BoolValue = false; +// if (IsSharingModeAdvanced) +// tl[UIBoolJoin.DualDisplayPageVisible].BoolValue = false; +// else +// tl[UIBoolJoin.SelectASourceVisible].BoolValue = false; + +// VolumeButtonsPopupFeedback.ClearNow(); +// CancelPowerOff(); + +// base.Hide(); +// } + +// /// +// /// +// /// +// void ShowCurrentSharingMode() +// { +// var tlb = TriList.BooleanInput; +// tlb[UIBoolJoin.ToggleSharingModeVisible].BoolValue = true; +// tlb[UIBoolJoin.SourceStagingBarVisible].BoolValue = true; +// if (IsSharingModeAdvanced) +// { +// tlb[UIBoolJoin.DualDisplayPageVisible].BoolValue = true; +// TriList.StringInput[UIStringJoin.Display1TitleLabel].StringValue = +// (CurrentRoom.Displays[1] as IKeyName).Name; +// TriList.StringInput[UIStringJoin.Display2TitleLabel].StringValue = +// (CurrentRoom.Displays[2] as IKeyName).Name; +// } +// else +// tlb[UIBoolJoin.SelectASourceVisible].BoolValue = true; +// } + +// /// +// /// +// /// +// void HideCurrentSharingMode() +// { +// var tl = TriList.BooleanInput; +// tl[UIBoolJoin.ToggleSharingModeVisible].BoolValue = false; +// tl[UIBoolJoin.SourceStagingBarVisible].BoolValue = false; +// tl[UIBoolJoin.DualDisplayPageVisible].BoolValue = false; +// tl[UIBoolJoin.SelectASourceVisible].BoolValue = false; +// } + +// /// +// /// Shows the various "modes" that this driver controls. Presentation, Setup page +// /// +// /// +// public void ShowMode(UiDisplayMode mode) +// { +// //Clear whatever is showing now. +// HideAndClearCurrentDisplayModeSigsInUse(); +// CurrentDisplayMode = mode; +// switch (mode) +// { +// case UiDisplayMode.PresentationMode: +// // show start page or staging... +// if (CurrentRoom.OnFeedback.BoolValue) +// { +// //TriList.BooleanInput[UIBoolJoin.StagingPageVisible].BoolValue = true; +// TriList.BooleanInput[UIBoolJoin.TapToBeginVisible].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; +// } +// else +// { +// TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = true; +// TriList.BooleanInput[UIBoolJoin.TapToBeginVisible].BoolValue = true; +// TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; +// } +// // Date/time +// if (Config.ShowDate && Config.ShowTime) +// { +// TriList.BooleanInput[UIBoolJoin.DateAndTimeVisible].BoolValue = true; +// TriList.BooleanInput[UIBoolJoin.DateOnlyVisible].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.TimeOnlyVisible].BoolValue = false; +// } +// else +// { +// TriList.BooleanInput[UIBoolJoin.DateAndTimeVisible].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.DateOnlyVisible].BoolValue = Config.ShowDate; +// TriList.BooleanInput[UIBoolJoin.TimeOnlyVisible].BoolValue = Config.ShowTime; +// } + +// //TriList.SetSigFalseAction(UIBoolJoin.ToggleSharingModePress, ToggleSharingModePressed); + +// ShowCurrentDisplayModeSigsInUse(); +// break; +// } +// } + +// /// +// /// +// /// +// void SetupSourceList() +// { +// // get the source list config and set up the source list +// var config = ConfigReader.ConfigObject.SourceLists; +// if (config.ContainsKey(CurrentRoom.SourceListKey)) +// { +// var srcList = config[CurrentRoom.SourceListKey] +// .Values.ToList().OrderBy(s => s.Order); +// // Setup sources list +// uint i = 1; // counter for UI list +// foreach (var srcConfig in srcList) +// { +// if (!srcConfig.IncludeInSourceList) // Skip sources marked this way +// continue; + +// var sourceKey = srcConfig.SourceKey; +// var actualSource = DeviceManager.GetDeviceForKey(sourceKey) as Device; +// if (actualSource == null) +// { +// Debug.Console(0, "Cannot assign missing source '{0}' to source UI list", +// srcConfig.SourceKey); +// continue; +// } +// var localSrcItem = srcConfig; // lambda scope below +// var localIndex = i; +// SourcesSrl.GetBoolFeedbackSig(i, 1).UserObject = new Action(b => +// { +// if (b) return; +// if (LastSelectedSourceSig != null) +// LastSelectedSourceSig.BoolValue = false; +// LastSelectedSourceSig = SourcesSrl.BoolInputSig(localIndex, 1); +// LastSelectedSourceSig.BoolValue = true; +// if (IsSharingModeAdvanced) +// { +// PendingSource = localSrcItem; +// } +// else +// { +// CurrentRoom.RouteSourceToAllDestinations(localSrcItem); +// } +// }); +// SourcesSrl.StringInputSig(i, 1).StringValue = srcConfig.PreferredName; +// i++; +// } +// var count = (ushort)(i-1); +// SourcesSrl.Count = count; +// TriList.BooleanInput[UIBoolJoin.StagingPageAdditionalArrowsVisible].BoolValue = +// count >= Config.SourcesOverflowCount; + +// _CurrentRoom.CurrentDisplay1SourceChange += _CurrentRoom_CurrentDisplay1SourceChange; +// _CurrentRoom.CurrentDisplay2SourceChange += _CurrentRoom_CurrentDisplay2SourceChange; +// } +// } + +// /// +// /// +// /// +// void ToggleSharingModePressed() +// { +// if (CurrentSourcePageManager != null) +// CurrentSourcePageManager.Hide(); +// HideCurrentSharingMode(); +// IsSharingModeAdvanced = !IsSharingModeAdvanced; +// TriList.BooleanInput[UIBoolJoin.ToggleSharingModePress].BoolValue = IsSharingModeAdvanced; +// ShowCurrentSharingMode(); +// } + +// ///// +// ///// +// ///// +// //void EnableAppropriateDisplayButtons() +// //{ +// // if (LastSelectedSourceSig != null) +// // LastSelectedSourceSig.BoolValue = false; +// //} + +// public void Display1Press() +// { +// CurrentRoom.SourceToDisplay1(PendingSource); +// } + +// public void Display1AudioPress() +// { + +// } + + +// public void Display1ControlPress() +// { +// var uiDev = CurrentRoom.Display1SourceInfo.SourceDevice as IUiDisplayInfo; +// ShowSource(uiDev); +// } + +// public void Display2Press() +// { +// CurrentRoom.SourceToDisplay2(PendingSource); +// } + +// public void Display2AudioPress() +// { + +// } + +// public void Display2ControlPress() +// { +// var uiDev = CurrentRoom.Display2SourceInfo.SourceDevice as IUiDisplayInfo; +// ShowSource(uiDev); +// } + +// /// +// /// When the room is off, set the footer SRL +// /// +// void SetupActivityFooterWhenRoomOff() +// { +// ActivityFooterSrl.Clear(); +// ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, 0, +// b => { if (!b) ShareButtonPressed(); })); +// // only show phone call when there's a dialer present +// ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, 1, +// b => { })); +// ActivityFooterSrl.Count = (ushort)(CurrentRoom.HasAudioDialer ? 2 : 1); +// TriList.UShortInput[UIUshortJoin.PresentationStagingCaretMode].UShortValue = +// (ushort)(CurrentRoom.HasAudioDialer ? 1 : 0); +// } + +// /// +// /// Sets up the footer SRL for when the room is on +// /// +// void SetupActivityFooterWhenRoomOn() +// { +// ActivityFooterSrl.Clear(); +// ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, +// 0, null)); +// if (CurrentRoom.HasAudioDialer) +// { +// ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, +// 1, b => { })); +// ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(3, ActivityFooterSrl, +// 3, b => { if (!b) PowerButtonPressed(); })); +// ActivityFooterSrl.Count = 3; +// TriList.UShortInput[UIUshortJoin.PresentationStagingCaretMode].UShortValue = 2; +// EndMeetingButtonSig = ActivityFooterSrl.BoolInputSig(3, 1); +// } +// else +// { +// ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, +// 3, b => { if (!b) PowerButtonPressed(); })); +// ActivityFooterSrl.Count = 2; +// TriList.UShortInput[UIUshortJoin.PresentationStagingCaretMode].UShortValue = 1; +// EndMeetingButtonSig = ActivityFooterSrl.BoolInputSig(2, 1); +// } +// } + +// /// +// /// Attached to activity list share button +// /// +// void ShareButtonPressed() +// { +// ShareButtonSig = ActivityFooterSrl.BoolInputSig(1, 1); +// if (!_CurrentRoom.OnFeedback.BoolValue) +// { +// ShareButtonSig.BoolValue = true; +// TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = false; +// ShowCurrentSharingMode(); +// } +// } + +// uint CurrentInterlockedModalJoin; + +// void ShowInterlockedModal(uint join) +// { +// if (CurrentInterlockedModalJoin == join) +// HideCurrentInterlockedModal(); +// else +// { +// TriList.BooleanInput[UIBoolJoin.HelpPageVisible].BoolValue = join == UIBoolJoin.HelpPageVisible; +// TriList.BooleanInput[UIBoolJoin.RoomHeaderPageVisible].BoolValue = join == UIBoolJoin.RoomHeaderPageVisible; +// TriList.BooleanInput[UIBoolJoin.VolumesPageVisible].BoolValue = join == UIBoolJoin.VolumesPageVisible; +// CurrentInterlockedModalJoin = join; +// } +// } + +// void HideCurrentInterlockedModal() +// { +// TriList.BooleanInput[CurrentInterlockedModalJoin].BoolValue = false; +// CurrentInterlockedModalJoin = 0; +// } + +// /// +// /// Shows all sigs that are in CurrentDisplayModeSigsInUse +// /// +// void ShowCurrentDisplayModeSigsInUse() +// { +// foreach (var sig in CurrentDisplayModeSigsInUse) +// sig.BoolValue = true; +// } + +// /// +// /// Hides all CurrentDisplayModeSigsInUse sigs and clears the array +// /// +// void HideAndClearCurrentDisplayModeSigsInUse() +// { +// foreach (var sig in CurrentDisplayModeSigsInUse) +// sig.BoolValue = false; +// CurrentDisplayModeSigsInUse.Clear(); +// } + +// /// +// /// Send the UI back depending on location, not used in huddle UI +// /// +// public override void BackButtonPressed() +// { +// switch (CurrentDisplayMode) +// { +// case UiDisplayMode.PresentationMode: +// //CancelReturnToSourceTimer(); +// BackToHome(); +// break; +// } +// } + +// /// +// /// +// /// +// void BackToHome() +// { +// Hide(); +// Parent.Show(); +// } + +// /// +// /// Loads the appropriate Sigs into CurrentDisplayModeSigsInUse and shows them +// /// +// void ShowCurrentSource() +// { +// if (CurrentRoom.CurrentSingleSourceInfo == null) +// return; +// var uiDev = CurrentRoom.CurrentSingleSourceInfo.SourceDevice as IUiDisplayInfo; +// ShowSource(uiDev); +// } + +// void ShowSource(IUiDisplayInfo uiDev) +// { +// PageManager pm = null; +// // If we need a page manager, get an appropriate one +// if (uiDev != null) +// { +// TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; +// if (IsSharingModeAdvanced) +// { +// TriList.BooleanInput[UIBoolJoin.SourceBackgroundOverlayVisible].BoolValue = true; +// TriList.SetSigFalseAction(UIBoolJoin.SourceBackgroundOverlayClosePress, new Action(() => +// { +// TriList.BooleanInput[UIBoolJoin.SourceBackgroundOverlayVisible].BoolValue = false; +// if (CurrentSourcePageManager != null) +// CurrentSourcePageManager.Hide(); +// })); +// } + + +// // Got an existing page manager, get it +// if (PageManagers.ContainsKey(uiDev)) +// pm = PageManagers[uiDev]; +// // Otherwise make an apporiate one +// else if (uiDev is ISetTopBoxControls) +// pm = new SetTopBoxThreePanelPageManager(uiDev as ISetTopBoxControls, TriList); +// else if (uiDev is IDiscPlayerControls) +// pm = new DiscPlayerMediumPageManager(uiDev as IDiscPlayerControls, TriList); +// else +// pm = new DefaultPageManager(uiDev, TriList); +// PageManagers[uiDev] = pm; +// CurrentSourcePageManager = pm; +// pm.Show(); +// } +// } + +// /// +// /// +// /// +// public void PowerButtonPressed() +// { +// if (!CurrentRoom.OnFeedback.BoolValue) +// return; +// EndMeetingButtonSig.BoolValue = true; +// ShareButtonSig.BoolValue = false; +// // Timeout or button 1 press will shut down +// var modal = new ModalDialog(TriList); +// uint time = 60000; +// uint seconds = time / 1000; +// var message = string.Format("Meeting will end in {0} seconds", seconds); +// modal.PresentModalDialog(2, "End Meeting", "Power", message, +// "End Meeting Now", "Cancel", true, true, +// but => +// { +// EndMeetingButtonSig.BoolValue = false; +// if (but != 2) +// { +// CurrentRoom.RouteSourceToAllDestinations(null); +// } +// else +// ShareButtonSig.BoolValue = true; // restore Share fb +// }); +// } + +// void CancelPowerOffTimer() +// { +// if (PowerOffTimer != null) +// { +// PowerOffTimer.Stop(); +// PowerOffTimer = null; +// } +// } + +// /// +// /// Runs the power off function on the current room +// /// +// public void FinishPowerOff() +// { +// if (CurrentRoom == null) +// return; +// CurrentRoom.RunRouteAction("roomOff"); +// CancelPowerOff(); +// } + +// /// +// /// Hides power off pages and stops timer +// /// +// void CancelPowerOff() +// { +// CancelPowerOffTimer(); +// TriList.BooleanInput[UIBoolJoin.PowerOffStep1Visible].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.PowerOffStep2Visible].BoolValue = false; +// } + +// /// +// /// +// /// +// void VolumeButtonsTogglePress() +// { +// if (VolumeButtonsPopupFeedback.BoolValue) +// VolumeButtonsPopupFeedback.ClearNow(); +// else +// { +// // Trigger the popup +// VolumeButtonsPopupFeedback.BoolValue = true; +// VolumeButtonsPopupFeedback.BoolValue = false; +// } +// } + +// /// +// /// +// /// +// /// +// public void VolumeUpPress(bool state) +// { +// // extend timeouts +// if (ShowVolumeGauge) +// VolumeGaugeFeedback.BoolValue = state; +// VolumeButtonsPopupFeedback.BoolValue = state; +// if (CurrentRoom.CurrentVolumeControls != null) +// CurrentRoom.CurrentVolumeControls.VolumeUp(state); +// } + +// /// +// /// +// /// +// /// +// public void VolumeDownPress(bool state) +// { +// // extend timeouts +// if (ShowVolumeGauge) +// VolumeGaugeFeedback.BoolValue = state; +// VolumeButtonsPopupFeedback.BoolValue = state; +// if (CurrentRoom.CurrentVolumeControls != null) +// CurrentRoom.CurrentVolumeControls.VolumeDown(state); +// } + +// /// +// /// Helper for property setter. Sets the panel to the given room, latching up all functionality +// /// +// void SetCurrentRoom(EssentialsPresentationRoom room) +// { +// if (_CurrentRoom == room) return; +// if (_CurrentRoom != null) +// { +// // Disconnect current room +// _CurrentRoom.OnFeedback.OutputChange -= _CurrentRoom_OnFeedback_OutputChange; +// _CurrentRoom.CurrentVolumeDeviceChange -= this._CurrentRoom_CurrentAudioDeviceChange; +// ClearAudioDeviceConnections(); +// _CurrentRoom.CurrentSingleSourceChange -= this._CurrentRoom_SourceInfoChange; +// DisconnectSource(_CurrentRoom.CurrentSingleSourceInfo); +// } +// _CurrentRoom = room; + +// if (_CurrentRoom != null) +// { +// if (IsSharingModeAdvanced) +// {} // add stuff here +// else +// SetupSourceList(); +// TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = _CurrentRoom.Name; + +// // Link up all the change events from the room +// _CurrentRoom.OnFeedback.OutputChange += _CurrentRoom_OnFeedback_OutputChange; +// _CurrentRoom.CurrentVolumeDeviceChange += _CurrentRoom_CurrentAudioDeviceChange; +// RefreshAudioDeviceConnections(); +// _CurrentRoom.CurrentSingleSourceChange += _CurrentRoom_SourceInfoChange; +// RefreshSourceInfo(); +// } +// else +// { +// // Clear sigs that need to be +// TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = "Select a room"; +// } +// } + +// /// +// /// Hides source for provided source info +// /// +// /// +// void DisconnectSource(SourceListItem previousInfo) +// { +// if (previousInfo == null) return; + +// // Hide whatever is showing +// if (IsVisible) +// { +// if (CurrentSourcePageManager != null) +// { +// CurrentSourcePageManager.Hide(); +// CurrentSourcePageManager = null; +// } +// } + +// if (previousInfo == null) return; +// var previousDev = previousInfo.SourceDevice; + +// // device type interfaces +// if (previousDev is ISetTopBoxControls) +// (previousDev as ISetTopBoxControls).UnlinkButtons(TriList); +// // common interfaces +// if (previousDev is IChannel) +// (previousDev as IChannel).UnlinkButtons(TriList); +// if (previousDev is IColor) +// (previousDev as IColor).UnlinkButtons(TriList); +// if (previousDev is IDPad) +// (previousDev as IDPad).UnlinkButtons(TriList); +// if (previousDev is IDvr) +// (previousDev as IDvr).UnlinkButtons(TriList); +// if (previousDev is INumericKeypad) +// (previousDev as INumericKeypad).UnlinkButtons(TriList); +// if (previousDev is IPower) +// (previousDev as IPower).UnlinkButtons(TriList); +// if (previousDev is ITransport) +// (previousDev as ITransport).UnlinkButtons(TriList); +// //if (previousDev is IRadio) +// // (previousDev as IRadio).UnlinkButtons(this); +// } + +// /// +// /// Refreshes and shows the room's current source +// /// +// void RefreshSourceInfo() +// { +// var routeInfo = CurrentRoom.CurrentSingleSourceInfo; +// // This will show off popup too +// if (this.IsVisible) +// ShowCurrentSource(); + +// if (routeInfo == null)// || !CurrentRoom.OnFeedback.BoolValue) +// { +// // Check for power off and insert "Room is off" +// TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "Room is off"; +// TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Power"; +// this.Hide(); +// Parent.Show(); +// return; +// } +// else if (CurrentRoom.CurrentSingleSourceInfo != null) +// { +// TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = routeInfo.PreferredName; +// TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = routeInfo.Icon; // defaults to "blank" +// } +// else +// { +// TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "---"; +// TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Blank"; +// } - // Connect controls - if (routeInfo.SourceDevice != null) - ConnectControlDeviceMethods(routeInfo.SourceDevice); - } +// // Connect controls +// if (routeInfo.SourceDevice != null) +// ConnectControlDeviceMethods(routeInfo.SourceDevice); +// } - /// - /// Attach the source to the buttons and things - /// - void ConnectControlDeviceMethods(Device dev) - { - if(dev is ISetTopBoxControls) - (dev as ISetTopBoxControls).LinkButtons(TriList); - if (dev is IChannel) - (dev as IChannel).LinkButtons(TriList); - if (dev is IColor) - (dev as IColor).LinkButtons(TriList); - if (dev is IDPad) - (dev as IDPad).LinkButtons(TriList); - if (dev is IDvr) - (dev as IDvr).LinkButtons(TriList); - if (dev is INumericKeypad) - (dev as INumericKeypad).LinkButtons(TriList); - if (dev is IPower) - (dev as IPower).LinkButtons(TriList); - if (dev is ITransport) - (dev as ITransport).LinkButtons(TriList); - //if (dev is IRadio) - // (dev as IRadio).LinkButtons(this); // +++++++++++++ Make part of this into page manager +// /// +// /// Attach the source to the buttons and things +// /// +// void ConnectControlDeviceMethods(Device dev) +// { +// if(dev is ISetTopBoxControls) +// (dev as ISetTopBoxControls).LinkButtons(TriList); +// if (dev is IChannel) +// (dev as IChannel).LinkButtons(TriList); +// if (dev is IColor) +// (dev as IColor).LinkButtons(TriList); +// if (dev is IDPad) +// (dev as IDPad).LinkButtons(TriList); +// if (dev is IDvr) +// (dev as IDvr).LinkButtons(TriList); +// if (dev is INumericKeypad) +// (dev as INumericKeypad).LinkButtons(TriList); +// if (dev is IPower) +// (dev as IPower).LinkButtons(TriList); +// if (dev is ITransport) +// (dev as ITransport).LinkButtons(TriList); +// //if (dev is IRadio) +// // (dev as IRadio).LinkButtons(this); // +++++++++++++ Make part of this into page manager - //if (dev is ICustomFunctions) - //{ - // var custBridge = (dev as ICustomFunctions).GetCustomBridge(); - // custBridge.Link(this.Remote); - } +// //if (dev is ICustomFunctions) +// //{ +// // var custBridge = (dev as ICustomFunctions).GetCustomBridge(); +// // custBridge.Link(this.Remote); +// } - /// - /// Detaches the buttons and feedback from the room's current audio device - /// - void ClearAudioDeviceConnections() - { - TriList.ClearBoolSigAction(UIBoolJoin.VolumeUpPress); - TriList.ClearBoolSigAction(UIBoolJoin.VolumeDownPress); - TriList.ClearBoolSigAction(UIBoolJoin.Volume1ProgramMutePressAndFB); +// /// +// /// Detaches the buttons and feedback from the room's current audio device +// /// +// void ClearAudioDeviceConnections() +// { +// TriList.ClearBoolSigAction(UIBoolJoin.VolumeUpPress); +// TriList.ClearBoolSigAction(UIBoolJoin.VolumeDownPress); +// TriList.ClearBoolSigAction(UIBoolJoin.Volume1ProgramMutePressAndFB); - var fDev = CurrentRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; - if (fDev != null) - { - TriList.ClearUShortSigAction(UIUshortJoin.VolumeSlider1Value); - fDev.VolumeLevelFeedback.UnlinkInputSig( - TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); - } - } +// var fDev = CurrentRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; +// if (fDev != null) +// { +// TriList.ClearUShortSigAction(UIUshortJoin.VolumeSlider1Value); +// fDev.VolumeLevelFeedback.UnlinkInputSig( +// TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); +// } +// } - /// - /// Attaches the buttons and feedback to the room's current audio device - /// - void RefreshAudioDeviceConnections() - { - var dev = CurrentRoom.CurrentVolumeControls; - if (dev != null) // connect buttons - { - TriList.SetBoolSigAction(UIBoolJoin.VolumeUpPress, VolumeUpPress); - TriList.SetBoolSigAction(UIBoolJoin.VolumeDownPress, VolumeDownPress); - TriList.SetSigFalseAction(UIBoolJoin.Volume1ProgramMutePressAndFB, dev.MuteToggle); - } +// /// +// /// Attaches the buttons and feedback to the room's current audio device +// /// +// void RefreshAudioDeviceConnections() +// { +// var dev = CurrentRoom.CurrentVolumeControls; +// if (dev != null) // connect buttons +// { +// TriList.SetBoolSigAction(UIBoolJoin.VolumeUpPress, VolumeUpPress); +// TriList.SetBoolSigAction(UIBoolJoin.VolumeDownPress, VolumeDownPress); +// TriList.SetSigFalseAction(UIBoolJoin.Volume1ProgramMutePressAndFB, dev.MuteToggle); +// } - var fbDev = dev as IBasicVolumeWithFeedback; - if (fbDev == null) // this should catch both IBasicVolume and IBasicVolumeWithFeeback - TriList.UShortInput[UIUshortJoin.VolumeSlider1Value].UShortValue = 0; - else - { - // slider - TriList.SetUShortSigAction(UIUshortJoin.VolumeSlider1Value, fbDev.SetVolume); - // feedbacks - fbDev.MuteFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.Volume1ProgramMutePressAndFB]); - fbDev.VolumeLevelFeedback.LinkInputSig( - TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); - } - } +// var fbDev = dev as IBasicVolumeWithFeedback; +// if (fbDev == null) // this should catch both IBasicVolume and IBasicVolumeWithFeeback +// TriList.UShortInput[UIUshortJoin.VolumeSlider1Value].UShortValue = 0; +// else +// { +// // slider +// TriList.SetUShortSigAction(UIUshortJoin.VolumeSlider1Value, fbDev.SetVolume); +// // feedbacks +// fbDev.MuteFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.Volume1ProgramMutePressAndFB]); +// fbDev.VolumeLevelFeedback.LinkInputSig( +// TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); +// } +// } - /// - /// Handler for when the room's volume control device changes - /// - void _CurrentRoom_CurrentAudioDeviceChange(object sender, VolumeDeviceChangeEventArgs args) - { - if (args.Type == ChangeType.WillChange) - ClearAudioDeviceConnections(); - else // did change - RefreshAudioDeviceConnections(); - } +// /// +// /// Handler for when the room's volume control device changes +// /// +// void _CurrentRoom_CurrentAudioDeviceChange(object sender, VolumeDeviceChangeEventArgs args) +// { +// if (args.Type == ChangeType.WillChange) +// ClearAudioDeviceConnections(); +// else // did change +// RefreshAudioDeviceConnections(); +// } - /// - /// For room on/off changes - /// - void _CurrentRoom_OnFeedback_OutputChange(object sender, EventArgs e) - { - var value = _CurrentRoom.OnFeedback.BoolValue; - TriList.BooleanInput[UIBoolJoin.RoomIsOn].BoolValue = value; - if (value) - { - SetupActivityFooterWhenRoomOn(); - TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = false; - } - else - { - HideCurrentSharingMode(); - SetupActivityFooterWhenRoomOff(); - TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = true; - if (LastSelectedSourceSig != null) - { - LastSelectedSourceSig.BoolValue = false; - LastSelectedSourceSig = null; - } - PendingSource = null; - } +// /// +// /// For room on/off changes +// /// +// void _CurrentRoom_OnFeedback_OutputChange(object sender, EventArgs e) +// { +// var value = _CurrentRoom.OnFeedback.BoolValue; +// TriList.BooleanInput[UIBoolJoin.RoomIsOn].BoolValue = value; +// if (value) +// { +// SetupActivityFooterWhenRoomOn(); +// TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = false; +// } +// else +// { +// HideCurrentSharingMode(); +// SetupActivityFooterWhenRoomOff(); +// TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = true; +// if (LastSelectedSourceSig != null) +// { +// LastSelectedSourceSig.BoolValue = false; +// LastSelectedSourceSig = null; +// } +// PendingSource = null; +// } - if (_CurrentRoom.HasAudioDialer) - { - TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = value; - TriList.BooleanInput[UIBoolJoin.VolumeSingleMute1Visible].BoolValue = false; - } - else - { - TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.VolumeSingleMute1Visible].BoolValue = value; - } - } +// if (_CurrentRoom.HasAudioDialer) +// { +// TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = value; +// TriList.BooleanInput[UIBoolJoin.VolumeSingleMute1Visible].BoolValue = false; +// } +// else +// { +// TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.VolumeSingleMute1Visible].BoolValue = value; +// } +// } - /// - /// Handles source change - /// - void _CurrentRoom_SourceInfoChange(EssentialsRoomBase room, - SourceListItem info, ChangeType change) - { - if (change == ChangeType.WillChange) - DisconnectSource(info); - else - RefreshSourceInfo(); - } +// /// +// /// Handles source change +// /// +// void _CurrentRoom_SourceInfoChange(EssentialsRoomBase room, +// SourceListItem info, ChangeType change) +// { +// if (change == ChangeType.WillChange) +// DisconnectSource(info); +// else +// RefreshSourceInfo(); +// } - /// - /// - /// - void _CurrentRoom_CurrentDisplay1SourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) - { - if (type == ChangeType.DidChange) - { - var isSource = info != null; - TriList.BooleanInput[UIBoolJoin.Display1SelectPressAndFb].BoolValue = isSource; - TriList.StringInput[UIStringJoin.Display1SourceLabel].StringValue = - isSource ? info.PreferredName : ""; - if (!isSource) // return if no source - { - TriList.BooleanInput[UIBoolJoin.Display1AudioButtonEnable].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.Display1ControlButtonEnable].BoolValue = false; - return; - } - // enable audio and control buttons - var devConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key == info.SourceKey); - TriList.BooleanInput[UIBoolJoin.Display1AudioButtonEnable].BoolValue = - ConfigPropertiesHelpers.GetHasAudio(devConfig); - TriList.BooleanInput[UIBoolJoin.Display1ControlButtonEnable].BoolValue = - ConfigPropertiesHelpers.GetHasControls(devConfig); - } - } +// /// +// /// +// /// +// void _CurrentRoom_CurrentDisplay1SourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) +// { +// if (type == ChangeType.DidChange) +// { +// var isSource = info != null; +// TriList.BooleanInput[UIBoolJoin.Display1SelectPressAndFb].BoolValue = isSource; +// TriList.StringInput[UIStringJoin.Display1SourceLabel].StringValue = +// isSource ? info.PreferredName : ""; +// if (!isSource) // return if no source +// { +// TriList.BooleanInput[UIBoolJoin.Display1AudioButtonEnable].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.Display1ControlButtonEnable].BoolValue = false; +// return; +// } +// // enable audio and control buttons +// var devConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key == info.SourceKey); +// TriList.BooleanInput[UIBoolJoin.Display1AudioButtonEnable].BoolValue = +// ConfigPropertiesHelpers.GetHasAudio(devConfig); +// TriList.BooleanInput[UIBoolJoin.Display1ControlButtonEnable].BoolValue = +// ConfigPropertiesHelpers.GetHasControls(devConfig); +// } +// } - /// - /// - /// - void _CurrentRoom_CurrentDisplay2SourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) - { - if (type == ChangeType.DidChange) - { - var isSource = info != null; - TriList.BooleanInput[UIBoolJoin.Display2SelectPressAndFb].BoolValue = isSource; - TriList.StringInput[UIStringJoin.Display2SourceLabel].StringValue = - isSource ? info.PreferredName : ""; - if (!isSource) - { - TriList.BooleanInput[UIBoolJoin.Display2AudioButtonEnable].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.Display2ControlButtonEnable].BoolValue = false; - return; - } - // enable audio and control buttons - var devConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key == info.SourceKey); - TriList.BooleanInput[UIBoolJoin.Display2AudioButtonEnable].BoolValue = - ConfigPropertiesHelpers.GetHasAudio(devConfig); - TriList.BooleanInput[UIBoolJoin.Display2ControlButtonEnable].BoolValue = - ConfigPropertiesHelpers.GetHasControls(devConfig); - } - } +// /// +// /// +// /// +// void _CurrentRoom_CurrentDisplay2SourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) +// { +// if (type == ChangeType.DidChange) +// { +// var isSource = info != null; +// TriList.BooleanInput[UIBoolJoin.Display2SelectPressAndFb].BoolValue = isSource; +// TriList.StringInput[UIStringJoin.Display2SourceLabel].StringValue = +// isSource ? info.PreferredName : ""; +// if (!isSource) +// { +// TriList.BooleanInput[UIBoolJoin.Display2AudioButtonEnable].BoolValue = false; +// TriList.BooleanInput[UIBoolJoin.Display2ControlButtonEnable].BoolValue = false; +// return; +// } +// // enable audio and control buttons +// var devConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key == info.SourceKey); +// TriList.BooleanInput[UIBoolJoin.Display2AudioButtonEnable].BoolValue = +// ConfigPropertiesHelpers.GetHasAudio(devConfig); +// TriList.BooleanInput[UIBoolJoin.Display2ControlButtonEnable].BoolValue = +// ConfigPropertiesHelpers.GetHasControls(devConfig); +// } +// } - } -} \ No newline at end of file +// } +//} \ No newline at end of file diff --git a/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs index 99803f26..9a2b7a31 100644 --- a/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs +++ b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs @@ -6,6 +6,7 @@ using Crestron.SimplSharpPro.UI; using PepperDash.Core; using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.SmartObjects; using PepperDash.Essentials.Core.PageManagers; @@ -163,7 +164,7 @@ namespace PepperDash.Essentials get { if (_TechDriver == null) - _TechDriver = new PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver(TriList, CurrentRoom.Config.Tech); + _TechDriver = new PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver(TriList, CurrentRoom.PropertiesConfig.Tech); return _TechDriver; } } @@ -219,9 +220,7 @@ namespace PepperDash.Essentials return; } - var roomConf = CurrentRoom.Config; - - TriList.SetString(UIStringJoin.CurrentRoomName, CurrentRoom.Name); + var roomConf = CurrentRoom.PropertiesConfig; if (Config.HeaderStyle.ToLower() == CrestronTouchpanelPropertiesConfig.Habanero) { @@ -480,7 +479,7 @@ namespace PepperDash.Essentials TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = true; // Run default source when room is off and share is pressed if (!CurrentRoom.OnFeedback.BoolValue) - CurrentRoom.RunDefaultRoute(); + CurrentRoom.RunDefaultPresentRoute(); } @@ -592,7 +591,7 @@ namespace PepperDash.Essentials EndMeetingButtonSig.BoolValue = true; ShareButtonSig.BoolValue = false; - if (CurrentRoom.ShutdownType == eShutdownType.Manual) + if (CurrentRoom.ShutdownType == eShutdownType.Manual || CurrentRoom.ShutdownType == eShutdownType.Vacancy) { PowerDownModal = new ModalDialog(TriList); var message = string.Format("Meeting will end in {0} seconds", CurrentRoom.ShutdownPromptSeconds); @@ -723,63 +722,62 @@ namespace PepperDash.Essentials CurrentRoom.CurrentVolumeControls.VolumeDown(state); } - /// - /// Helper for property setter. Sets the panel to the given room, latching up all functionality - /// - void SetCurrentRoom(EssentialsHuddleSpaceRoom room) - { - if (_CurrentRoom == room) return; - // Disconnect current (probably never called) - if (_CurrentRoom != null) - { - // Disconnect current room - _CurrentRoom.CurrentVolumeDeviceChange -= this.CurrentRoom_CurrentAudioDeviceChange; - ClearAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange -= this.CurrentRoom_SourceInfoChange; - DisconnectSource(_CurrentRoom.CurrentSourceInfo); + + /// + /// Helper for property setter. Sets the panel to the given room, latching up all functionality + /// + public void RefreshCurrentRoom(EssentialsHuddleSpaceRoom room) + { + if (_CurrentRoom != null) + { + // Disconnect current room + _CurrentRoom.CurrentVolumeDeviceChange -= this.CurrentRoom_CurrentAudioDeviceChange; + ClearAudioDeviceConnections(); + _CurrentRoom.CurrentSingleSourceChange -= this.CurrentRoom_SourceInfoChange; + DisconnectSource(_CurrentRoom.CurrentSourceInfo); _CurrentRoom.ShutdownPromptTimer.HasStarted -= ShutdownPromptTimer_HasStarted; _CurrentRoom.ShutdownPromptTimer.HasFinished -= ShutdownPromptTimer_HasFinished; _CurrentRoom.ShutdownPromptTimer.WasCancelled -= ShutdownPromptTimer_WasCancelled; - _CurrentRoom.OnFeedback.OutputChange += CurrentRoom_OnFeedback_OutputChange; + _CurrentRoom.OnFeedback.OutputChange -= CurrentRoom_OnFeedback_OutputChange; _CurrentRoom.IsWarmingUpFeedback.OutputChange -= CurrentRoom_IsWarmingFeedback_OutputChange; _CurrentRoom.IsCoolingDownFeedback.OutputChange -= IsCoolingDownFeedback_OutputChange; } - _CurrentRoom = room; + _CurrentRoom = room; - if (_CurrentRoom != null) - { - // get the source list config and set up the source list - var config = ConfigReader.ConfigObject.SourceLists; - if (config.ContainsKey(_CurrentRoom.SourceListKey)) - { - var srcList = config[_CurrentRoom.SourceListKey]; - // Setup sources list - uint i = 1; // counter for UI list - foreach (var kvp in srcList) - { - var srcConfig = kvp.Value; - if (!srcConfig.IncludeInSourceList) // Skip sources marked this way - continue; + if (_CurrentRoom != null) + { + // get the source list config and set up the source list + var config = ConfigReader.ConfigObject.SourceLists; + if (config.ContainsKey(_CurrentRoom.SourceListKey)) + { + var srcList = config[_CurrentRoom.SourceListKey]; + // Setup sources list + uint i = 1; // counter for UI list + foreach (var kvp in srcList) + { + var srcConfig = kvp.Value; + if (!srcConfig.IncludeInSourceList) // Skip sources marked this way + continue; - var actualSource = DeviceManager.GetDeviceForKey(srcConfig.SourceKey) as Device; - if (actualSource == null) - { - Debug.Console(1, "Cannot assign missing source '{0}' to source UI list", - srcConfig.SourceKey); - continue; - } - var routeKey = kvp.Key; + var actualSource = DeviceManager.GetDeviceForKey(srcConfig.SourceKey) as Device; + if (actualSource == null) + { + Debug.Console(1, "Cannot assign missing source '{0}' to source UI list", + srcConfig.SourceKey); + continue; + } + var routeKey = kvp.Key; var item = new SubpageReferenceListSourceItem(i++, SourcesSrl, srcConfig, b => { if (!b) UiSelectSource(routeKey); }); SourcesSrl.AddItem(item); // add to the SRL item.RegisterForSourceChange(_CurrentRoom); - } + } SourcesSrl.Count = (ushort)(i - 1); - } + } // Name and logo - TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = _CurrentRoom.Name; + TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = _CurrentRoom.Name; if (_CurrentRoom.LogoUrl == null) { TriList.BooleanInput[UIBoolJoin.LogoDefaultVisible].BoolValue = true; @@ -803,97 +801,40 @@ namespace PepperDash.Essentials _CurrentRoom.IsWarmingUpFeedback.OutputChange += CurrentRoom_IsWarmingFeedback_OutputChange; _CurrentRoom.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange; - _CurrentRoom.CurrentVolumeDeviceChange += CurrentRoom_CurrentAudioDeviceChange; - RefreshAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange += CurrentRoom_SourceInfoChange; - RefreshSourceInfo(); + _CurrentRoom.CurrentVolumeDeviceChange += CurrentRoom_CurrentAudioDeviceChange; + RefreshAudioDeviceConnections(); + _CurrentRoom.CurrentSingleSourceChange += CurrentRoom_SourceInfoChange; + RefreshSourceInfo(); (Parent as EssentialsPanelMainInterfaceDriver).HeaderDriver.SetupHeaderButtons(this, CurrentRoom); } - else - { - // Clear sigs that need to be - TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = "Select a room"; - } + else + { + // Clear sigs that need to be + TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = "Select a room"; + } + } + + void SetCurrentRoom(EssentialsHuddleSpaceRoom room) + { + if (_CurrentRoom == room) return; + // Disconnect current (probably never called) + + room.ConfigChanged -= room_ConfigChanged; + room.ConfigChanged += room_ConfigChanged; + + RefreshCurrentRoom(room); } - //void SetupHeaderButtons() - //{ - // HeaderButtonsAreSetUp = false; - - // TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, true); - - // var roomConf = CurrentRoom.Config; - - // // Gear - // TriList.SetString(UIStringJoin.HeaderButtonIcon5, "Gear"); - // TriList.SetSigHeldAction(UIBoolJoin.HeaderIcon5Press, 2000, - // ShowTech, - // null, - // () => - // { - // if (CurrentRoom.OnFeedback.BoolValue) - // PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.VolumesPageVisible); - // else - // PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.VolumesPagePowerOffVisible); - // }); - // TriList.SetSigFalseAction(UIBoolJoin.TechExitButton, () => - // PopupInterlock.HideAndClear()); - - // // Help button and popup - // if (CurrentRoom.Config.Help != null) - // { - // TriList.SetString(UIStringJoin.HelpMessage, roomConf.Help.Message); - // TriList.SetBool(UIBoolJoin.HelpPageShowCallButtonVisible, roomConf.Help.ShowCallButton); - // TriList.SetString(UIStringJoin.HelpPageCallButtonText, roomConf.Help.CallButtonText); - // if (roomConf.Help.ShowCallButton) - // TriList.SetSigFalseAction(UIBoolJoin.HelpPageShowCallButtonPress, () => { }); // ************ FILL IN - // else - // TriList.ClearBoolSigAction(UIBoolJoin.HelpPageShowCallButtonPress); - // } - // else // older config - // { - // TriList.SetString(UIStringJoin.HelpMessage, CurrentRoom.Config.HelpMessage); - // TriList.SetBool(UIBoolJoin.HelpPageShowCallButtonVisible, false); - // TriList.SetString(UIStringJoin.HelpPageCallButtonText, null); - // TriList.ClearBoolSigAction(UIBoolJoin.HelpPageShowCallButtonPress); - // } - // TriList.SetString(UIStringJoin.HeaderButtonIcon4, "Help"); - // TriList.SetSigFalseAction(UIBoolJoin.HeaderIcon4Press, () => - // { - // string message = null; - // var room = DeviceManager.GetDeviceForKey(Config.DefaultRoomKey) - // as EssentialsHuddleSpaceRoom; - // if (room != null) - // message = room.Config.HelpMessage; - // else - // message = "Sorry, no help message available. No room connected."; - // //TriList.StringInput[UIStringJoin.HelpMessage].StringValue = message; - // PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.HelpPageVisible); - // }); - // uint nextJoin = 3953; - - // //// Calendar button - // //if (_CurrentRoom.ScheduleSource != null) - // //{ - // // TriList.SetString(nextJoin, "Calendar"); - // // TriList.SetSigFalseAction(nextJoin, CalendarPress); - - // // nextJoin--; - // //} - - // //nextJoin--; - - // // blank any that remain - // for (var i = nextJoin; i > 3950; i--) - // { - // TriList.SetString(i, "Blank"); - // TriList.SetSigFalseAction(i, () => { }); - // } - - // HeaderButtonsAreSetUp = true; - //} - + /// + /// Fires when room config of current room has changed. Meant to refresh room values to propegate any updates to UI + /// + /// + /// + void room_ConfigChanged(object sender, EventArgs e) + { + RefreshCurrentRoom(_CurrentRoom); + } /// /// For room on/off changes diff --git a/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddleTechPageDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddleTechPageDriver.cs index 02221d52..b8e1dfd6 100644 --- a/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddleTechPageDriver.cs +++ b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddleTechPageDriver.cs @@ -8,7 +8,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using PepperDash.Essentials; -using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.SmartObjects; using PepperDash.Essentials.Core.Touchpanels.Keyboards; using PepperDash.Essentials.Devices.Displays; diff --git a/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs index 8644d24d..4b1f2188 100644 --- a/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs +++ b/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs @@ -1,1533 +1,1403 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.UI; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.SmartObjects; -using PepperDash.Essentials.Core.PageManagers; -using PepperDash.Essentials.Room.Config; -using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials -{ - /// - /// - /// - public class EssentialsHuddleVtc1PanelAvFunctionsDriver : PanelDriverBase, IAVWithVCDriver - { - CrestronTouchpanelPropertiesConfig Config; - - public enum UiDisplayMode - { - Presentation, AudioSetup, Call, Start - } - - /// - /// Whether volume ramping from this panel will show the volume - /// gauge popup. - /// - public bool ShowVolumeGauge { get; set; } - - /// - /// - /// - public uint PowerOffTimeout { get; set; } - - /// - /// - /// - public string DefaultRoomKey { get; set; } - - - /// - /// - /// - public EssentialsHuddleVtc1Room CurrentRoom - { - get { return _CurrentRoom; } - set - { - SetCurrentRoom(value); - } - } - EssentialsHuddleVtc1Room _CurrentRoom; - - /// - /// For hitting feedbacks - /// - BoolInputSig CallButtonSig; - BoolInputSig ShareButtonSig; - BoolInputSig EndMeetingButtonSig; - - BoolFeedback CallSharingInfoVisibleFeedback; - - /// - /// The parent driver for this - /// - PanelDriverBase Parent; - - /// - /// All children attached to this driver. For hiding and showing as a group. - /// - List ChildDrivers = new List(); - - List CurrentDisplayModeSigsInUse = new List(); - - //// Important smart objects - - /// - /// Smart Object 3200 - /// - SubpageReferenceList SourceStagingSrl; - - /// - /// Smart Object 15022 - /// - SubpageReferenceList ActivityFooterSrl; - - /// - /// - /// - public SubpageReferenceList MeetingOrContactMethodModalSrl { get; set; } - - /// - /// The list of buttons on the header. Managed with visibility only - /// - //SmartObjectHeaderButtonList HeaderButtonsList; - - /// - /// The AV page mangagers that have been used, to keep them alive for later - /// - Dictionary PageManagers = new Dictionary(); - - /// - /// Current page manager running for a source - /// - PageManager CurrentSourcePageManager; - - /// - /// Will auto-timeout a power off - /// - CTimer PowerOffTimer; - - /// - /// - /// - ModalDialog PowerDownModal; - - /// - /// - /// - //ModalDialog WarmingCoolingModal; - - /// - /// Represents - /// - public JoinedSigInterlock PopupInterlock { get; private set; } - - /// - /// Interlock for various source, camera, call control bars. The bar above the activity footer. This is also - /// used to show start page - /// - JoinedSigInterlock StagingBarInterlock; - - /// - /// Interlocks the various call-related subpages - /// - JoinedSigInterlock CallPagesInterlock; - - /// - /// The Video codec driver - /// - PepperDash.Essentials.UIDrivers.VC.EssentialsVideoCodecUiDriver VCDriver; - - /// - /// The driver for the tech page. Lazy getter for memory usage - /// - PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver TechDriver - { - get - { - if (_TechDriver == null) - _TechDriver = new PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver(TriList, CurrentRoom.Config.Tech); - return _TechDriver; - } - } - PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver _TechDriver; - - /// - /// Controls timeout of notification ribbon timer - /// - CTimer RibbonTimer; - - /// - /// The keyboard - /// - public PepperDash.Essentials.Core.Touchpanels.Keyboards.HabaneroKeyboardController Keyboard { get; private set; } - - /// - /// The mode showing. Presentation or call. - /// - UiDisplayMode CurrentMode = UiDisplayMode.Start; - - CTimer NextMeetingTimer; - - /// - /// Tracks the last meeting that was cancelled - /// - string LastMeetingDismissedId; - - /// - /// Constructor - /// - public EssentialsHuddleVtc1PanelAvFunctionsDriver(PanelDriverBase parent, CrestronTouchpanelPropertiesConfig config) - : base(parent.TriList) - { - Config = config; - Parent = parent; - - PopupInterlock = new JoinedSigInterlock(TriList); - StagingBarInterlock = new JoinedSigInterlock(TriList); - CallPagesInterlock = new JoinedSigInterlock(TriList); - - SourceStagingSrl = new SubpageReferenceList(TriList, UISmartObjectJoin.SourceStagingSRL, 3, 3, 3); - - ActivityFooterSrl = new SubpageReferenceList(TriList, UISmartObjectJoin.ActivityFooterSRL, 3, 3, 3); - CallButtonSig = ActivityFooterSrl.BoolInputSig(2, 1); - ShareButtonSig = ActivityFooterSrl.BoolInputSig(1, 1); - EndMeetingButtonSig = ActivityFooterSrl.BoolInputSig(3, 1); - - MeetingOrContactMethodModalSrl = new SubpageReferenceList(TriList, UISmartObjectJoin.MeetingListSRL, 3, 3, 5); - - - // buttons are added in SetCurrentRoom - //HeaderButtonsList = new SmartObjectHeaderButtonList(TriList.SmartObjects[UISmartObjectJoin.HeaderButtonList]); - - SetupActivityFooterWhenRoomOff(); - - ShowVolumeGauge = true; - Keyboard = new PepperDash.Essentials.Core.Touchpanels.Keyboards.HabaneroKeyboardController(TriList); - } - - /// - /// Add a video codec driver to this - /// - /// - public void SetVideoCodecDriver(PepperDash.Essentials.UIDrivers.VC.EssentialsVideoCodecUiDriver vcd) - { - VCDriver = vcd; - } - - /// - /// - /// - public override void Show() - { - if (CurrentRoom == null) - { - Debug.Console(1, "ERROR: AVUIFunctionsDriver, Cannot show. No room assigned"); - return; - } - - var roomConf = CurrentRoom.Config; - - TriList.SetString(UIStringJoin.CurrentRoomName, CurrentRoom.Name); - - if (Config.HeaderStyle.ToLower() == CrestronTouchpanelPropertiesConfig.Habanero) - { - TriList.SetSigFalseAction(UIBoolJoin.HeaderRoomButtonPress, () => - PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.RoomHeaderPageVisible)); - } - else if (Config.HeaderStyle.ToLower() == CrestronTouchpanelPropertiesConfig.Verbose) - { - // room name on join 1, concat phone and sip on join 2, no button method - //var addr = roomConf.Addresses; - //if (addr == null) // protect from missing values by using default empties - // addr = new EssentialsRoomAddressPropertiesConfig(); - //// empty string when either missing, pipe when both showing - //TriList.SetString(UIStringJoin.RoomAddressPipeText, - // (string.IsNullOrEmpty(addr.PhoneNumber.Trim()) - // || string.IsNullOrEmpty(addr.SipAddress.Trim())) ? "" : " | "); - //TriList.SetString(UIStringJoin.RoomPhoneText, addr.PhoneNumber); - //TriList.SetString(UIStringJoin.RoomSipText, addr.SipAddress); - } - - TriList.SetBool(UIBoolJoin.DateAndTimeVisible, Config.ShowDate && Config.ShowTime); - TriList.SetBool(UIBoolJoin.DateOnlyVisible, Config.ShowDate && !Config.ShowTime); - TriList.SetBool(UIBoolJoin.TimeOnlyVisible, !Config.ShowDate && Config.ShowTime); - - TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, true); - - TriList.SetBool(UIBoolJoin.ActivityFooterVisible, true); - - // Privacy mute button - TriList.SetSigFalseAction(UIBoolJoin.Volume1SpeechMutePressAndFB, CurrentRoom.PrivacyModeToggle); - CurrentRoom.PrivacyModeIsOnFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.Volume1SpeechMutePressAndFB]); - - // Default to showing rooms/sources now. - if (CurrentRoom.OnFeedback.BoolValue) - { - TriList.SetBool(UIBoolJoin.TapToBeginVisible, false); - SetupActivityFooterWhenRoomOn(); - } - else - { - TriList.SetBool(UIBoolJoin.StartPageVisible, true); - TriList.SetBool(UIBoolJoin.TapToBeginVisible, true); - SetupActivityFooterWhenRoomOff(); - } - ShowCurrentDisplayModeSigsInUse(); - - // *** Header Buttons *** - - // Generic "close" button for popup modals - TriList.SetSigFalseAction(UIBoolJoin.InterlockedModalClosePress, PopupInterlock.HideAndClear); - - // Volume related things - TriList.SetSigFalseAction(UIBoolJoin.VolumeDefaultPress, () => CurrentRoom.SetDefaultLevels()); - TriList.SetString(UIStringJoin.AdvancedVolumeSlider1Text, "Room"); - - //if (TriList is CrestronApp) - // TriList.BooleanInput[UIBoolJoin.GearButtonVisible].BoolValue = false; - //else - // TriList.BooleanInput[UIBoolJoin.GearButtonVisible].BoolValue = true; - - // power-related functions - // Note: some of these are not directly-related to the huddle space UI, but are held over - // in case - TriList.SetSigFalseAction(UIBoolJoin.ShowPowerOffPress, EndMeetingPress); - - TriList.SetSigFalseAction(UIBoolJoin.DisplayPowerTogglePress, () => - { - if (CurrentRoom != null && CurrentRoom.DefaultDisplay is IPower) - (CurrentRoom.DefaultDisplay as IPower).PowerToggle(); - }); - - SetupNextMeetingTimer(); - - base.Show(); - } - - /// - /// Allows PopupInterlock to be toggled if the calls list is already visible, or if the codec is in a call - /// - public void ShowActiveCallsList() - { - TriList.SetBool(UIBoolJoin.CallEndAllConfirmVisible, true); - if(PopupInterlock.CurrentJoin == UIBoolJoin.HeaderActiveCallsListVisible) - PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.HeaderActiveCallsListVisible); - else - { - if((CurrentRoom.ScheduleSource as VideoCodecBase).IsInCall) - PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.HeaderActiveCallsListVisible); - } - } - - /// - /// - /// - void ShowLogo() - { - if (CurrentRoom.LogoUrl == null) - { - TriList.SetBool(UIBoolJoin.LogoDefaultVisible, true); - TriList.SetBool(UIBoolJoin.LogoUrlVisible, false); - } - else - { - TriList.SetBool(UIBoolJoin.LogoDefaultVisible, false); - TriList.SetBool(UIBoolJoin.LogoUrlVisible, true); - TriList.SetString(UIStringJoin.LogoUrl, _CurrentRoom.LogoUrl); - } - } - - /// - /// - /// - void HideLogo() - { - TriList.SetBool(UIBoolJoin.LogoDefaultVisible, false); - TriList.SetBool(UIBoolJoin.LogoUrlVisible, false); - } - - /// - /// - /// - public override void Hide() - { - HideAndClearCurrentDisplayModeSigsInUse(); - TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, false); - TriList.BooleanInput[UIBoolJoin.ActivityFooterVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.TapToBeginVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; - if (NextMeetingTimer != null) - NextMeetingTimer.Stop(); - HideNextMeetingPopup(); - base.Hide(); - } - - /// - /// Reveals a message on the notification ribbon until cleared - /// - /// Text to display - /// Time in ms to display. 0 to keep on screen - public void ShowNotificationRibbon(string message, int timeout) - { - TriList.SetString(UIStringJoin.NotificationRibbonText, message); - TriList.SetBool(UIBoolJoin.NotificationRibbonVisible, true); - if (timeout > 0) - { - if (RibbonTimer != null) - RibbonTimer.Stop(); - RibbonTimer = new CTimer(o => { - TriList.SetBool(UIBoolJoin.NotificationRibbonVisible, false); - RibbonTimer = null; - }, timeout); - } - } - - /// - /// Hides the notification ribbon - /// - public void HideNotificationRibbon() - { - TriList.SetBool(UIBoolJoin.NotificationRibbonVisible, false); - if (RibbonTimer != null) - { - RibbonTimer.Stop(); - RibbonTimer = null; - } - } - - void SetupNextMeetingTimer() - { - var ss = CurrentRoom.ScheduleSource; - if (ss != null) - { - NextMeetingTimer = new CTimer(o => ShowNextMeetingTimerCallback(), null, 0, 60000); - } - } - - /// - /// - /// - void ShowNextMeetingTimerCallback() - { - // Every 60 seconds, refresh the calendar - RefreshMeetingsList(); - // check meetings list for the closest, joinable meeting - var ss = CurrentRoom.ScheduleSource; - var meetings = ss.CodecSchedule.Meetings; - - if (meetings.Count > 0) - { - // If the room is off pester the user - // If the room is on, and the meeting is joinable - // and the LastMeetingDismissed != this meeting - - var lastMeetingDismissed = meetings.FirstOrDefault(m => m.Id == LastMeetingDismissedId); - Debug.Console(0, "*#* Room on: {0}, lastMeetingDismissedId: {1} {2} *#*", - CurrentRoom.OnFeedback.BoolValue, - LastMeetingDismissedId, - lastMeetingDismissed != null ? lastMeetingDismissed.StartTime.ToShortTimeString() : ""); - - var meeting = meetings.LastOrDefault(m => m.Joinable); - if (CurrentRoom.OnFeedback.BoolValue - && lastMeetingDismissed == meeting) - { - return; - } - - LastMeetingDismissedId = null; - // Clear the popup when we run out of meetings - if (meeting == null) - { - HideNextMeetingPopup(); - } - else - { - TriList.SetString(UIStringJoin.MeetingsOrContactMethodListTitleText, "Upcoming meeting"); - TriList.SetString(UIStringJoin.NextMeetingStartTimeText, meeting.StartTime.ToShortTimeString()); - TriList.SetString(UIStringJoin.NextMeetingEndTimeText, meeting.EndTime.ToShortTimeString()); - TriList.SetString(UIStringJoin.NextMeetingTitleText, meeting.Title); - TriList.SetString(UIStringJoin.NextMeetingNameText, meeting.Organizer); - TriList.SetString(UIStringJoin.NextMeetingButtonLabel, "Join"); - TriList.SetSigFalseAction(UIBoolJoin.NextMeetingJoinPress, () => - { - HideNextMeetingPopup(); - PopupInterlock.Hide(); - RoomOnAndDialMeeting(meeting); - }); - TriList.SetString(UIStringJoin.NextMeetingSecondaryButtonLabel, "Show Schedule"); - TriList.SetSigFalseAction(UIBoolJoin.CalendarHeaderButtonPress, () => - { - HideNextMeetingPopup(); - //CalendarPress(); - RefreshMeetingsList(); - PopupInterlock.ShowInterlocked(UIBoolJoin.MeetingsOrContacMethodsListVisible); - }); - var indexOfNext = meetings.IndexOf(meeting) + 1; - - // indexOf = 3, 4 meetings : - if (indexOfNext < meetings.Count) - TriList.SetString(UIStringJoin.NextMeetingFollowingMeetingText, - meetings[indexOfNext].StartTime.ToShortTimeString()); - else - TriList.SetString(UIStringJoin.NextMeetingFollowingMeetingText, "No more meetings today"); - - TriList.SetSigFalseAction(UIBoolJoin.NextMeetingModalClosePress, () => - { - // Mark the meeting to not re-harass the user - if(CurrentRoom.OnFeedback.BoolValue) - LastMeetingDismissedId = meeting.Id; - HideNextMeetingPopup(); - }); - - TriList.SetBool(UIBoolJoin.NextMeetingModalVisible, true); - } - } - } - - /// - /// - /// - void HideNextMeetingPopup() - { - TriList.SetBool(UIBoolJoin.NextMeetingModalVisible, false); - - } - - /// - /// Calendar should only be visible when it's supposed to - /// - public void CalendarPress() - { - //RefreshMeetingsList(); // List should be up-to-date - PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.MeetingsOrContacMethodsListVisible); - } - - /// - /// Dials a meeting after turning on room (if necessary) - /// - void RoomOnAndDialMeeting(Meeting meeting) - { - Action dialAction = () => - { - var d = CurrentRoom.ScheduleSource as VideoCodecBase; - if (d != null) - { - d.Dial(meeting); - LastMeetingDismissedId = meeting.Id; // To prevent prompts for already-joined call - } - }; - if (CurrentRoom.OnFeedback.BoolValue) - dialAction(); - else - { - // Rig a one-time handler to catch when the room is warmed and then dial call - EventHandler oneTimeHandler = null; - oneTimeHandler = (o, a) => - { - if (!CurrentRoom.IsWarmingUpFeedback.BoolValue) - { - CurrentRoom.IsWarmingUpFeedback.OutputChange -= oneTimeHandler; - dialAction(); - } - }; - CurrentRoom.IsWarmingUpFeedback.OutputChange += oneTimeHandler; - ActivityCallButtonPressed(); - } - } - - /// - /// Reveals the tech page and puts away anything that's in the way. - /// - public void ShowTech() - { - PopupInterlock.HideAndClear(); - TechDriver.Show(); - } - - /// - /// When the room is off, set the footer SRL - /// - void SetupActivityFooterWhenRoomOff() - { - ActivityFooterSrl.Clear(); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, 0, - b => { if (!b) ActivityShareButtonPressed(); })); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, 3, - b => { if (!b) ActivityCallButtonPressed(); })); - ActivityFooterSrl.Count = 2; - TriList.SetUshort(UIUshortJoin.PresentationStagingCaretMode, 1); // right one slot - TriList.SetUshort(UIUshortJoin.CallStagingCaretMode, 5); // left one slot - } - - /// - /// Sets up the footer SRL for when the room is on - /// - void SetupActivityFooterWhenRoomOn() - { - ActivityFooterSrl.Clear(); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, 0, - b => { if (!b) ActivityShareButtonPressed(); })); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, 3, - b => { if (!b) ActivityCallButtonPressed(); })); - ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(3, ActivityFooterSrl, 4, - b => { if (!b) EndMeetingPress(); })); - ActivityFooterSrl.Count = 3; - TriList.SetUshort(UIUshortJoin.PresentationStagingCaretMode, 2); // center - TriList.SetUshort(UIUshortJoin.CallStagingCaretMode, 0); // left -2 - } - - /// - /// Single point call for setting the feedbacks on the activity buttons - /// - void SetActivityFooterFeedbacks() - { - CallButtonSig.BoolValue = CurrentMode == UiDisplayMode.Call - && CurrentRoom.ShutdownType == eShutdownType.None; - ShareButtonSig.BoolValue = CurrentMode == UiDisplayMode.Presentation - && CurrentRoom.ShutdownType == eShutdownType.None; - EndMeetingButtonSig.BoolValue = CurrentRoom.ShutdownType != eShutdownType.None; - } - - /// - /// - /// - public void ActivityCallButtonPressed() - { - if (VCDriver.IsVisible) - return; - HideLogo(); - HideNextMeetingPopup(); - TriList.SetBool(UIBoolJoin.StartPageVisible, false); - TriList.SetBool(UIBoolJoin.SourceStagingBarVisible, false); - TriList.SetBool(UIBoolJoin.SelectASourceVisible, false); - if (CurrentSourcePageManager != null) - CurrentSourcePageManager.Hide(); - PowerOnFromCall(); - CurrentMode = UiDisplayMode.Call; - SetActivityFooterFeedbacks(); - VCDriver.Show(); - } - - /// - /// Attached to activity list share button - /// - void ActivityShareButtonPressed() - { - SetupSourceList(); - if (VCDriver.IsVisible) - VCDriver.Hide(); - HideNextMeetingPopup(); - TriList.SetBool(UIBoolJoin.StartPageVisible, false); - TriList.SetBool(UIBoolJoin.CallStagingBarVisible, false); - TriList.SetBool(UIBoolJoin.SourceStagingBarVisible, true); - // Run default source when room is off and share is pressed - if (!CurrentRoom.OnFeedback.BoolValue) - { - if (!CurrentRoom.OnFeedback.BoolValue) - { - // If there's no default, show UI elements - if (!CurrentRoom.RunDefaultPresentRoute()) - TriList.SetBool(UIBoolJoin.SelectASourceVisible, true); - } - } - else // room is on show what's active or select a source if nothing is yet active - { - if(CurrentRoom.CurrentSourceInfo == null || CurrentRoom.CurrentSourceInfoKey == CurrentRoom.DefaultCodecRouteString) - TriList.SetBool(UIBoolJoin.SelectASourceVisible, true); - else if (CurrentSourcePageManager != null) - CurrentSourcePageManager.Show(); - } - CurrentMode = UiDisplayMode.Presentation; - SetupSourceList(); - SetActivityFooterFeedbacks(); - } - - /// - /// Powers up the system to the codec route, if not already on. - /// - void PowerOnFromCall() - { - if (!CurrentRoom.OnFeedback.BoolValue) - { - CurrentRoom.RunDefaultCallRoute(); - } - } - - /// - /// Shows all sigs that are in CurrentDisplayModeSigsInUse - /// - void ShowCurrentDisplayModeSigsInUse() - { - foreach (var sig in CurrentDisplayModeSigsInUse) - sig.BoolValue = true; - } - - /// - /// Hides all CurrentDisplayModeSigsInUse sigs and clears the array - /// - void HideAndClearCurrentDisplayModeSigsInUse() - { - foreach (var sig in CurrentDisplayModeSigsInUse) - sig.BoolValue = false; - CurrentDisplayModeSigsInUse.Clear(); - } - - - /// - /// Loads the appropriate Sigs into CurrentDisplayModeSigsInUse and shows them - /// - void ShowCurrentSource() - { - if (CurrentRoom.CurrentSourceInfo == null) - return; - - if (CurrentRoom.CurrentSourceInfo.SourceDevice == null) - { - TriList.SetBool(UIBoolJoin.SelectASourceVisible, true); - return; - } - - var uiDev = CurrentRoom.CurrentSourceInfo.SourceDevice as IUiDisplayInfo; - PageManager pm = null; - // If we need a page manager, get an appropriate one - if (uiDev != null) - { - TriList.SetBool(UIBoolJoin.SelectASourceVisible, false); - // Got an existing page manager, get it - if (PageManagers.ContainsKey(uiDev)) - pm = PageManagers[uiDev]; - // Otherwise make an apporiate one - else if (uiDev is ISetTopBoxControls) - pm = new SetTopBoxThreePanelPageManager(uiDev as ISetTopBoxControls, TriList); - else if (uiDev is IDiscPlayerControls) - pm = new DiscPlayerMediumPageManager(uiDev as IDiscPlayerControls, TriList); - else - pm = new DefaultPageManager(uiDev, TriList); - PageManagers[uiDev] = pm; - CurrentSourcePageManager = pm; - pm.Show(); - } - } - - /// - /// Called from button presses on source, where We can assume we want - /// to change to the proper screen. - /// - /// The key name of the route to run - void UiSelectSource(string key) - { - // Run the route and when it calls back, show the source - CurrentRoom.RunRouteAction(key, null); - } - - /// - /// - /// - public void EndMeetingPress() - { - if (!CurrentRoom.OnFeedback.BoolValue - || CurrentRoom.ShutdownPromptTimer.IsRunningFeedback.BoolValue) - return; - - CurrentRoom.StartShutdown(eShutdownType.Manual); - } - - /// - /// Puts away modals and things that might be up when call comes in - /// - public void PrepareForCodecIncomingCall() - { - if (PowerDownModal != null && PowerDownModal.ModalIsVisible) - PowerDownModal.CancelDialog(); - PopupInterlock.Hide(); - } - - /// - /// - /// - /// - /// - void ShutdownPromptTimer_HasStarted(object sender, EventArgs e) - { - // Do we need to check where the UI is? No? - var timer = CurrentRoom.ShutdownPromptTimer; - SetActivityFooterFeedbacks(); - - if (CurrentRoom.ShutdownType == eShutdownType.Manual) - { - PowerDownModal = new ModalDialog(TriList); - var message = string.Format("Meeting will end in {0} seconds", CurrentRoom.ShutdownPromptSeconds); - - // Attach timer things to modal - CurrentRoom.ShutdownPromptTimer.TimeRemainingFeedback.OutputChange += ShutdownPromptTimer_TimeRemainingFeedback_OutputChange; - CurrentRoom.ShutdownPromptTimer.PercentFeedback.OutputChange += ShutdownPromptTimer_PercentFeedback_OutputChange; - - // respond to offs by cancelling dialog - var onFb = CurrentRoom.OnFeedback; - EventHandler offHandler = null; - offHandler = (o, a) => - { - if (!onFb.BoolValue) - { - PowerDownModal.HideDialog(); - SetActivityFooterFeedbacks(); - onFb.OutputChange -= offHandler; - } - }; - onFb.OutputChange += offHandler; - - PowerDownModal.PresentModalDialog(2, "End Meeting", "Power", message, "Cancel", "End Meeting Now", true, true, - but => - { - if (but != 2) // any button except for End cancels - timer.Cancel(); - else - timer.Finish(); - }); - } - } - - /// - /// - /// - /// - /// - void ShutdownPromptTimer_HasFinished(object sender, EventArgs e) - { - SetActivityFooterFeedbacks(); - CurrentRoom.ShutdownPromptTimer.TimeRemainingFeedback.OutputChange -= ShutdownPromptTimer_TimeRemainingFeedback_OutputChange; - CurrentRoom.ShutdownPromptTimer.PercentFeedback.OutputChange -= ShutdownPromptTimer_PercentFeedback_OutputChange; - } - - /// - /// - /// - /// - /// - void ShutdownPromptTimer_WasCancelled(object sender, EventArgs e) - { - if (PowerDownModal != null) - PowerDownModal.HideDialog(); - SetActivityFooterFeedbacks(); - - CurrentRoom.ShutdownPromptTimer.TimeRemainingFeedback.OutputChange += ShutdownPromptTimer_TimeRemainingFeedback_OutputChange; - CurrentRoom.ShutdownPromptTimer.PercentFeedback.OutputChange -= ShutdownPromptTimer_PercentFeedback_OutputChange; - } - - /// - /// Event handler for countdown timer on power off modal - /// - void ShutdownPromptTimer_TimeRemainingFeedback_OutputChange(object sender, EventArgs e) - { - - var message = string.Format("Meeting will end in {0} seconds", (sender as StringFeedback).StringValue); - TriList.StringInput[ModalDialog.MessageTextJoin].StringValue = message; - } - - /// - /// Event handler for percentage on power off countdown - /// - void ShutdownPromptTimer_PercentFeedback_OutputChange(object sender, EventArgs e) - { - var value = (ushort)((sender as IntFeedback).UShortValue * 65535 / 100); - TriList.UShortInput[ModalDialog.TimerGaugeJoin].UShortValue = value; - } - - /// - /// - /// - void CancelPowerOffTimer() - { - if (PowerOffTimer != null) - { - PowerOffTimer.Stop(); - PowerOffTimer = null; - } - } - - /// - /// - /// - /// - public void VolumeUpPress(bool state) - { - if (CurrentRoom.CurrentVolumeControls != null) - CurrentRoom.CurrentVolumeControls.VolumeUp(state); - } - - /// - /// - /// - /// - public void VolumeDownPress(bool state) - { - if (CurrentRoom.CurrentVolumeControls != null) - CurrentRoom.CurrentVolumeControls.VolumeDown(state); - } - - /// - /// Helper for property setter. Sets the panel to the given room, latching up all functionality - /// - void SetCurrentRoom(EssentialsHuddleVtc1Room room) - { - if (_CurrentRoom == room) return; - // Disconnect current (probably never called) - if (_CurrentRoom != null) - { - // Disconnect current room - _CurrentRoom.CurrentVolumeDeviceChange -= this.CurrentRoom_CurrentAudioDeviceChange; - ClearAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange -= this.CurrentRoom_SourceInfoChange; - DisconnectSource(_CurrentRoom.CurrentSourceInfo); - _CurrentRoom.ShutdownPromptTimer.HasStarted -= ShutdownPromptTimer_HasStarted; - _CurrentRoom.ShutdownPromptTimer.HasFinished -= ShutdownPromptTimer_HasFinished; - _CurrentRoom.ShutdownPromptTimer.WasCancelled -= ShutdownPromptTimer_WasCancelled; - - _CurrentRoom.OnFeedback.OutputChange += CurrentRoom_OnFeedback_OutputChange; - _CurrentRoom.IsWarmingUpFeedback.OutputChange -= CurrentRoom_IsWarmingFeedback_OutputChange; - _CurrentRoom.IsCoolingDownFeedback.OutputChange -= CurrentRoom_IsCoolingDownFeedback_OutputChange; - _CurrentRoom.InCallFeedback.OutputChange -= CurrentRoom_InCallFeedback_OutputChange; - } - - _CurrentRoom = room; - - if (_CurrentRoom != null) - { - // get the source list config and set up the source list - - SetupSourceList(); - - // Name and logo - TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = _CurrentRoom.Name; - ShowLogo(); - - // Shutdown timer - _CurrentRoom.ShutdownPromptTimer.HasStarted += ShutdownPromptTimer_HasStarted; - _CurrentRoom.ShutdownPromptTimer.HasFinished += ShutdownPromptTimer_HasFinished; - _CurrentRoom.ShutdownPromptTimer.WasCancelled += ShutdownPromptTimer_WasCancelled; - - // Link up all the change events from the room - _CurrentRoom.OnFeedback.OutputChange += CurrentRoom_OnFeedback_OutputChange; - CurrentRoom_SyncOnFeedback(); - _CurrentRoom.IsWarmingUpFeedback.OutputChange += CurrentRoom_IsWarmingFeedback_OutputChange; - _CurrentRoom.IsCoolingDownFeedback.OutputChange += CurrentRoom_IsCoolingDownFeedback_OutputChange; - _CurrentRoom.InCallFeedback.OutputChange += CurrentRoom_InCallFeedback_OutputChange; - - - _CurrentRoom.CurrentVolumeDeviceChange += CurrentRoom_CurrentAudioDeviceChange; - RefreshAudioDeviceConnections(); - _CurrentRoom.CurrentSingleSourceChange += CurrentRoom_SourceInfoChange; - RefreshSourceInfo(); - - (_CurrentRoom.VideoCodec as IHasScheduleAwareness).CodecSchedule.MeetingsListHasChanged += CodecSchedule_MeetingsListHasChanged; - - CallSharingInfoVisibleFeedback = new BoolFeedback(() => _CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue); - _CurrentRoom.VideoCodec.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange; - CallSharingInfoVisibleFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.CallSharedSourceInfoVisible]); - - SetActiveCallListSharingContentStatus(); - - if (_CurrentRoom != null) - _CurrentRoom.CurrentSingleSourceChange += new SourceInfoChangeHandler(CurrentRoom_CurrentSingleSourceChange); - - TriList.SetSigFalseAction(UIBoolJoin.CallStopSharingPress, () => _CurrentRoom.RunRouteAction("codecOsd")); - - (Parent as EssentialsPanelMainInterfaceDriver).HeaderDriver.SetupHeaderButtons(this, CurrentRoom); - } - else - { - // Clear sigs that need to be - TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = "Select a room"; - } - } - - /// - /// - /// - /// - /// - void CurrentRoom_InCallFeedback_OutputChange(object sender, EventArgs e) - { - var inCall = CurrentRoom.InCallFeedback.BoolValue; - if (inCall) - { - // Check if transitioning to in call - and non-sharable source is in use - if (CurrentRoom.CurrentSourceInfo != null && CurrentRoom.CurrentSourceInfo.DisableCodecSharing) - { - Debug.Console(1, CurrentRoom, "Transitioning to in-call, cancelling non-sharable source"); - CurrentRoom.RunRouteAction("codecOsd"); - } - } - - SetupSourceList(); - } - - /// - /// - /// - void SetupSourceList() - { - - var inCall = CurrentRoom.InCallFeedback.BoolValue; - var config = ConfigReader.ConfigObject.SourceLists; - if (config.ContainsKey(_CurrentRoom.SourceListKey)) - { - var srcList = config[_CurrentRoom.SourceListKey].OrderBy(kv => kv.Value.Order); - - // Setup sources list - SourceStagingSrl.Clear(); - uint i = 1; // counter for UI list - foreach (var kvp in srcList) - { - var srcConfig = kvp.Value; - Debug.Console(1, "**** {0}, {1}, {2}, {3}, {4}", srcConfig.PreferredName, srcConfig.IncludeInSourceList, - srcConfig.DisableCodecSharing, inCall, this.CurrentMode); - // Skip sources marked as not included, and filter list of non-sharable sources when in call - // or on share screen - if (!srcConfig.IncludeInSourceList || (inCall && srcConfig.DisableCodecSharing) - || this.CurrentMode == UiDisplayMode.Call && srcConfig.DisableCodecSharing) - { - Debug.Console(1, "Skipping {0}", srcConfig.PreferredName); - continue; - } - - var routeKey = kvp.Key; - var item = new SubpageReferenceListSourceItem(i++, SourceStagingSrl, srcConfig, - b => { if (!b) UiSelectSource(routeKey); }); - SourceStagingSrl.AddItem(item); // add to the SRL - item.RegisterForSourceChange(_CurrentRoom); - } - SourceStagingSrl.Count = (ushort)(i - 1); - } - - } - - /// - /// If the schedule changes, this event will fire - /// - /// - /// - void CodecSchedule_MeetingsListHasChanged(object sender, EventArgs e) - { - RefreshMeetingsList(); - } - - /// - /// Updates the current shared source label on the call list when the source changes - /// - /// - /// - /// - void CurrentRoom_CurrentSingleSourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) - { - if (_CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && _CurrentRoom.CurrentSourceInfo != null) - TriList.StringInput[UIStringJoin.CallSharedSourceNameText].StringValue = _CurrentRoom.CurrentSourceInfo.PreferredName; - } - - /// - /// Fires when the sharing source feedback of the codec changes - /// - /// - /// - void SharingContentIsOnFeedback_OutputChange(object sender, EventArgs e) - { - SetActiveCallListSharingContentStatus(); - } - - /// - /// Sets the values for the text and button visibilty for the active call list source sharing info - /// - void SetActiveCallListSharingContentStatus() - { - CallSharingInfoVisibleFeedback.FireUpdate(); - - string callListSharedSourceLabel; - - if (_CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && _CurrentRoom.CurrentSourceInfo != null) - { - Debug.Console(0, "*#* CurrentRoom.CurrentSourceInfo = {0}", - _CurrentRoom.CurrentSourceInfo != null ? _CurrentRoom.CurrentSourceInfo.SourceKey : "Nada!"); - callListSharedSourceLabel = _CurrentRoom.CurrentSourceInfo.PreferredName; - } - else - callListSharedSourceLabel = "None"; - - TriList.StringInput[UIStringJoin.CallSharedSourceNameText].StringValue = callListSharedSourceLabel; - } - - - ///// - ///// - ///// - //void SetupHeaderButtons() - //{ - // HeaderButtonsAreSetUp = false; - - // TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, true); - - // var roomConf = CurrentRoom.Config; - - // // Gear - // TriList.SetString(UIStringJoin.HeaderButtonIcon5, "Gear"); - // TriList.SetSigHeldAction(UIBoolJoin.HeaderIcon5Press, 2000, - // ShowTech, - // null, - // () => - // { - // if (CurrentRoom.OnFeedback.BoolValue) - // PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.VolumesPageVisible); - // else - // PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.VolumesPagePowerOffVisible); - // }); - // TriList.SetSigFalseAction(UIBoolJoin.TechExitButton, () => - // PopupInterlock.HideAndClear()); - - // // Help button and popup - // if (CurrentRoom.Config.Help != null) - // { - // TriList.SetString(UIStringJoin.HelpMessage, roomConf.Help.Message); - // TriList.SetBool(UIBoolJoin.HelpPageShowCallButtonVisible, roomConf.Help.ShowCallButton); - // TriList.SetString(UIStringJoin.HelpPageCallButtonText, roomConf.Help.CallButtonText); - // if (roomConf.Help.ShowCallButton) - // TriList.SetSigFalseAction(UIBoolJoin.HelpPageShowCallButtonPress, () => { }); // ************ FILL IN - // else - // TriList.ClearBoolSigAction(UIBoolJoin.HelpPageShowCallButtonPress); - // } - // else // older config - // { - // TriList.SetString(UIStringJoin.HelpMessage, CurrentRoom.Config.HelpMessage); - // TriList.SetBool(UIBoolJoin.HelpPageShowCallButtonVisible, false); - // TriList.SetString(UIStringJoin.HelpPageCallButtonText, null); - // TriList.ClearBoolSigAction(UIBoolJoin.HelpPageShowCallButtonPress); - // } - // TriList.SetString(UIStringJoin.HeaderButtonIcon4, "Help"); - // TriList.SetSigFalseAction(UIBoolJoin.HeaderIcon4Press, () => - // { - // string message = null; - // var room = DeviceManager.GetDeviceForKey(Config.DefaultRoomKey) - // as EssentialsHuddleSpaceRoom; - // if (room != null) - // message = room.Config.HelpMessage; - // else - // message = "Sorry, no help message available. No room connected."; - // //TriList.StringInput[UIStringJoin.HelpMessage].StringValue = message; - // PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.HelpPageVisible); - // }); - // uint nextJoin = 3953; - - // // Calendar button - // if (_CurrentRoom.ScheduleSource != null) - // { - // TriList.SetString(nextJoin, "Calendar"); - // TriList.SetSigFalseAction(nextJoin, CalendarPress); - - // nextJoin--; - // } - - // // Call button - // TriList.SetString(nextJoin, "DND"); - // TriList.SetSigFalseAction(nextJoin, ShowActiveCallsList); - // HeaderCallButtonIconSig = TriList.StringInput[nextJoin]; - - // nextJoin--; - - // // blank any that remain - // for (var i = nextJoin; i > 3950; i--) - // { - // TriList.SetString(i, "Blank"); - // TriList.SetSigFalseAction(i, () => { }); - // } - - // TriList.SetSigFalseAction(UIBoolJoin.HeaderCallStatusLabelPress, ShowActiveCallsList); - - // // Set Call Status Subpage Position - - // if (nextJoin == 3951) - // { - // // Set to right position - // TriList.SetBool(UIBoolJoin.HeaderCallStatusLeftPositionVisible, false); - // TriList.SetBool(UIBoolJoin.HeaderCallStatusRightPositionVisible, true); - // } - // else if (nextJoin == 3950) - // { - // // Set to left position - // TriList.SetBool(UIBoolJoin.HeaderCallStatusLeftPositionVisible, true); - // TriList.SetBool(UIBoolJoin.HeaderCallStatusRightPositionVisible, false); - // } - - // HeaderButtonsAreSetUp = true; - - // ComputeHeaderCallStatus(CurrentRoom.VideoCodec); - //} - - ///// - ///// Evaluates the call status and sets the icon mode and text label - ///// - //public void ComputeHeaderCallStatus(VideoCodecBase codec) - //{ - // if (codec == null) - // { - // Debug.Console(1, "ComputeHeaderCallStatus() cannot execute. codec is null"); - // return; - // } - - // if (HeaderCallButtonIconSig == null) - // { - // Debug.Console(1, "ComputeHeaderCallStatus() cannot execute. HeaderCallButtonIconSig is null"); - // return; - // } - - // // Set mode of header button - // if (!codec.IsInCall) - // { - // HeaderCallButtonIconSig.StringValue = "DND"; - // //HeaderCallButton.SetIcon(HeaderListButton.OnHook); - // } - // else if (codec.ActiveCalls.Any(c => c.Type == eCodecCallType.Video)) - // HeaderCallButtonIconSig.StringValue = "Misc-06_Dark"; - // //HeaderCallButton.SetIcon(HeaderListButton.Camera); - // //TriList.SetUshort(UIUshortJoin.CallHeaderButtonMode, 2); - // else - // HeaderCallButtonIconSig.StringValue = "Misc-09_Dark"; - // //HeaderCallButton.SetIcon(HeaderListButton.Phone); - // //TriList.SetUshort(UIUshortJoin.CallHeaderButtonMode, 1); - - // // Set the call status text - // if (codec.ActiveCalls.Count > 0) - // { - // if (codec.ActiveCalls.Count == 1) - // TriList.SetString(UIStringJoin.HeaderCallStatusLabel, "1 Active Call"); - // else if (codec.ActiveCalls.Count > 1) - // TriList.SetString(UIStringJoin.HeaderCallStatusLabel, string.Format("{0} Active Calls", codec.ActiveCalls.Count)); - // } - // else - // TriList.SetString(UIStringJoin.HeaderCallStatusLabel, "No Active Calls"); - //} - - /// - /// - /// - void RefreshMeetingsList() - { - // See if this is helpful or if the callback response in the codec class maybe doesn't come it time? - // Let's build list from event - // CurrentRoom.ScheduleSource.GetSchedule(); - - TriList.SetString(UIStringJoin.MeetingsOrContactMethodListIcon, "Calendar"); - TriList.SetString(UIStringJoin.MeetingsOrContactMethodListTitleText, "Today's Meetings"); - - ushort i = 0; - foreach (var m in CurrentRoom.ScheduleSource.CodecSchedule.Meetings) - { - i++; - MeetingOrContactMethodModalSrl.StringInputSig(i, 1).StringValue = m.StartTime.ToShortTimeString(); - MeetingOrContactMethodModalSrl.StringInputSig(i, 2).StringValue = m.EndTime.ToShortTimeString(); - MeetingOrContactMethodModalSrl.StringInputSig(i, 3).StringValue = m.Title; - MeetingOrContactMethodModalSrl.StringInputSig(i, 4).StringValue = string.Format("
{0}",m.Organizer); - MeetingOrContactMethodModalSrl.StringInputSig(i, 5).StringValue = "Join"; - MeetingOrContactMethodModalSrl.BoolInputSig(i, 2).BoolValue = m.Joinable; - var mm = m; // lambda scope - MeetingOrContactMethodModalSrl.GetBoolFeedbackSig(i, 1).SetSigFalseAction(() => - { - PopupInterlock.Hide(); - ActivityCallButtonPressed(); - var d = CurrentRoom.ScheduleSource as VideoCodecBase; - if (d != null) - RoomOnAndDialMeeting(mm); - }); - } - MeetingOrContactMethodModalSrl.Count = i; - - if (i == 0) // Show item indicating no meetings are booked for rest of day - { - MeetingOrContactMethodModalSrl.Count = 1; - - MeetingOrContactMethodModalSrl.StringInputSig(1, 1).StringValue = string.Empty; - MeetingOrContactMethodModalSrl.StringInputSig(1, 2).StringValue = string.Empty; - MeetingOrContactMethodModalSrl.StringInputSig(1, 3).StringValue = "No Meetings are booked for the remainder of the day."; - MeetingOrContactMethodModalSrl.StringInputSig(1, 4).StringValue = string.Empty; - MeetingOrContactMethodModalSrl.StringInputSig(1, 5).StringValue = string.Empty; - } - } - - /// - /// For room on/off changes - /// - void CurrentRoom_OnFeedback_OutputChange(object sender, EventArgs e) - { - CurrentRoom_SyncOnFeedback(); - } - - /// - /// - /// - void CurrentRoom_SyncOnFeedback() - { - var value = _CurrentRoom.OnFeedback.BoolValue; - TriList.BooleanInput[UIBoolJoin.RoomIsOn].BoolValue = value; - - TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = !value; - - if (value) //ON - { - SetupActivityFooterWhenRoomOn(); - TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = true; - - } - else - { - CurrentMode = UiDisplayMode.Start; - if (VCDriver.IsVisible) - VCDriver.Hide(); - SetupActivityFooterWhenRoomOff(); - ShowLogo(); - SetActivityFooterFeedbacks(); - TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = false; - TriList.BooleanInput[UIBoolJoin.SourceStagingBarVisible].BoolValue = false; - // Clear this so that the pesky meeting warning can resurface every minute when off - LastMeetingDismissedId = null; - } - } - - /// - /// - /// - void CurrentRoom_IsWarmingFeedback_OutputChange(object sender, EventArgs e) - { - if (CurrentRoom.IsWarmingUpFeedback.BoolValue) - { - ShowNotificationRibbon("Room is powering on. Please wait...", 0); - } - else - { - ShowNotificationRibbon("Room is powered on. Welcome.", 2000); - } - } - - /// - /// - /// - /// - /// - void CurrentRoom_IsCoolingDownFeedback_OutputChange(object sender, EventArgs e) - { - if (CurrentRoom.IsCoolingDownFeedback.BoolValue) - { - ShowNotificationRibbon("Room is powering off. Please wait.", 0); - } - else - { - HideNotificationRibbon(); - } - } - - /// - /// Hides source for provided source info - /// - /// - void DisconnectSource(SourceListItem previousInfo) - { - if (previousInfo == null) return; - - // Hide whatever is showing - if (IsVisible) - { - if (CurrentSourcePageManager != null) - { - CurrentSourcePageManager.Hide(); - CurrentSourcePageManager = null; - } - } - - if (previousInfo == null) return; - var previousDev = previousInfo.SourceDevice; - - // device type interfaces - if (previousDev is ISetTopBoxControls) - (previousDev as ISetTopBoxControls).UnlinkButtons(TriList); - // common interfaces - if (previousDev is IChannel) - (previousDev as IChannel).UnlinkButtons(TriList); - if (previousDev is IColor) - (previousDev as IColor).UnlinkButtons(TriList); - if (previousDev is IDPad) - (previousDev as IDPad).UnlinkButtons(TriList); - if (previousDev is IDvr) - (previousDev as IDvr).UnlinkButtons(TriList); - if (previousDev is INumericKeypad) - (previousDev as INumericKeypad).UnlinkButtons(TriList); - if (previousDev is IPower) - (previousDev as IPower).UnlinkButtons(TriList); - if (previousDev is ITransport) - (previousDev as ITransport).UnlinkButtons(TriList); - } - - /// - /// Refreshes and shows the room's current source - /// - void RefreshSourceInfo() - { - var routeInfo = CurrentRoom.CurrentSourceInfo; - // This will show off popup too - if (this.IsVisible && !VCDriver.IsVisible) - ShowCurrentSource(); - - if (routeInfo == null)// || !CurrentRoom.OnFeedback.BoolValue) - { - // Check for power off and insert "Room is off" - TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "Room is off"; - TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Power"; - this.Hide(); - Parent.Show(); - return; - } - else if (routeInfo != null) - { - TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = routeInfo.PreferredName; - TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = routeInfo.Icon; // defaults to "blank" - } - else // This never gets hit???!!! - { - TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "---"; - TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Blank"; - } - - // Connect controls - if (routeInfo.SourceDevice != null) - ConnectControlDeviceMethods(routeInfo.SourceDevice); - } - - /// - /// Attach the source to the buttons and things - /// - void ConnectControlDeviceMethods(Device dev) - { - if (dev is ISetTopBoxControls) - (dev as ISetTopBoxControls).LinkButtons(TriList); - if (dev is IChannel) - (dev as IChannel).LinkButtons(TriList); - if (dev is IColor) - (dev as IColor).LinkButtons(TriList); - if (dev is IDPad) - (dev as IDPad).LinkButtons(TriList); - if (dev is IDvr) - (dev as IDvr).LinkButtons(TriList); - if (dev is INumericKeypad) - (dev as INumericKeypad).LinkButtons(TriList); - if (dev is IPower) - (dev as IPower).LinkButtons(TriList); - if (dev is ITransport) - (dev as ITransport).LinkButtons(TriList); - } - - /// - /// Detaches the buttons and feedback from the room's current audio device - /// - void ClearAudioDeviceConnections() - { - TriList.ClearBoolSigAction(UIBoolJoin.VolumeUpPress); - TriList.ClearBoolSigAction(UIBoolJoin.VolumeDownPress); - TriList.ClearBoolSigAction(UIBoolJoin.Volume1ProgramMutePressAndFB); - - var fDev = CurrentRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; - if (fDev != null) - { - TriList.ClearUShortSigAction(UIUshortJoin.VolumeSlider1Value); - fDev.VolumeLevelFeedback.UnlinkInputSig( - TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); - } - } - - /// - /// Attaches the buttons and feedback to the room's current audio device - /// - void RefreshAudioDeviceConnections() - { - var dev = CurrentRoom.CurrentVolumeControls; - if (dev != null) // connect buttons - { - TriList.SetBoolSigAction(UIBoolJoin.VolumeUpPress, VolumeUpPress); - TriList.SetBoolSigAction(UIBoolJoin.VolumeDownPress, VolumeDownPress); - TriList.SetSigFalseAction(UIBoolJoin.Volume1ProgramMutePressAndFB, dev.MuteToggle); - } - - var fbDev = dev as IBasicVolumeWithFeedback; - if (fbDev == null) // this should catch both IBasicVolume and IBasicVolumeWithFeeback - TriList.UShortInput[UIUshortJoin.VolumeSlider1Value].UShortValue = 0; - else - { - // slider - TriList.SetUShortSigAction(UIUshortJoin.VolumeSlider1Value, fbDev.SetVolume); - // feedbacks - fbDev.MuteFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.Volume1ProgramMutePressAndFB]); - fbDev.VolumeLevelFeedback.LinkInputSig( - TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); - } - } - - /// - /// Handler for when the room's volume control device changes - /// - void CurrentRoom_CurrentAudioDeviceChange(object sender, VolumeDeviceChangeEventArgs args) - { - if (args.Type == ChangeType.WillChange) - ClearAudioDeviceConnections(); - else // did change - RefreshAudioDeviceConnections(); - } - - /// - /// Handles source change - /// - void CurrentRoom_SourceInfoChange(EssentialsRoomBase room, - SourceListItem info, ChangeType change) - { - if (change == ChangeType.WillChange) - DisconnectSource(info); - else - RefreshSourceInfo(); - } - } - - /// - /// For hanging off various common AV things that child drivers might need from a parent AV driver - /// - public interface IAVDriver - { - JoinedSigInterlock PopupInterlock { get; } - void ShowNotificationRibbon(string message, int timeout); - void HideNotificationRibbon(); - void ShowTech(); - } - - /// - /// For hanging off various common VC things that child drivers might need from a parent AV driver - /// - public interface IAVWithVCDriver : IAVDriver - { - EssentialsHuddleVtc1Room CurrentRoom { get; } - - PepperDash.Essentials.Core.Touchpanels.Keyboards.HabaneroKeyboardController Keyboard { get; } - /// - /// Exposes the ability to switch into call mode - /// - void ActivityCallButtonPressed(); - /// - /// Allows the codec to trigger the main UI to clear up if call is coming in. - /// - void PrepareForCodecIncomingCall(); - - SubpageReferenceList MeetingOrContactMethodModalSrl { get; } - } -} +using System; +using System.Linq; +using System.Collections.Generic; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.UI; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.SmartObjects; +using PepperDash.Essentials.Core.PageManagers; +using PepperDash.Essentials.Room.Config; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace PepperDash.Essentials +{ + /// + /// + /// + public class EssentialsHuddleVtc1PanelAvFunctionsDriver : PanelDriverBase, IAVWithVCDriver + { + CrestronTouchpanelPropertiesConfig Config; + + public enum UiDisplayMode + { + Presentation, AudioSetup, Call, Start + } + + /// + /// Whether volume ramping from this panel will show the volume + /// gauge popup. + /// + public bool ShowVolumeGauge { get; set; } + + /// + /// + /// + public uint PowerOffTimeout { get; set; } + + /// + /// + /// + public string DefaultRoomKey { get; set; } + + + /// + /// + /// + public EssentialsHuddleVtc1Room CurrentRoom + { + get { return _CurrentRoom; } + set + { + SetCurrentRoom(value); + } + } + EssentialsHuddleVtc1Room _CurrentRoom; + + /// + /// For hitting feedbacks + /// + BoolInputSig CallButtonSig; + BoolInputSig ShareButtonSig; + BoolInputSig EndMeetingButtonSig; + + BoolFeedback CallSharingInfoVisibleFeedback; + + /// + /// The parent driver for this + /// + PanelDriverBase Parent; + + /// + /// All children attached to this driver. For hiding and showing as a group. + /// + List ChildDrivers = new List(); + + List CurrentDisplayModeSigsInUse = new List(); + + //// Important smart objects + + /// + /// Smart Object 3200 + /// + SubpageReferenceList SourceStagingSrl; + + /// + /// Smart Object 15022 + /// + SubpageReferenceList ActivityFooterSrl; + + /// + /// + /// + public SubpageReferenceList MeetingOrContactMethodModalSrl { get; set; } + + /// + /// The list of buttons on the header. Managed with visibility only + /// + //SmartObjectHeaderButtonList HeaderButtonsList; + + /// + /// The AV page mangagers that have been used, to keep them alive for later + /// + Dictionary PageManagers = new Dictionary(); + + /// + /// Current page manager running for a source + /// + PageManager CurrentSourcePageManager; + + /// + /// Will auto-timeout a power off + /// + CTimer PowerOffTimer; + + /// + /// + /// + ModalDialog PowerDownModal; + + /// + /// + /// + //ModalDialog WarmingCoolingModal; + + /// + /// Represents + /// + public JoinedSigInterlock PopupInterlock { get; private set; } + + /// + /// Interlock for various source, camera, call control bars. The bar above the activity footer. This is also + /// used to show start page + /// + JoinedSigInterlock StagingBarInterlock; + + /// + /// Interlocks the various call-related subpages + /// + JoinedSigInterlock CallPagesInterlock; + + /// + /// The Video codec driver + /// + PepperDash.Essentials.UIDrivers.VC.EssentialsVideoCodecUiDriver VCDriver; + + /// + /// The driver for the tech page. Lazy getter for memory usage + /// + PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver TechDriver + { + get + { + if (_TechDriver == null) + _TechDriver = new PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver(TriList, CurrentRoom.PropertiesConfig.Tech); + return _TechDriver; + } + } + PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver _TechDriver; + + /// + /// Controls timeout of notification ribbon timer + /// + CTimer RibbonTimer; + + /// + /// The keyboard + /// + public PepperDash.Essentials.Core.Touchpanels.Keyboards.HabaneroKeyboardController Keyboard { get; private set; } + + /// + /// The mode showing. Presentation or call. + /// + UiDisplayMode CurrentMode = UiDisplayMode.Start; + + CTimer NextMeetingTimer; + + /// + /// Tracks the last meeting that was cancelled + /// + string LastMeetingDismissedId; + + /// + /// Constructor + /// + public EssentialsHuddleVtc1PanelAvFunctionsDriver(PanelDriverBase parent, CrestronTouchpanelPropertiesConfig config) + : base(parent.TriList) + { + Config = config; + Parent = parent; + + PopupInterlock = new JoinedSigInterlock(TriList); + StagingBarInterlock = new JoinedSigInterlock(TriList); + CallPagesInterlock = new JoinedSigInterlock(TriList); + + SourceStagingSrl = new SubpageReferenceList(TriList, UISmartObjectJoin.SourceStagingSRL, 3, 3, 3); + + ActivityFooterSrl = new SubpageReferenceList(TriList, UISmartObjectJoin.ActivityFooterSRL, 3, 3, 3); + CallButtonSig = ActivityFooterSrl.BoolInputSig(2, 1); + ShareButtonSig = ActivityFooterSrl.BoolInputSig(1, 1); + EndMeetingButtonSig = ActivityFooterSrl.BoolInputSig(3, 1); + + MeetingOrContactMethodModalSrl = new SubpageReferenceList(TriList, UISmartObjectJoin.MeetingListSRL, 3, 3, 5); + + + // buttons are added in SetCurrentRoom + //HeaderButtonsList = new SmartObjectHeaderButtonList(TriList.SmartObjects[UISmartObjectJoin.HeaderButtonList]); + + SetupActivityFooterWhenRoomOff(); + + ShowVolumeGauge = true; + Keyboard = new PepperDash.Essentials.Core.Touchpanels.Keyboards.HabaneroKeyboardController(TriList); + } + + /// + /// Add a video codec driver to this + /// + /// + public void SetVideoCodecDriver(PepperDash.Essentials.UIDrivers.VC.EssentialsVideoCodecUiDriver vcd) + { + VCDriver = vcd; + } + + /// + /// + /// + public override void Show() + { + if (CurrentRoom == null) + { + Debug.Console(1, "ERROR: AVUIFunctionsDriver, Cannot show. No room assigned"); + return; + } + + var roomConf = CurrentRoom.PropertiesConfig; + + if (Config.HeaderStyle.ToLower() == CrestronTouchpanelPropertiesConfig.Habanero) + { + TriList.SetSigFalseAction(UIBoolJoin.HeaderRoomButtonPress, () => + PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.RoomHeaderPageVisible)); + } + else if (Config.HeaderStyle.ToLower() == CrestronTouchpanelPropertiesConfig.Verbose) + { + // room name on join 1, concat phone and sip on join 2, no button method + //var addr = roomConf.Addresses; + //if (addr == null) // protect from missing values by using default empties + // addr = new EssentialsRoomAddressPropertiesConfig(); + //// empty string when either missing, pipe when both showing + //TriList.SetString(UIStringJoin.RoomAddressPipeText, + // (string.IsNullOrEmpty(addr.PhoneNumber.Trim()) + // || string.IsNullOrEmpty(addr.SipAddress.Trim())) ? "" : " | "); + //TriList.SetString(UIStringJoin.RoomPhoneText, addr.PhoneNumber); + //TriList.SetString(UIStringJoin.RoomSipText, addr.SipAddress); + } + + TriList.SetBool(UIBoolJoin.DateAndTimeVisible, Config.ShowDate && Config.ShowTime); + TriList.SetBool(UIBoolJoin.DateOnlyVisible, Config.ShowDate && !Config.ShowTime); + TriList.SetBool(UIBoolJoin.TimeOnlyVisible, !Config.ShowDate && Config.ShowTime); + + TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, true); + + TriList.SetBool(UIBoolJoin.ActivityFooterVisible, true); + + // Privacy mute button + TriList.SetSigFalseAction(UIBoolJoin.Volume1SpeechMutePressAndFB, CurrentRoom.PrivacyModeToggle); + CurrentRoom.PrivacyModeIsOnFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.Volume1SpeechMutePressAndFB]); + + // Default to showing rooms/sources now. + if (CurrentRoom.OnFeedback.BoolValue) + { + TriList.SetBool(UIBoolJoin.TapToBeginVisible, false); + SetupActivityFooterWhenRoomOn(); + } + else + { + TriList.SetBool(UIBoolJoin.StartPageVisible, true); + TriList.SetBool(UIBoolJoin.TapToBeginVisible, true); + SetupActivityFooterWhenRoomOff(); + } + ShowCurrentDisplayModeSigsInUse(); + + // *** Header Buttons *** + + // Generic "close" button for popup modals + TriList.SetSigFalseAction(UIBoolJoin.InterlockedModalClosePress, PopupInterlock.HideAndClear); + + // Volume related things + TriList.SetSigFalseAction(UIBoolJoin.VolumeDefaultPress, () => CurrentRoom.SetDefaultLevels()); + TriList.SetString(UIStringJoin.AdvancedVolumeSlider1Text, "Room"); + + //if (TriList is CrestronApp) + // TriList.BooleanInput[UIBoolJoin.GearButtonVisible].BoolValue = false; + //else + // TriList.BooleanInput[UIBoolJoin.GearButtonVisible].BoolValue = true; + + // power-related functions + // Note: some of these are not directly-related to the huddle space UI, but are held over + // in case + TriList.SetSigFalseAction(UIBoolJoin.ShowPowerOffPress, EndMeetingPress); + + TriList.SetSigFalseAction(UIBoolJoin.DisplayPowerTogglePress, () => + { + if (CurrentRoom != null && CurrentRoom.DefaultDisplay is IPower) + (CurrentRoom.DefaultDisplay as IPower).PowerToggle(); + }); + + SetupNextMeetingTimer(); + + base.Show(); + } + + /// + /// Allows PopupInterlock to be toggled if the calls list is already visible, or if the codec is in a call + /// + public void ShowActiveCallsList() + { + TriList.SetBool(UIBoolJoin.CallEndAllConfirmVisible, true); + if(PopupInterlock.CurrentJoin == UIBoolJoin.HeaderActiveCallsListVisible) + PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.HeaderActiveCallsListVisible); + else + { + if((CurrentRoom.ScheduleSource as VideoCodecBase).IsInCall) + PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.HeaderActiveCallsListVisible); + } + } + + /// + /// + /// + void ShowLogo() + { + if (CurrentRoom.LogoUrl == null) + { + TriList.SetBool(UIBoolJoin.LogoDefaultVisible, true); + TriList.SetBool(UIBoolJoin.LogoUrlVisible, false); + } + else + { + TriList.SetBool(UIBoolJoin.LogoDefaultVisible, false); + TriList.SetBool(UIBoolJoin.LogoUrlVisible, true); + TriList.SetString(UIStringJoin.LogoUrl, _CurrentRoom.LogoUrl); + } + } + + /// + /// + /// + void HideLogo() + { + TriList.SetBool(UIBoolJoin.LogoDefaultVisible, false); + TriList.SetBool(UIBoolJoin.LogoUrlVisible, false); + } + + /// + /// + /// + public override void Hide() + { + HideAndClearCurrentDisplayModeSigsInUse(); + TriList.SetBool(UIBoolJoin.TopBarHabaneroDynamicVisible, false); + TriList.BooleanInput[UIBoolJoin.ActivityFooterVisible].BoolValue = false; + TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = false; + TriList.BooleanInput[UIBoolJoin.TapToBeginVisible].BoolValue = false; + TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; + if (NextMeetingTimer != null) + NextMeetingTimer.Stop(); + HideNextMeetingPopup(); + base.Hide(); + } + + /// + /// Reveals a message on the notification ribbon until cleared + /// + /// Text to display + /// Time in ms to display. 0 to keep on screen + public void ShowNotificationRibbon(string message, int timeout) + { + TriList.SetString(UIStringJoin.NotificationRibbonText, message); + TriList.SetBool(UIBoolJoin.NotificationRibbonVisible, true); + if (timeout > 0) + { + if (RibbonTimer != null) + RibbonTimer.Stop(); + RibbonTimer = new CTimer(o => { + TriList.SetBool(UIBoolJoin.NotificationRibbonVisible, false); + RibbonTimer = null; + }, timeout); + } + } + + /// + /// Hides the notification ribbon + /// + public void HideNotificationRibbon() + { + TriList.SetBool(UIBoolJoin.NotificationRibbonVisible, false); + if (RibbonTimer != null) + { + RibbonTimer.Stop(); + RibbonTimer = null; + } + } + + void SetupNextMeetingTimer() + { + var ss = CurrentRoom.ScheduleSource; + if (ss != null) + { + NextMeetingTimer = new CTimer(o => ShowNextMeetingTimerCallback(), null, 0, 60000); + } + } + + /// + /// + /// + void ShowNextMeetingTimerCallback() + { + // Every 60 seconds, refresh the calendar + RefreshMeetingsList(); + // check meetings list for the closest, joinable meeting + var ss = CurrentRoom.ScheduleSource; + var meetings = ss.CodecSchedule.Meetings; + + if (meetings.Count > 0) + { + // If the room is off pester the user + // If the room is on, and the meeting is joinable + // and the LastMeetingDismissed != this meeting + + var lastMeetingDismissed = meetings.FirstOrDefault(m => m.Id == LastMeetingDismissedId); + Debug.Console(0, "*#* Room on: {0}, lastMeetingDismissedId: {1} {2} *#*", + CurrentRoom.OnFeedback.BoolValue, + LastMeetingDismissedId, + lastMeetingDismissed != null ? lastMeetingDismissed.StartTime.ToShortTimeString() : ""); + + var meeting = meetings.LastOrDefault(m => m.Joinable); + if (CurrentRoom.OnFeedback.BoolValue + && lastMeetingDismissed == meeting) + { + return; + } + + LastMeetingDismissedId = null; + // Clear the popup when we run out of meetings + if (meeting == null) + { + HideNextMeetingPopup(); + } + else + { + TriList.SetString(UIStringJoin.MeetingsOrContactMethodListTitleText, "Upcoming meeting"); + TriList.SetString(UIStringJoin.NextMeetingStartTimeText, meeting.StartTime.ToShortTimeString()); + TriList.SetString(UIStringJoin.NextMeetingEndTimeText, meeting.EndTime.ToShortTimeString()); + TriList.SetString(UIStringJoin.NextMeetingTitleText, meeting.Title); + TriList.SetString(UIStringJoin.NextMeetingNameText, meeting.Organizer); + TriList.SetString(UIStringJoin.NextMeetingButtonLabel, "Join"); + TriList.SetSigFalseAction(UIBoolJoin.NextMeetingJoinPress, () => + { + HideNextMeetingPopup(); + PopupInterlock.Hide(); + RoomOnAndDialMeeting(meeting); + }); + TriList.SetString(UIStringJoin.NextMeetingSecondaryButtonLabel, "Show Schedule"); + TriList.SetSigFalseAction(UIBoolJoin.CalendarHeaderButtonPress, () => + { + HideNextMeetingPopup(); + //CalendarPress(); + RefreshMeetingsList(); + PopupInterlock.ShowInterlocked(UIBoolJoin.MeetingsOrContacMethodsListVisible); + }); + var indexOfNext = meetings.IndexOf(meeting) + 1; + + // indexOf = 3, 4 meetings : + if (indexOfNext < meetings.Count) + TriList.SetString(UIStringJoin.NextMeetingFollowingMeetingText, + meetings[indexOfNext].StartTime.ToShortTimeString()); + else + TriList.SetString(UIStringJoin.NextMeetingFollowingMeetingText, "No more meetings today"); + + TriList.SetSigFalseAction(UIBoolJoin.NextMeetingModalClosePress, () => + { + // Mark the meeting to not re-harass the user + if(CurrentRoom.OnFeedback.BoolValue) + LastMeetingDismissedId = meeting.Id; + HideNextMeetingPopup(); + }); + + TriList.SetBool(UIBoolJoin.NextMeetingModalVisible, true); + } + } + } + + /// + /// + /// + void HideNextMeetingPopup() + { + TriList.SetBool(UIBoolJoin.NextMeetingModalVisible, false); + + } + + /// + /// Calendar should only be visible when it's supposed to + /// + public void CalendarPress() + { + //RefreshMeetingsList(); // List should be up-to-date + PopupInterlock.ShowInterlockedWithToggle(UIBoolJoin.MeetingsOrContacMethodsListVisible); + } + + /// + /// Dials a meeting after turning on room (if necessary) + /// + void RoomOnAndDialMeeting(Meeting meeting) + { + Action dialAction = () => + { + var d = CurrentRoom.ScheduleSource as VideoCodecBase; + if (d != null) + { + d.Dial(meeting); + LastMeetingDismissedId = meeting.Id; // To prevent prompts for already-joined call + } + }; + if (CurrentRoom.OnFeedback.BoolValue) + dialAction(); + else + { + // Rig a one-time handler to catch when the room is warmed and then dial call + EventHandler oneTimeHandler = null; + oneTimeHandler = (o, a) => + { + if (!CurrentRoom.IsWarmingUpFeedback.BoolValue) + { + CurrentRoom.IsWarmingUpFeedback.OutputChange -= oneTimeHandler; + dialAction(); + } + }; + CurrentRoom.IsWarmingUpFeedback.OutputChange += oneTimeHandler; + ActivityCallButtonPressed(); + } + } + + /// + /// Reveals the tech page and puts away anything that's in the way. + /// + public void ShowTech() + { + PopupInterlock.HideAndClear(); + TechDriver.Show(); + } + + /// + /// When the room is off, set the footer SRL + /// + void SetupActivityFooterWhenRoomOff() + { + ActivityFooterSrl.Clear(); + ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, 0, + b => { if (!b) ActivityShareButtonPressed(); })); + ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, 3, + b => { if (!b) ActivityCallButtonPressed(); })); + ActivityFooterSrl.Count = 2; + TriList.SetUshort(UIUshortJoin.PresentationStagingCaretMode, 1); // right one slot + TriList.SetUshort(UIUshortJoin.CallStagingCaretMode, 5); // left one slot + } + + /// + /// Sets up the footer SRL for when the room is on + /// + void SetupActivityFooterWhenRoomOn() + { + ActivityFooterSrl.Clear(); + ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(1, ActivityFooterSrl, 0, + b => { if (!b) ActivityShareButtonPressed(); })); + ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(2, ActivityFooterSrl, 3, + b => { if (!b) ActivityCallButtonPressed(); })); + ActivityFooterSrl.AddItem(new SubpageReferenceListActivityItem(3, ActivityFooterSrl, 4, + b => { if (!b) EndMeetingPress(); })); + ActivityFooterSrl.Count = 3; + TriList.SetUshort(UIUshortJoin.PresentationStagingCaretMode, 2); // center + TriList.SetUshort(UIUshortJoin.CallStagingCaretMode, 0); // left -2 + } + + /// + /// Single point call for setting the feedbacks on the activity buttons + /// + void SetActivityFooterFeedbacks() + { + CallButtonSig.BoolValue = CurrentMode == UiDisplayMode.Call + && CurrentRoom.ShutdownType == eShutdownType.None; + ShareButtonSig.BoolValue = CurrentMode == UiDisplayMode.Presentation + && CurrentRoom.ShutdownType == eShutdownType.None; + EndMeetingButtonSig.BoolValue = CurrentRoom.ShutdownType != eShutdownType.None; + } + + /// + /// + /// + public void ActivityCallButtonPressed() + { + if (VCDriver.IsVisible) + return; + HideLogo(); + HideNextMeetingPopup(); + TriList.SetBool(UIBoolJoin.StartPageVisible, false); + TriList.SetBool(UIBoolJoin.SourceStagingBarVisible, false); + TriList.SetBool(UIBoolJoin.SelectASourceVisible, false); + if (CurrentSourcePageManager != null) + CurrentSourcePageManager.Hide(); + PowerOnFromCall(); + CurrentMode = UiDisplayMode.Call; + SetActivityFooterFeedbacks(); + VCDriver.Show(); + } + + /// + /// Attached to activity list share button + /// + void ActivityShareButtonPressed() + { + SetupSourceList(); + if (VCDriver.IsVisible) + VCDriver.Hide(); + HideNextMeetingPopup(); + TriList.SetBool(UIBoolJoin.StartPageVisible, false); + TriList.SetBool(UIBoolJoin.CallStagingBarVisible, false); + TriList.SetBool(UIBoolJoin.SourceStagingBarVisible, true); + // Run default source when room is off and share is pressed + if (!CurrentRoom.OnFeedback.BoolValue) + { + if (!CurrentRoom.OnFeedback.BoolValue) + { + // If there's no default, show UI elements + if (!CurrentRoom.RunDefaultPresentRoute()) + TriList.SetBool(UIBoolJoin.SelectASourceVisible, true); + } + } + else // room is on show what's active or select a source if nothing is yet active + { + if(CurrentRoom.CurrentSourceInfo == null || CurrentRoom.CurrentSourceInfoKey == CurrentRoom.DefaultCodecRouteString) + TriList.SetBool(UIBoolJoin.SelectASourceVisible, true); + else if (CurrentSourcePageManager != null) + CurrentSourcePageManager.Show(); + } + CurrentMode = UiDisplayMode.Presentation; + SetupSourceList(); + SetActivityFooterFeedbacks(); + } + + /// + /// Powers up the system to the codec route, if not already on. + /// + void PowerOnFromCall() + { + if (!CurrentRoom.OnFeedback.BoolValue) + { + CurrentRoom.RunDefaultCallRoute(); + } + } + + /// + /// Shows all sigs that are in CurrentDisplayModeSigsInUse + /// + void ShowCurrentDisplayModeSigsInUse() + { + foreach (var sig in CurrentDisplayModeSigsInUse) + sig.BoolValue = true; + } + + /// + /// Hides all CurrentDisplayModeSigsInUse sigs and clears the array + /// + void HideAndClearCurrentDisplayModeSigsInUse() + { + foreach (var sig in CurrentDisplayModeSigsInUse) + sig.BoolValue = false; + CurrentDisplayModeSigsInUse.Clear(); + } + + + /// + /// Loads the appropriate Sigs into CurrentDisplayModeSigsInUse and shows them + /// + void ShowCurrentSource() + { + if (CurrentRoom.CurrentSourceInfo == null) + return; + + if (CurrentRoom.CurrentSourceInfo.SourceDevice == null) + { + TriList.SetBool(UIBoolJoin.SelectASourceVisible, true); + return; + } + + var uiDev = CurrentRoom.CurrentSourceInfo.SourceDevice as IUiDisplayInfo; + PageManager pm = null; + // If we need a page manager, get an appropriate one + if (uiDev != null) + { + TriList.SetBool(UIBoolJoin.SelectASourceVisible, false); + // Got an existing page manager, get it + if (PageManagers.ContainsKey(uiDev)) + pm = PageManagers[uiDev]; + // Otherwise make an apporiate one + else if (uiDev is ISetTopBoxControls) + pm = new SetTopBoxThreePanelPageManager(uiDev as ISetTopBoxControls, TriList); + else if (uiDev is IDiscPlayerControls) + pm = new DiscPlayerMediumPageManager(uiDev as IDiscPlayerControls, TriList); + else + pm = new DefaultPageManager(uiDev, TriList); + PageManagers[uiDev] = pm; + CurrentSourcePageManager = pm; + pm.Show(); + } + } + + /// + /// Called from button presses on source, where We can assume we want + /// to change to the proper screen. + /// + /// The key name of the route to run + void UiSelectSource(string key) + { + // Run the route and when it calls back, show the source + CurrentRoom.RunRouteAction(key, null); + } + + /// + /// + /// + public void EndMeetingPress() + { + if (!CurrentRoom.OnFeedback.BoolValue + || CurrentRoom.ShutdownPromptTimer.IsRunningFeedback.BoolValue) + return; + + CurrentRoom.StartShutdown(eShutdownType.Manual); + } + + /// + /// Puts away modals and things that might be up when call comes in + /// + public void PrepareForCodecIncomingCall() + { + if (PowerDownModal != null && PowerDownModal.ModalIsVisible) + PowerDownModal.CancelDialog(); + PopupInterlock.Hide(); + } + + /// + /// + /// + /// + /// + void ShutdownPromptTimer_HasStarted(object sender, EventArgs e) + { + // Do we need to check where the UI is? No? + var timer = CurrentRoom.ShutdownPromptTimer; + SetActivityFooterFeedbacks(); + + if (CurrentRoom.ShutdownType == eShutdownType.Manual || CurrentRoom.ShutdownType == eShutdownType.Vacancy) + { + PowerDownModal = new ModalDialog(TriList); + var message = string.Format("Meeting will end in {0} seconds", CurrentRoom.ShutdownPromptSeconds); + + // Attach timer things to modal + CurrentRoom.ShutdownPromptTimer.TimeRemainingFeedback.OutputChange += ShutdownPromptTimer_TimeRemainingFeedback_OutputChange; + CurrentRoom.ShutdownPromptTimer.PercentFeedback.OutputChange += ShutdownPromptTimer_PercentFeedback_OutputChange; + + // respond to offs by cancelling dialog + var onFb = CurrentRoom.OnFeedback; + EventHandler offHandler = null; + offHandler = (o, a) => + { + if (!onFb.BoolValue) + { + PowerDownModal.HideDialog(); + SetActivityFooterFeedbacks(); + onFb.OutputChange -= offHandler; + } + }; + onFb.OutputChange += offHandler; + + PowerDownModal.PresentModalDialog(2, "End Meeting", "Power", message, "Cancel", "End Meeting Now", true, true, + but => + { + if (but != 2) // any button except for End cancels + timer.Cancel(); + else + timer.Finish(); + }); + } + } + + /// + /// + /// + /// + /// + void ShutdownPromptTimer_HasFinished(object sender, EventArgs e) + { + SetActivityFooterFeedbacks(); + CurrentRoom.ShutdownPromptTimer.TimeRemainingFeedback.OutputChange -= ShutdownPromptTimer_TimeRemainingFeedback_OutputChange; + CurrentRoom.ShutdownPromptTimer.PercentFeedback.OutputChange -= ShutdownPromptTimer_PercentFeedback_OutputChange; + } + + /// + /// + /// + /// + /// + void ShutdownPromptTimer_WasCancelled(object sender, EventArgs e) + { + if (PowerDownModal != null) + PowerDownModal.HideDialog(); + SetActivityFooterFeedbacks(); + + CurrentRoom.ShutdownPromptTimer.TimeRemainingFeedback.OutputChange += ShutdownPromptTimer_TimeRemainingFeedback_OutputChange; + CurrentRoom.ShutdownPromptTimer.PercentFeedback.OutputChange -= ShutdownPromptTimer_PercentFeedback_OutputChange; + } + + /// + /// Event handler for countdown timer on power off modal + /// + void ShutdownPromptTimer_TimeRemainingFeedback_OutputChange(object sender, EventArgs e) + { + + var message = string.Format("Meeting will end in {0} seconds", (sender as StringFeedback).StringValue); + TriList.StringInput[ModalDialog.MessageTextJoin].StringValue = message; + } + + /// + /// Event handler for percentage on power off countdown + /// + void ShutdownPromptTimer_PercentFeedback_OutputChange(object sender, EventArgs e) + { + var value = (ushort)((sender as IntFeedback).UShortValue * 65535 / 100); + TriList.UShortInput[ModalDialog.TimerGaugeJoin].UShortValue = value; + } + + /// + /// + /// + void CancelPowerOffTimer() + { + if (PowerOffTimer != null) + { + PowerOffTimer.Stop(); + PowerOffTimer = null; + } + } + + /// + /// + /// + /// + public void VolumeUpPress(bool state) + { + if (CurrentRoom.CurrentVolumeControls != null) + CurrentRoom.CurrentVolumeControls.VolumeUp(state); + } + + /// + /// + /// + /// + public void VolumeDownPress(bool state) + { + if (CurrentRoom.CurrentVolumeControls != null) + CurrentRoom.CurrentVolumeControls.VolumeDown(state); + } + + /// + /// Helper for property setter. Sets the panel to the given room, latching up all functionality + /// + void RefreshCurrentRoom(EssentialsHuddleVtc1Room room) + { + + if (_CurrentRoom != null) + { + // Disconnect current room + _CurrentRoom.CurrentVolumeDeviceChange -= this.CurrentRoom_CurrentAudioDeviceChange; + ClearAudioDeviceConnections(); + _CurrentRoom.CurrentSingleSourceChange -= this.CurrentRoom_SourceInfoChange; + DisconnectSource(_CurrentRoom.CurrentSourceInfo); + _CurrentRoom.ShutdownPromptTimer.HasStarted -= ShutdownPromptTimer_HasStarted; + _CurrentRoom.ShutdownPromptTimer.HasFinished -= ShutdownPromptTimer_HasFinished; + _CurrentRoom.ShutdownPromptTimer.WasCancelled -= ShutdownPromptTimer_WasCancelled; + + _CurrentRoom.OnFeedback.OutputChange -= CurrentRoom_OnFeedback_OutputChange; + _CurrentRoom.IsWarmingUpFeedback.OutputChange -= CurrentRoom_IsWarmingFeedback_OutputChange; + _CurrentRoom.IsCoolingDownFeedback.OutputChange -= CurrentRoom_IsCoolingDownFeedback_OutputChange; + _CurrentRoom.InCallFeedback.OutputChange -= CurrentRoom_InCallFeedback_OutputChange; + } + + _CurrentRoom = room; + + if (_CurrentRoom != null) + { + // get the source list config and set up the source list + + SetupSourceList(); + + // Name and logo + TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = _CurrentRoom.Name; + ShowLogo(); + + // Shutdown timer + _CurrentRoom.ShutdownPromptTimer.HasStarted += ShutdownPromptTimer_HasStarted; + _CurrentRoom.ShutdownPromptTimer.HasFinished += ShutdownPromptTimer_HasFinished; + _CurrentRoom.ShutdownPromptTimer.WasCancelled += ShutdownPromptTimer_WasCancelled; + + // Link up all the change events from the room + _CurrentRoom.OnFeedback.OutputChange += CurrentRoom_OnFeedback_OutputChange; + CurrentRoom_SyncOnFeedback(); + _CurrentRoom.IsWarmingUpFeedback.OutputChange += CurrentRoom_IsWarmingFeedback_OutputChange; + _CurrentRoom.IsCoolingDownFeedback.OutputChange += CurrentRoom_IsCoolingDownFeedback_OutputChange; + _CurrentRoom.InCallFeedback.OutputChange += CurrentRoom_InCallFeedback_OutputChange; + + + _CurrentRoom.CurrentVolumeDeviceChange += CurrentRoom_CurrentAudioDeviceChange; + RefreshAudioDeviceConnections(); + _CurrentRoom.CurrentSingleSourceChange += CurrentRoom_SourceInfoChange; + RefreshSourceInfo(); + + (_CurrentRoom.VideoCodec as IHasScheduleAwareness).CodecSchedule.MeetingsListHasChanged += CodecSchedule_MeetingsListHasChanged; + + CallSharingInfoVisibleFeedback = new BoolFeedback(() => _CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue); + _CurrentRoom.VideoCodec.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange; + CallSharingInfoVisibleFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.CallSharedSourceInfoVisible]); + + SetActiveCallListSharingContentStatus(); + + if (_CurrentRoom != null) + _CurrentRoom.CurrentSingleSourceChange += new SourceInfoChangeHandler(CurrentRoom_CurrentSingleSourceChange); + + TriList.SetSigFalseAction(UIBoolJoin.CallStopSharingPress, () => _CurrentRoom.RunRouteAction("codecOsd")); + + (Parent as EssentialsPanelMainInterfaceDriver).HeaderDriver.SetupHeaderButtons(this, CurrentRoom); + } + else + { + // Clear sigs that need to be + TriList.StringInput[UIStringJoin.CurrentRoomName].StringValue = "Select a room"; + } + } + + void SetCurrentRoom(EssentialsHuddleVtc1Room room) + { + if (_CurrentRoom == room) return; + // Disconnect current (probably never called) + + room.ConfigChanged -= room_ConfigChanged; + room.ConfigChanged += room_ConfigChanged; + + RefreshCurrentRoom(room); + } + + /// + /// Fires when room config of current room has changed. Meant to refresh room values to propegate any updates to UI + /// + /// + /// + void room_ConfigChanged(object sender, EventArgs e) + { + RefreshCurrentRoom(_CurrentRoom); + } + + /// + /// + /// + /// + /// + void CurrentRoom_InCallFeedback_OutputChange(object sender, EventArgs e) + { + var inCall = CurrentRoom.InCallFeedback.BoolValue; + if (inCall) + { + // Check if transitioning to in call - and non-sharable source is in use + if (CurrentRoom.CurrentSourceInfo != null && CurrentRoom.CurrentSourceInfo.DisableCodecSharing) + { + Debug.Console(1, CurrentRoom, "Transitioning to in-call, cancelling non-sharable source"); + CurrentRoom.RunRouteAction("codecOsd"); + } + } + + SetupSourceList(); + } + + /// + /// + /// + void SetupSourceList() + { + + var inCall = CurrentRoom.InCallFeedback.BoolValue; + var config = ConfigReader.ConfigObject.SourceLists; + if (config.ContainsKey(_CurrentRoom.SourceListKey)) + { + var srcList = config[_CurrentRoom.SourceListKey].OrderBy(kv => kv.Value.Order); + + // Setup sources list + SourceStagingSrl.Clear(); + uint i = 1; // counter for UI list + foreach (var kvp in srcList) + { + var srcConfig = kvp.Value; + Debug.Console(1, "**** {0}, {1}, {2}, {3}, {4}", srcConfig.PreferredName, srcConfig.IncludeInSourceList, + srcConfig.DisableCodecSharing, inCall, this.CurrentMode); + // Skip sources marked as not included, and filter list of non-sharable sources when in call + // or on share screen + if (!srcConfig.IncludeInSourceList || (inCall && srcConfig.DisableCodecSharing) + || this.CurrentMode == UiDisplayMode.Call && srcConfig.DisableCodecSharing) + { + Debug.Console(1, "Skipping {0}", srcConfig.PreferredName); + continue; + } + + var routeKey = kvp.Key; + var item = new SubpageReferenceListSourceItem(i++, SourceStagingSrl, srcConfig, + b => { if (!b) UiSelectSource(routeKey); }); + SourceStagingSrl.AddItem(item); // add to the SRL + item.RegisterForSourceChange(_CurrentRoom); + } + SourceStagingSrl.Count = (ushort)(i - 1); + } + + } + + /// + /// If the schedule changes, this event will fire + /// + /// + /// + void CodecSchedule_MeetingsListHasChanged(object sender, EventArgs e) + { + RefreshMeetingsList(); + } + + /// + /// Updates the current shared source label on the call list when the source changes + /// + /// + /// + /// + void CurrentRoom_CurrentSingleSourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + { + if (_CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && _CurrentRoom.CurrentSourceInfo != null) + TriList.StringInput[UIStringJoin.CallSharedSourceNameText].StringValue = _CurrentRoom.CurrentSourceInfo.PreferredName; + } + + /// + /// Fires when the sharing source feedback of the codec changes + /// + /// + /// + void SharingContentIsOnFeedback_OutputChange(object sender, EventArgs e) + { + SetActiveCallListSharingContentStatus(); + } + + /// + /// Sets the values for the text and button visibilty for the active call list source sharing info + /// + void SetActiveCallListSharingContentStatus() + { + CallSharingInfoVisibleFeedback.FireUpdate(); + + string callListSharedSourceLabel; + + if (_CurrentRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && _CurrentRoom.CurrentSourceInfo != null) + { + Debug.Console(0, "*#* CurrentRoom.CurrentSourceInfo = {0}", + _CurrentRoom.CurrentSourceInfo != null ? _CurrentRoom.CurrentSourceInfo.SourceKey : "Nada!"); + callListSharedSourceLabel = _CurrentRoom.CurrentSourceInfo.PreferredName; + } + else + callListSharedSourceLabel = "None"; + + TriList.StringInput[UIStringJoin.CallSharedSourceNameText].StringValue = callListSharedSourceLabel; + } + + /// + /// + /// + void RefreshMeetingsList() + { + // See if this is helpful or if the callback response in the codec class maybe doesn't come it time? + // Let's build list from event + // CurrentRoom.ScheduleSource.GetSchedule(); + + TriList.SetString(UIStringJoin.MeetingsOrContactMethodListIcon, "Calendar"); + TriList.SetString(UIStringJoin.MeetingsOrContactMethodListTitleText, "Today's Meetings"); + + ushort i = 0; + foreach (var m in CurrentRoom.ScheduleSource.CodecSchedule.Meetings) + { + i++; + MeetingOrContactMethodModalSrl.StringInputSig(i, 1).StringValue = m.StartTime.ToShortTimeString(); + MeetingOrContactMethodModalSrl.StringInputSig(i, 2).StringValue = m.EndTime.ToShortTimeString(); + MeetingOrContactMethodModalSrl.StringInputSig(i, 3).StringValue = m.Title; + MeetingOrContactMethodModalSrl.StringInputSig(i, 4).StringValue = string.Format("
{0}",m.Organizer); + MeetingOrContactMethodModalSrl.StringInputSig(i, 5).StringValue = "Join"; + MeetingOrContactMethodModalSrl.BoolInputSig(i, 2).BoolValue = m.Joinable; + var mm = m; // lambda scope + MeetingOrContactMethodModalSrl.GetBoolFeedbackSig(i, 1).SetSigFalseAction(() => + { + PopupInterlock.Hide(); + ActivityCallButtonPressed(); + var d = CurrentRoom.ScheduleSource as VideoCodecBase; + if (d != null) + RoomOnAndDialMeeting(mm); + }); + } + MeetingOrContactMethodModalSrl.Count = i; + + if (i == 0) // Show item indicating no meetings are booked for rest of day + { + MeetingOrContactMethodModalSrl.Count = 1; + + MeetingOrContactMethodModalSrl.StringInputSig(1, 1).StringValue = string.Empty; + MeetingOrContactMethodModalSrl.StringInputSig(1, 2).StringValue = string.Empty; + MeetingOrContactMethodModalSrl.StringInputSig(1, 3).StringValue = "No Meetings are booked for the remainder of the day."; + MeetingOrContactMethodModalSrl.StringInputSig(1, 4).StringValue = string.Empty; + MeetingOrContactMethodModalSrl.StringInputSig(1, 5).StringValue = string.Empty; + } + } + + /// + /// For room on/off changes + /// + void CurrentRoom_OnFeedback_OutputChange(object sender, EventArgs e) + { + CurrentRoom_SyncOnFeedback(); + } + + /// + /// + /// + void CurrentRoom_SyncOnFeedback() + { + var value = _CurrentRoom.OnFeedback.BoolValue; + TriList.BooleanInput[UIBoolJoin.RoomIsOn].BoolValue = value; + + TriList.BooleanInput[UIBoolJoin.StartPageVisible].BoolValue = !value; + + if (value) //ON + { + SetupActivityFooterWhenRoomOn(); + TriList.BooleanInput[UIBoolJoin.SelectASourceVisible].BoolValue = false; + TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = true; + + } + else + { + CurrentMode = UiDisplayMode.Start; + if (VCDriver.IsVisible) + VCDriver.Hide(); + SetupActivityFooterWhenRoomOff(); + ShowLogo(); + SetActivityFooterFeedbacks(); + TriList.BooleanInput[UIBoolJoin.VolumeDualMute1Visible].BoolValue = false; + TriList.BooleanInput[UIBoolJoin.SourceStagingBarVisible].BoolValue = false; + // Clear this so that the pesky meeting warning can resurface every minute when off + LastMeetingDismissedId = null; + } + } + + /// + /// + /// + void CurrentRoom_IsWarmingFeedback_OutputChange(object sender, EventArgs e) + { + if (CurrentRoom.IsWarmingUpFeedback.BoolValue) + { + ShowNotificationRibbon("Room is powering on. Please wait...", 0); + } + else + { + ShowNotificationRibbon("Room is powered on. Welcome.", 2000); + } + } + + /// + /// + /// + /// + /// + void CurrentRoom_IsCoolingDownFeedback_OutputChange(object sender, EventArgs e) + { + if (CurrentRoom.IsCoolingDownFeedback.BoolValue) + { + ShowNotificationRibbon("Room is powering off. Please wait.", 0); + } + else + { + HideNotificationRibbon(); + } + } + + /// + /// Hides source for provided source info + /// + /// + void DisconnectSource(SourceListItem previousInfo) + { + if (previousInfo == null) return; + + // Hide whatever is showing + if (IsVisible) + { + if (CurrentSourcePageManager != null) + { + CurrentSourcePageManager.Hide(); + CurrentSourcePageManager = null; + } + } + + if (previousInfo == null) return; + var previousDev = previousInfo.SourceDevice; + + // device type interfaces + if (previousDev is ISetTopBoxControls) + (previousDev as ISetTopBoxControls).UnlinkButtons(TriList); + // common interfaces + if (previousDev is IChannel) + (previousDev as IChannel).UnlinkButtons(TriList); + if (previousDev is IColor) + (previousDev as IColor).UnlinkButtons(TriList); + if (previousDev is IDPad) + (previousDev as IDPad).UnlinkButtons(TriList); + if (previousDev is IDvr) + (previousDev as IDvr).UnlinkButtons(TriList); + if (previousDev is INumericKeypad) + (previousDev as INumericKeypad).UnlinkButtons(TriList); + if (previousDev is IPower) + (previousDev as IPower).UnlinkButtons(TriList); + if (previousDev is ITransport) + (previousDev as ITransport).UnlinkButtons(TriList); + } + + /// + /// Refreshes and shows the room's current source + /// + void RefreshSourceInfo() + { + var routeInfo = CurrentRoom.CurrentSourceInfo; + // This will show off popup too + if (this.IsVisible && !VCDriver.IsVisible) + ShowCurrentSource(); + + if (routeInfo == null)// || !CurrentRoom.OnFeedback.BoolValue) + { + // Check for power off and insert "Room is off" + TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "Room is off"; + TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Power"; + this.Hide(); + Parent.Show(); + return; + } + else if (routeInfo != null) + { + TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = routeInfo.PreferredName; + TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = routeInfo.Icon; // defaults to "blank" + } + else // This never gets hit???!!! + { + TriList.StringInput[UIStringJoin.CurrentSourceName].StringValue = "---"; + TriList.StringInput[UIStringJoin.CurrentSourceIcon].StringValue = "Blank"; + } + + // Connect controls + if (routeInfo.SourceDevice != null) + ConnectControlDeviceMethods(routeInfo.SourceDevice); + } + + /// + /// Attach the source to the buttons and things + /// + void ConnectControlDeviceMethods(Device dev) + { + if (dev is ISetTopBoxControls) + (dev as ISetTopBoxControls).LinkButtons(TriList); + if (dev is IChannel) + (dev as IChannel).LinkButtons(TriList); + if (dev is IColor) + (dev as IColor).LinkButtons(TriList); + if (dev is IDPad) + (dev as IDPad).LinkButtons(TriList); + if (dev is IDvr) + (dev as IDvr).LinkButtons(TriList); + if (dev is INumericKeypad) + (dev as INumericKeypad).LinkButtons(TriList); + if (dev is IPower) + (dev as IPower).LinkButtons(TriList); + if (dev is ITransport) + (dev as ITransport).LinkButtons(TriList); + } + + /// + /// Detaches the buttons and feedback from the room's current audio device + /// + void ClearAudioDeviceConnections() + { + TriList.ClearBoolSigAction(UIBoolJoin.VolumeUpPress); + TriList.ClearBoolSigAction(UIBoolJoin.VolumeDownPress); + TriList.ClearBoolSigAction(UIBoolJoin.Volume1ProgramMutePressAndFB); + + var fDev = CurrentRoom.CurrentVolumeControls as IBasicVolumeWithFeedback; + if (fDev != null) + { + TriList.ClearUShortSigAction(UIUshortJoin.VolumeSlider1Value); + fDev.VolumeLevelFeedback.UnlinkInputSig( + TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); + } + } + + /// + /// Attaches the buttons and feedback to the room's current audio device + /// + void RefreshAudioDeviceConnections() + { + var dev = CurrentRoom.CurrentVolumeControls; + if (dev != null) // connect buttons + { + TriList.SetBoolSigAction(UIBoolJoin.VolumeUpPress, VolumeUpPress); + TriList.SetBoolSigAction(UIBoolJoin.VolumeDownPress, VolumeDownPress); + TriList.SetSigFalseAction(UIBoolJoin.Volume1ProgramMutePressAndFB, dev.MuteToggle); + } + + var fbDev = dev as IBasicVolumeWithFeedback; + if (fbDev == null) // this should catch both IBasicVolume and IBasicVolumeWithFeeback + TriList.UShortInput[UIUshortJoin.VolumeSlider1Value].UShortValue = 0; + else + { + // slider + TriList.SetUShortSigAction(UIUshortJoin.VolumeSlider1Value, fbDev.SetVolume); + // feedbacks + fbDev.MuteFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.Volume1ProgramMutePressAndFB]); + fbDev.VolumeLevelFeedback.LinkInputSig( + TriList.UShortInput[UIUshortJoin.VolumeSlider1Value]); + } + } + + /// + /// Handler for when the room's volume control device changes + /// + void CurrentRoom_CurrentAudioDeviceChange(object sender, VolumeDeviceChangeEventArgs args) + { + if (args.Type == ChangeType.WillChange) + ClearAudioDeviceConnections(); + else // did change + RefreshAudioDeviceConnections(); + } + + /// + /// Handles source change + /// + void CurrentRoom_SourceInfoChange(EssentialsRoomBase room, + SourceListItem info, ChangeType change) + { + if (change == ChangeType.WillChange) + DisconnectSource(info); + else + RefreshSourceInfo(); + } + } + + /// + /// For hanging off various common AV things that child drivers might need from a parent AV driver + /// + public interface IAVDriver + { + JoinedSigInterlock PopupInterlock { get; } + void ShowNotificationRibbon(string message, int timeout); + void HideNotificationRibbon(); + void ShowTech(); + } + + /// + /// For hanging off various common VC things that child drivers might need from a parent AV driver + /// + public interface IAVWithVCDriver : IAVDriver + { + EssentialsHuddleVtc1Room CurrentRoom { get; } + + PepperDash.Essentials.Core.Touchpanels.Keyboards.HabaneroKeyboardController Keyboard { get; } + /// + /// Exposes the ability to switch into call mode + /// + void ActivityCallButtonPressed(); + /// + /// Allows the codec to trigger the main UI to clear up if call is coming in. + /// + void PrepareForCodecIncomingCall(); + + SubpageReferenceList MeetingOrContactMethodModalSrl { get; } + } +} diff --git a/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs b/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs new file mode 100644 index 00000000..621ba257 --- /dev/null +++ b/PepperDashEssentials/UIDrivers/SourceChangeArgs.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Core; +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); +} \ No newline at end of file diff --git a/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs b/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs index 999a7f0b..be65410e 100644 --- a/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs +++ b/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs @@ -202,8 +202,6 @@ namespace PepperDash.Essentials.UIDrivers.VC { string roomNumberSipUri = ""; -#warning FIX PHONE FORMATTING TO ONLY SHOW WHEN APPROPRIATE - TALK TO NEIL - if (!string.IsNullOrEmpty(Codec.CodecInfo.SipUri)) // If both values are present, format the string with a pipe divider roomNumberSipUri = string.Format("{0} | {1}", GetFormattedPhoneNumber(Codec.CodecInfo.SipPhoneNumber), Codec.CodecInfo.SipUri); else // If only one value present, just show the phone number @@ -539,25 +537,22 @@ namespace PepperDash.Essentials.UIDrivers.VC var codec = Codec as IHasDirectory; if (codec != null) { - if (codec != null) + DirectoryList = new SmartObjectDynamicList(TriList.SmartObjects[UISmartObjectJoin.VCDirectoryList], + true, 1300); + codec.DirectoryResultReturned += new EventHandler(dir_DirectoryResultReturned); + + if (codec.PhonebookSyncState.InitialSyncComplete) + SetCurrentDirectoryToRoot(); + else { - DirectoryList = new SmartObjectDynamicList(TriList.SmartObjects[UISmartObjectJoin.VCDirectoryList], - true, 1300); - codec.DirectoryResultReturned += new EventHandler(dir_DirectoryResultReturned); - - if (codec.PhonebookSyncState.InitialSyncComplete) - SetCurrentDirectoryToRoot(); - else - { - codec.PhonebookSyncState.InitialSyncCompleted += new EventHandler(PhonebookSyncState_InitialSyncCompleted); - } + codec.PhonebookSyncState.InitialSyncCompleted += new EventHandler(PhonebookSyncState_InitialSyncCompleted); + } - // If there is something here now, show it otherwise wait for the event - if (CurrentDirectoryResult != null && codec.DirectoryRoot.DirectoryResults.Count > 0) - { - RefreshDirectory(); - } + // If there is something here now, show it otherwise wait for the event + if (CurrentDirectoryResult != null && codec.DirectoryRoot.DirectoryResults.Count > 0) + { + RefreshDirectory(); } } } diff --git a/PepperDashEssentials/UIDrivers/VolumeAndSourceChangeArgs.cs b/PepperDashEssentials/UIDrivers/VolumeAndSourceChangeArgs.cs deleted file mode 100644 index e67fc1d6..00000000 --- a/PepperDashEssentials/UIDrivers/VolumeAndSourceChangeArgs.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -using PepperDash.Core; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials -{ - /// - /// - /// - public class VolumeDeviceChangeEventArgs : EventArgs - { - public IBasicVolumeControls OldDev { get; private set; } - public IBasicVolumeControls NewDev { get; private set; } - public ChangeType Type { get; private set; } - - public VolumeDeviceChangeEventArgs(IBasicVolumeControls oldDev, IBasicVolumeControls newDev, ChangeType type) - { - OldDev = oldDev; - NewDev = newDev; - Type = type; - } - } - - /// - /// The handler type for a Room's SourceInfoChange - /// - public delegate void SourceInfoChangeHandler(EssentialsRoomBase room, SourceListItem info, ChangeType type); - - /// - /// - /// - public enum ChangeType - { - WillChange, DidChange - } -} \ No newline at end of file diff --git a/Release Package/PepperDashEssentials.cpz b/Release Package/PepperDashEssentials.cpz new file mode 100644 index 00000000..730aac16 Binary files /dev/null and b/Release Package/PepperDashEssentials.cpz differ diff --git a/Release Package/PepperDashEssentials.dll b/Release Package/PepperDashEssentials.dll new file mode 100644 index 00000000..9832c536 Binary files /dev/null and b/Release Package/PepperDashEssentials.dll differ diff --git a/devjson commands.json b/devjson commands.json index 67ee4df4..6b042a92 100644 --- a/devjson commands.json +++ b/devjson commands.json @@ -29,3 +29,5 @@ devjson:1 {"deviceKey":"microphonePrivacyController-1", "methodName":"TogglePriv devjson:1 {"deviceKey":"room1.InCallFeedback","methodName":"SetTestValue", "params": [ true ]} devjson:1 {"deviceKey":"room1.InCallFeedback","methodName":"ClearTestValue", "params": []} + +devjson:3 {"deviceKey":"room1.RoomOccupancy.RoomIsOccupiedFeedback","methodName":"SetTestValue", "params": [ true ]} \ No newline at end of file