diff --git a/PepperDashEssentials/AppServer/CotijaSystemController.cs b/PepperDashEssentials/AppServer/CotijaSystemController.cs index 33f66eff..5fd5e958 100644 --- a/PepperDashEssentials/AppServer/CotijaSystemController.cs +++ b/PepperDashEssentials/AppServer/CotijaSystemController.cs @@ -1,810 +1,847 @@ -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; - - //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; - 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); - - } - - /// - /// - /// - /// - 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"); - } - } +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; + + DateTime LastAckMessage; + + 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 != null + && WSClient.Connected) + { + CleanUpWebsocketClient(); + } + } + + public void PrintActionDictionaryPaths(object o) + { + Debug.Console(0, this, "ActionDictionary Contents:"); + + foreach (var item in ActionDictionary) + { + Debug.Console(0, this, "{0}", item.Key); + } + } + + /// + /// Adds an action to the dictionary + /// + /// The path of the API command + /// The action to be triggered by the commmand + public void AddAction(string key, object action) + { + if (!ActionDictionary.ContainsKey(key)) + { + ActionDictionary.Add(key, action); + } + else + { + Debug.Console(1, this, "Cannot add action with key '{0}' because key already exists in ActionDictionary.", key); + } + } + + /// + /// Removes an action from the dictionary + /// + /// + public void RemoveAction(string key) + { + if (ActionDictionary.ContainsKey(key)) + ActionDictionary.Remove(key); + } + + /// + /// + /// + /// + public void AddBridge(CotijaBridgeBase bridge) + { + RoomBridges.Add(bridge); + var b = bridge as IDelayedConfiguration; + if (b != null) + { + Debug.Console(0, this, "Adding room bridge with delayed configuration"); + b.ConfigurationIsReady += new EventHandler(bridge_ConfigurationIsReady); + } + else + { + Debug.Console(0, this, "Adding room bridge and sending configuration"); + //SystemUuid = ConfigReader.ConfigObject.SystemUuid; + RegisterSystemToServer(); + } + } + + /// + /// + /// + /// + /// + void bridge_ConfigurationIsReady(object sender, EventArgs e) + { + Debug.Console(1, this, "Bridge ready. Registering"); + //SystemUuid = ConfigReader.ConfigObject.SystemUuid; + // send the configuration object to the server + RegisterSystemToServer(); + } + + /// + /// + /// + /// + void ReconnectToServerTimerCallback(object o) + { + RegisterSystemToServer(); + } + + /// + /// Verifies system connection with servers + /// + /// + void AuthorizeSystem(string code) + { + //SystemUuid = ConfigReader.ConfigObject.SystemUuid; + + if (string.IsNullOrEmpty(SystemUuid)) + { + CrestronConsole.ConsoleCommandResponse("System does not have a UUID. Please ensure proper portal-format configuration is loaded and restart."); + return; + } + + if (string.IsNullOrEmpty(code)) + { + CrestronConsole.ConsoleCommandResponse("Please enter a user code to authorize a system"); + return; + } + + var req = new HttpClientRequest(); + string url = string.Format("http://{0}/api/system/grantcode/{1}/{2}", Config.ServerUrl, code, SystemUuid); + Debug.Console(0, this, "Authorizing to: {0}", url); + + if (string.IsNullOrEmpty(Config.ServerUrl)) + { + CrestronConsole.ConsoleCommandResponse("Config URL address is not set. Check portal configuration"); + return; + } + try + { + req.Url.Parse(url); + new HttpClient().DispatchAsync(req, (r, e) => + { + CheckHttpDebug(r, e); + if (e == HTTP_CALLBACK_ERROR.COMPLETED) + { + if (r.Code == 200) + { + Debug.Console(0, "System authorized, sending config."); +#warning This registration may need to wait for config ready. Maybe. + RegisterSystemToServer(); + } + else if (r.Code == 404) + { + if (r.ContentString.Contains("codeNotFound")) + { + Debug.Console(0, "Authorization failed, code not found for system UUID {0}", SystemUuid); + } + else if (r.ContentString.Contains("uuidNotFound")) + { + Debug.Console(0, "Authorization failed, uuid {0} not found. Check Essentials configuration is correct", + SystemUuid); + } + } + } + else + Debug.Console(0, this, "Error {0} in authorizing system", e); + }); + } + catch (Exception e) + { + Debug.Console(0, this, "Error in authorizing: {0}", e); + } + } + + /// + /// Dumps info in response to console command. + /// + void ShowInfo() + { + var url = Config != null ? Config.ServerUrl : "No config"; + string name; + string code; + if (RoomBridges != null && RoomBridges.Count > 0) + { + name = RoomBridges[0].RoomName; + code = RoomBridges[0].UserCode; + } + else + { + name = "No config"; + code = "Not available"; + } + var conn = WSClient == null ? "No client" : (WSClient.Connected ? "Yes" : "No"); + var secSinceLastAck = DateTime.Now - LastAckMessage; + + + CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: + Server address: {0} + System Name: {1} + System UUID: {2} + System User code: {3} + Connected?: {4} + Seconds Since Last Ack: {5}", url, name, SystemUuid, + code, conn, secSinceLastAck.Seconds); + } + + /// + /// Registers the room with the server + /// + /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" + void RegisterSystemToServer() + { + ConnectWebsocketClient(); + } + + /// + /// Connects the Websocket Client + /// + /// + void ConnectWebsocketClient() + { + + Debug.Console(1, this, "Initializing Stream client to server."); + + if (WSClient != null) + { + Debug.Console(1, this, "Cleaning up previous socket"); + CleanUpWebsocketClient(); + } + + WSClient = new WebSocketClient(); + WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); + WSClient.ConnectionCallBack = Websocket_ConnectCallback; + WSClient.ConnectAsync(); + } + + /// + /// + /// + /// + /// + int Websocket_ConnectCallback(WebSocketClient.WEBSOCKET_RESULT_CODES code) + { + if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) + { + StopServerReconnectTimer(); + Debug.Console(1, this, "Websocket connected"); + WSClient.DisconnectCallBack = Websocket_DisconnectCallback; + WSClient.SendCallBack = Websocket_SendCallback; + WSClient.ReceiveCallBack = Websocket_ReceiveCallback; + WSClient.ReceiveAsync(); + SendMessageObjectToServer(new + { + type = "hello" + }); + } + else + { + if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_HTTP_HANDSHAKE_TOKEN_ERROR) + { + // This is the case when app server is running behind a websever and app server is down + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failed. Check that app server is running behind web server"); + } + else if (code == WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SOCKET_CONNECTION_FAILED) + { + // this will be the case when webserver is unreachable + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failed"); + } + else + { + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Web socket connection failure: {0}", code); + } + StartServerReconnectTimer(); + } + + return 0; + } + + /// + /// After a "hello" from the server, sends config and stuff + /// + void SendInitialMessage() + { + Debug.Console(1, this, "Sending initial join message"); + var confObject = ConfigReader.ConfigObject; + confObject.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; + var version = Assembly.GetExecutingAssembly().GetName().Version; + confObject.Info.RuntimeInfo.AssemblyVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); + + var msg = new + { + type = "join", + content = new + { + config = confObject + } + }; + SendMessageObjectToServer(msg); + } + + /// + /// Sends any object type to server + /// + /// + public void SendMessageObjectToServer(object o) + { + SendMessageToServer(JObject.FromObject(o)); + } + + /// + /// Sends a message to the server from a room + /// + /// room from which the message originates + /// object to be serialized and sent in post body + public void SendMessageToServer(JObject o) + { + if (WSClient != null && WSClient.Connected) + { + string message = JsonConvert.SerializeObject(o, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + + if (!message.Contains("/system/heartbeat")) + Debug.Console(1, this, "Message TX: {0}", message); + //else + // Debug.Console(1, this, "TX messages contains /system/heartbeat"); + + var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); + var result = WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); + if (result != WebSocketClient.WEBSOCKET_RESULT_CODES.WEBSOCKET_CLIENT_SUCCESS) + { + Debug.Console(1, this, "Socket send result error: {0}", result); + } + } + else if (!WSClient.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; + + if (!message.Contains("/system/heartbeat")) + Debug.Console(1, this, "Message RX: {0}", message); + else + { + LastAckMessage = DateTime.Now; + //Debug.Console(1, this, "RX message contains /system/heartbeat"); + } + + 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/AppServer/Messengers/AudioCodecBaseMessenger.cs b/PepperDashEssentials/AppServer/Messengers/AudioCodecBaseMessenger.cs new file mode 100644 index 00000000..b37b80da --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/AudioCodecBaseMessenger.cs @@ -0,0 +1,102 @@ +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.AudioCodec; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Provides a messaging bridge for an AudioCodecBase device + /// + public class AudioCodecBaseMessenger : MessengerBase + { + /// + /// Device being bridged + /// + public AudioCodecBase Codec { get; set; } + + /// + /// Constuctor + /// + /// + /// + /// + public AudioCodecBaseMessenger(string key, AudioCodecBase codec, string messagePath) + : base(key, messagePath) + { + if (codec == null) + throw new ArgumentNullException("codec"); + + Codec = codec; + codec.CallStatusChange += new EventHandler(codec_CallStatusChange); + + } + + protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + { + appServerController.AddAction(MessagePath + "/fullStatus", new Action(SendAtcFullMessageObject)); + appServerController.AddAction(MessagePath + "/dial", new Action(s => Codec.Dial(s))); + appServerController.AddAction(MessagePath + "/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); + })); + } + + /// + /// Helper to grab a call with string ID + /// + /// + /// + CodecActiveCallItem GetCallWithId(string id) + { + return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id); + } + + void codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) + { + SendAtcFullMessageObject(); + } + + /// + /// Helper method to build call status for vtc + /// + /// + void SendAtcFullMessageObject() + { + + var info = Codec.CodecInfo; + PostStatusMessage(new + { + isInCall = Codec.IsInCall, + calls = Codec.ActiveCalls, + info = new + { + phoneNumber = info.PhoneNumber + } + }); + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Messengers/AtcDdvc01Messenger.cs b/PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs similarity index 64% rename from PepperDashEssentials/AppServer/Messengers/AtcDdvc01Messenger.cs rename to PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs index 882ad317..b578018b 100644 --- a/PepperDashEssentials/AppServer/Messengers/AtcDdvc01Messenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/Ddvc01AtcMessenger.cs @@ -16,12 +16,33 @@ namespace PepperDash.Essentials.AppServer.Messengers { BasicTriList EISC; + /// + /// 221 + /// const uint BDialHangup = 221; + /// + /// 251 + /// const uint BIncomingAnswer = 251; + /// + /// 252 + /// const uint BIncomingReject = 252; + /// + /// 241 + /// const uint BSpeedDial1 = 241; + /// + /// 242 + /// const uint BSpeedDial2 = 242; + /// + /// 243 + /// const uint BSpeedDial3 = 243; + /// + /// 244 + /// const uint BSpeedDial4 = 244; /// @@ -31,14 +52,22 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// 211 /// - const uint SCurrentCallString = 211; + const uint SCurrentCallNumber = 211; + /// + /// 212 + /// + const uint SCurrentCallName = 212; /// /// 221 /// const uint SHookState = 221; + /// + /// 222 + /// + const uint SCallDirection = 222; /// - /// + /// 201-212 0-9*# /// Dictionary DTMFMap = new Dictionary { @@ -56,6 +85,9 @@ namespace PepperDash.Essentials.AppServer.Messengers { "#", 212 }, }; + /// + /// + /// CodecActiveCallItem CurrentCallItem; @@ -64,8 +96,8 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public Ddvc01AtcMessenger(BasicTriList eisc, string messagePath) - : base(messagePath) + public Ddvc01AtcMessenger(string key, BasicTriList eisc, string messagePath) + : base(key, messagePath) { EISC = eisc; @@ -79,12 +111,14 @@ namespace PepperDash.Essentials.AppServer.Messengers /// void SendFullStatus() { + + this.PostStatusMessage(new { calls = GetCurrentCallList(), - callStatus = EISC.GetString(SHookState), - currentCallString = EISC.GetString(SCurrentCallString), + currentCallString = EISC.GetString(SCurrentCallNumber), currentDialString = EISC.GetString(SCurrentDialString), + isInCall = EISC.GetString(SHookState) == "Connected" }); } @@ -94,31 +128,33 @@ namespace PepperDash.Essentials.AppServer.Messengers /// protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) { - Action send = this.PostStatusMessage; - EISC.SetStringSigAction(SCurrentDialString, s => send(new { currentDialString = s })); + //EISC.SetStringSigAction(SCurrentDialString, s => PostStatusMessage(new { currentDialString = s })); EISC.SetStringSigAction(SHookState, s => { CurrentCallItem.Status = (eCodecCallStatus)Enum.Parse(typeof(eCodecCallStatus), s, true); - GetCurrentCallList(); - send(new - { - calls = GetCurrentCallList(), - callStatus = s - }); + //GetCurrentCallList(); + SendCallsList(); }); - EISC.SetStringSigAction(SCurrentCallString, s => + EISC.SetStringSigAction(SCurrentCallNumber, s => { - CurrentCallItem.Name = s; CurrentCallItem.Number = s; - send(new - { - calls = GetCurrentCallList(), - currentCallString = s - }); + SendCallsList(); }); + EISC.SetStringSigAction(SCurrentCallName, s => + { + CurrentCallItem.Name = s; + SendCallsList(); + }); + + EISC.SetStringSigAction(SCallDirection, s => + { + CurrentCallItem.Direction = (eCodecCallDirection)Enum.Parse(typeof(eCodecCallDirection), s, true); + SendCallsList(); + }); + // Add press and holds using helper Action addPHAction = (s, u) => AppServerController.AddAction(MessagePath + s, new PressAndHoldAction(b => EISC.SetBool(u, b))); @@ -126,9 +162,9 @@ namespace PepperDash.Essentials.AppServer.Messengers // 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("/endCallById", BDialHangup); + addAction("/acceptById", BIncomingAnswer); + addAction("/rejectById", BIncomingReject); addAction("/speedDial1", BSpeedDial1); addAction("/speedDial2", BSpeedDial2); addAction("/speedDial3", BSpeedDial3); @@ -148,6 +184,17 @@ namespace PepperDash.Essentials.AppServer.Messengers })); } + /// + /// + /// + void SendCallsList() + { + PostStatusMessage(new + { + calls = GetCurrentCallList(), + }); + } + /// /// Turns the /// diff --git a/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs b/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs new file mode 100644 index 00000000..d6f20ab4 --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/Ddvc01VtcMessenger.cs @@ -0,0 +1,604 @@ +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 Ddvc01VtcMessenger : MessengerBase + { + BasicTriList EISC; + + /********* Bools *********/ + /// + /// 724 + /// + const uint BDialHangup = 724; + /// + /// 750 + /// + const uint BCallIncoming = 750; + /// + /// 751 + /// + const uint BIncomingAnswer = 751; + /// + /// 752 + /// + const uint BIncomingReject = 752; + /// + /// 741 + /// + const uint BSpeedDial1 = 741; + /// + /// 742 + /// + const uint BSpeedDial2 = 742; + /// + /// 743 + /// + const uint BSpeedDial3 = 743; + /// + /// 744 + /// + const uint BSpeedDial4 = 744; + /// + /// 800 + /// + const uint BDirectorySearchBusy = 800; + /// + /// 801 + /// + const uint BDirectoryLineSelected = 801; + /// + /// 801 when selected entry is a contact + /// + const uint BDirectoryEntryIsContact = 801; + /// + /// 802 To show/hide back button + /// + const uint BDirectoryIsRoot = 802; + /// + /// 803 Pulse from system to inform us when directory is ready + /// + const uint DDirectoryHasChanged = 803; + /// + /// 804 + /// + const uint BDirectoryRoot = 804; + /// + /// 805 + /// + const uint BDirectoryFolderBack = 805; + /// + /// 806 + /// + const uint BDirectoryDialSelectedLine = 806; + /// + /// 811 + /// + const uint BCameraControlUp = 811; + /// + /// 812 + /// + const uint BCameraControlDown = 812; + /// + /// 813 + /// + const uint BCameraControlLeft = 813; + /// + /// 814 + /// + const uint BCameraControlRight = 814; + /// + /// 815 + /// + const uint BCameraControlZoomIn = 815; + /// + /// 816 + /// + const uint BCameraControlZoomOut = 816; + /// + /// 821 - 826 + /// + const uint BCameraPresetStart = 821; + + /// + /// 831 + /// + const uint BCameraModeAuto = 831; + /// + /// 832 + /// + const uint BCameraModeManual = 832; + /// + /// 833 + /// + const uint BCameraModeOff = 833; + + + + /********* Ushorts *********/ + /// + /// 760 + /// + const uint UCameraNumberSelect = 760; + /// + /// 801 + /// + const uint UDirectorySelectRow = 801; + /// + /// 801 + /// + const uint UDirectoryRowCount = 801; + + + + /********* Strings *********/ + /// + /// 701 + /// + const uint SCurrentDialString = 701; + /// + /// 702 + /// + const uint SCurrentCallName = 702; + /// + /// 703 + /// + const uint SCurrentCallNumber = 703; + /// + /// 731 + /// + const uint SHookState = 731; + /// + /// 722 + /// + const uint SCallDirection = 722; + /// + /// 751 + /// + const uint SIncomingCallName = 751; + /// + /// 752 + /// + const uint SIncomingCallNumber = 752; + + /// + /// 800 + /// + const uint SDirectorySearchString = 800; + /// + /// 801-1055 + /// + const uint SDirectoryEntriesStart = 801; + /// + /// 1056 + /// + const uint SDirectoryEntrySelectedName = 1056; + /// + /// 1057 + /// + const uint SDirectoryEntrySelectedNumber = 1057; + /// + /// 1058 + /// + const uint SDirectorySelectedFolderName = 1058; + + + /// + /// 701-712 0-9*# + /// + Dictionary DTMFMap = new Dictionary + { + { "1", 701 }, + { "2", 702 }, + { "3", 703 }, + { "4", 704 }, + { "5", 705 }, + { "6", 706 }, + { "7", 707 }, + { "8", 708 }, + { "9", 709 }, + { "0", 710 }, + { "*", 711 }, + { "#", 712 }, + }; + + CodecActiveCallItem CurrentCallItem; + CodecActiveCallItem IncomingCallItem; + + ushort PreviousDirectoryLength = 0; + + /// + /// + /// + /// + /// + public Ddvc01VtcMessenger(string key, BasicTriList eisc, string messagePath) + : base(key, messagePath) + { + EISC = eisc; + + CurrentCallItem = new CodecActiveCallItem(); + CurrentCallItem.Type = eCodecCallType.Video; + CurrentCallItem.Id = "-video-"; + } + + /// + /// + /// + /// + protected override void CustomRegisterWithAppServer(CotijaSystemController appServerController) + { + var asc = appServerController; + EISC.SetStringSigAction(SHookState, s => + { + CurrentCallItem.Status = (eCodecCallStatus)Enum.Parse(typeof(eCodecCallStatus), s, true); + PostFullStatus(); // SendCallsList(); + }); + + EISC.SetStringSigAction(SCurrentCallNumber, s => + { + CurrentCallItem.Number = s; + PostCallsList(); + }); + + EISC.SetStringSigAction(SCurrentCallName, s => + { + CurrentCallItem.Name = s; + PostCallsList(); + }); + + EISC.SetStringSigAction(SCallDirection, s => + { + CurrentCallItem.Direction = (eCodecCallDirection)Enum.Parse(typeof(eCodecCallDirection), s, true); + PostCallsList(); + }); + + EISC.SetBoolSigAction(BCallIncoming, b => + { + if (b) + { + var ica = new CodecActiveCallItem() + { + Direction = eCodecCallDirection.Incoming, + Id = "-video-incoming", + Name = EISC.GetString(SIncomingCallName), + Number = EISC.GetString(SIncomingCallNumber), + Status = eCodecCallStatus.Ringing, + Type = eCodecCallType.Video + }; + IncomingCallItem = ica; + } + else + { + IncomingCallItem = null; + } + PostCallsList(); + }); + + // Directory insanity + EISC.SetUShortSigAction(UDirectoryRowCount, u => + { + // The length of the list comes in before the list does. + // Splice the sig change operation onto the last string sig that will be changing + // when the directory entries make it through. + if (PreviousDirectoryLength > 0) + { + EISC.ClearStringSigAction(SDirectoryEntriesStart + PreviousDirectoryLength - 1); + } + EISC.SetStringSigAction(SDirectoryEntriesStart + u - 1, s => PostDirectory()); + PreviousDirectoryLength = u; + }); + + EISC.SetStringSigAction(SDirectoryEntrySelectedName, s => + { + PostStatusMessage(new + { + directoryContactSelected = new + { + name = EISC.GetString(SDirectoryEntrySelectedName), + } + }); + }); + + EISC.SetStringSigAction(SDirectoryEntrySelectedNumber, s => + { + PostStatusMessage(new + { + directoryContactSelected = new + { + number = EISC.GetString(SDirectoryEntrySelectedNumber), + } + }); + }); + + EISC.SetStringSigAction(SDirectorySelectedFolderName, s => PostStatusMessage(new + { + directorySelectedFolderName = EISC.GetString(SDirectorySelectedFolderName) + })); + + EISC.SetSigTrueAction(BCameraModeAuto, () => PostCameraMode()); + EISC.SetSigTrueAction(BCameraModeManual, () => PostCameraMode()); + EISC.SetSigTrueAction(BCameraModeOff, () => PostCameraMode()); + + EISC.SetUShortSigAction(UCameraNumberSelect, (u) => PostSelectedCamera()); + + + // Add press and holds using helper action + Action addPHAction = (s, u) => + AppServerController.AddAction(MessagePath + s, new PressAndHoldAction(b => EISC.SetBool(u, b))); + addPHAction("/cameraUp", BCameraControlUp); + addPHAction("/cameraDown", BCameraControlDown); + addPHAction("/cameraLeft", BCameraControlLeft); + addPHAction("/cameraRight", BCameraControlRight); + addPHAction("/cameraZoomIn", BCameraControlZoomIn); + addPHAction("/cameraZoomOut", BCameraControlZoomOut); + + // Add straight pulse calls using helper action + Action addAction = (s, u) => + AppServerController.AddAction(MessagePath + s, new Action(() => EISC.PulseBool(u, 100))); + addAction("/endCallById", BDialHangup); + addAction("/acceptById", BIncomingAnswer); + addAction("/rejectById", BIncomingReject); + addAction("/speedDial1", BSpeedDial1); + addAction("/speedDial2", BSpeedDial2); + addAction("/speedDial3", BSpeedDial3); + addAction("/speedDial4", BSpeedDial4); + addAction("/cameraModeAuto", BCameraModeAuto); + addAction("/cameraModeManual", BCameraModeManual); + addAction("/cameraModeOff", BCameraModeOff); + + asc.AddAction("/cameraSelect", new Action(SelectCamera)); + + // camera presets + for(uint i = 0; i < 6; i++) + { + addAction("/cameraPreset" + (i + 1), BCameraPresetStart + i); + } + + asc.AddAction(MessagePath + "/isReady", new Action(PostIsReady)); + // Get status + asc.AddAction(MessagePath + "/fullStatus", new Action(PostFullStatus)); + // Dial on string + asc.AddAction(MessagePath + "/dial", new Action(s => + EISC.SetString(SCurrentDialString, s))); + // Pulse DTMF + asc.AddAction(MessagePath + "/dtmf", new Action(s => + { + if (DTMFMap.ContainsKey(s)) + { + EISC.PulseBool(DTMFMap[s], 100); + } + })); + + // Directory madness + asc.AddAction(MessagePath + "/directoryRoot", new Action(() => EISC.PulseBool(BDirectoryRoot))); + asc.AddAction(MessagePath + "/directoryBack", new Action(() => EISC.PulseBool(BDirectoryFolderBack))); + asc.AddAction(MessagePath + "/directoryById", new Action(s => + { + // the id should contain the line number to forward to simpl + try + { + var u = ushort.Parse(s); + EISC.SetUshort(UDirectorySelectRow, u); + EISC.PulseBool(BDirectoryLineSelected); + } + catch (Exception) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, + "/directoryById request contains non-numeric ID incompatible with DDVC bridge"); + } + + })); + asc.AddAction(MessagePath + "/directorySelectContact", new Action(s => + { + try + { + var u = ushort.Parse(s); + EISC.SetUshort(UDirectorySelectRow, u); + EISC.PulseBool(BDirectoryLineSelected); + } + catch + { + + } + })); + asc.AddAction(MessagePath + "/directoryDialContact", new Action(() => { + EISC.PulseBool(BDirectoryDialSelectedLine); + })); + asc.AddAction(MessagePath + "/getDirectory", new Action(() => + { + if (EISC.GetUshort(UDirectoryRowCount) > 0) + { + PostDirectory(); + } + else + { + EISC.PulseBool(BDirectoryRoot); + } + })); + } + + /// + /// + /// + void PostFullStatus() + { + this.PostStatusMessage(new + { + calls = GetCurrentCallList(), + cameraMode = GetCameraMode(), + currentCallString = EISC.GetString(SCurrentCallNumber), + currentDialString = EISC.GetString(SCurrentDialString), + directoryContactSelected = new + { + name = EISC.GetString(SDirectoryEntrySelectedName), + number = EISC.GetString(SDirectoryEntrySelectedNumber) + }, + directorySelectedFolderName = EISC.GetString(SDirectorySelectedFolderName), + isInCall = EISC.GetString(SHookState) == "Connected", + hasDirectory = true, + hasDirectorySearch = false, + hasRecents = true, + hasCameras = true, + selectedCamera = GetSelectedCamera(), + }); + } + + /// + /// + /// + void PostDirectory() + { + var u = EISC.GetUshort(UDirectoryRowCount); + var items = new List(); + for (uint i = 0; i < u; i++) + { + var name = EISC.GetString(SDirectoryEntriesStart + i); + var id = (i + 1).ToString(); + // is folder or contact? + if (name.StartsWith("[+]")) + { + items.Add(new + { + folderId = id, + name = name + }); + } + else + { + items.Add(new + { + contactId = id, + name = name + }); + } + } + + var directoryMessage = new + { + currentDirectory = new + { + isRootDirectory = EISC.GetBool(BDirectoryIsRoot), + directoryResults = items + } + }; + PostStatusMessage(directoryMessage); + } + + /// + /// + /// + void PostCameraMode() + { + PostStatusMessage(new + { + cameraMode = GetCameraMode() + }); + } + + /// + /// + /// + /// + string GetCameraMode() + { + string m; + if (EISC.GetBool(BCameraModeAuto)) m = "auto"; + else if (EISC.GetBool(BCameraModeManual)) m = "manual"; + else m = "off"; + return m; + } + + void PostSelectedCamera() + { + PostStatusMessage(new + { + selectedCamera = GetSelectedCamera() + }); + } + + /// + /// + /// + string GetSelectedCamera() + { + var num = EISC.GetUshort(UCameraNumberSelect); + string m; + if (num == 100) + { + m = "cameraFar"; + } + else + { + m = "camera" + num; + } + return m; + } + + /// + /// + /// + void PostIsReady() + { + PostStatusMessage(new + { + isReady = true + }); + } + + /// + /// + /// + void PostCallsList() + { + PostStatusMessage(new + { + calls = GetCurrentCallList(), + }); + } + + /// + /// + /// + /// + void SelectCamera(string s) + { + var cam = s.Substring(6); + if (cam.ToLower() == "far") + { + EISC.SetUshort(UCameraNumberSelect, 100); + } + else + { + EISC.SetUshort(UCameraNumberSelect, UInt16.Parse(cam)); + } + } + + /// + /// Turns the + /// + /// + List GetCurrentCallList() + { + var list = new List(); + if (CurrentCallItem.Status != eCodecCallStatus.Disconnected) + { + list.Add(CurrentCallItem); + } + if (EISC.GetBool(BCallIncoming)) { + + } + return list; + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs b/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs index 125bf83a..f9833afb 100644 --- a/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs +++ b/PepperDashEssentials/AppServer/Messengers/MessengerBase.cs @@ -7,6 +7,8 @@ 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; @@ -15,8 +17,10 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// Provides a messaging bridge for a VideoCodecBase /// - public abstract class MessengerBase + public abstract class MessengerBase : IKeyed { + public string Key { get; private set; } + /// /// /// @@ -28,8 +32,10 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public MessengerBase(string messagePath) + public MessengerBase(string key, string messagePath) { + Key = key; + if (string.IsNullOrEmpty(messagePath)) throw new ArgumentException("messagePath must not be empty or null"); @@ -62,11 +68,14 @@ namespace PepperDash.Essentials.AppServer.Messengers /// The contents of the content object protected void PostStatusMessage(object contentObject) { - AppServerController.SendMessageToServer(JObject.FromObject(new - { - type = MessagePath, - content = 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..7b56ab29 --- /dev/null +++ b/PepperDashEssentials/AppServer/Messengers/SystemMonitorMessenger.cs @@ -0,0 +1,92 @@ +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.ProgramInfoChanged += new EventHandler(ProgramInfoChanged); + } + + CrestronConsole.AddNewConsoleCommand(s => SendFullStatusMessage(), "SendFullSysMonStatus", "Sends the full System Monitor Status", ConsoleAccessLevelEnum.AccessOperator); + } + + /// + /// Posts the program information message + /// + /// + /// + void ProgramInfoChanged(object sender, ProgramInfoEventArgs e) + { + Debug.Console(1, "Posting Status Message: {0}", e.ProgramInfo.ToString()); + PostStatusMessage(e.ProgramInfo); + } + + /// + /// Posts the system monitor properties + /// + /// + /// + void SysMon_SystemMonitorPropertiesChanged(object sender, EventArgs e) + { + SendSystemMonitorStatusMessage(); + } + + void SendFullStatusMessage() + { + SendSystemMonitorStatusMessage(); + + foreach (var p in SysMon.ProgramStatusFeedbackCollection) + { + PostStatusMessage(p.Value.ProgramInfo); + } + } + + void SendSystemMonitorStatusMessage() + { + Debug.Console(1, "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 index 37437b5d..bd44e139 100644 --- a/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs +++ b/PepperDashEssentials/AppServer/Messengers/VideoCodecBaseMessenger.cs @@ -13,7 +13,7 @@ using PepperDash.Essentials.Devices.Common.VideoCodec; namespace PepperDash.Essentials.AppServer.Messengers { /// - /// Provides a messaging bridge for a VideoCodecBase + /// Provides a messaging bridge for a VideoCodecBase device /// public class VideoCodecBaseMessenger : MessengerBase { @@ -26,7 +26,8 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public VideoCodecBaseMessenger(VideoCodecBase codec, string messagePath) : base(messagePath) + public VideoCodecBaseMessenger(string key, VideoCodecBase codec, string messagePath) + : base(key, messagePath) { if (codec == null) throw new ArgumentNullException("codec"); @@ -40,7 +41,31 @@ namespace PepperDash.Essentials.AppServer.Messengers { dirCodec.DirectoryResultReturned += new EventHandler(dirCodec_DirectoryResultReturned); } + + var recCodec = codec as IHasCallHistory; + if (recCodec != null) + { + recCodec.CallHistory.RecentCallsListHasChanged += new EventHandler(CallHistory_RecentCallsListHasChanged); + } } + + /// + /// + /// + /// + /// + void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e) + { + var recents = (sender as CodecCallHistory).RecentCalls; + + if (recents != null) + { + PostStatusMessage(new + { + recentCalls = recents + }); + } + } /// /// @@ -49,7 +74,6 @@ namespace PepperDash.Essentials.AppServer.Messengers /// void dirCodec_DirectoryResultReturned(object sender, DirectoryEventArgs e) { - var dir = e.Directory; PostStatusMessage(new { currentDirectory = e.Directory @@ -98,9 +122,10 @@ namespace PepperDash.Essentials.AppServer.Messengers if (call != null) Codec.AcceptCall(call); })); - appServerController.AddAction(MessagePath + "/directoryRoot", new Action(GetDirectoryRoot)); + appServerController.AddAction(MessagePath + "/getDirectory", new Action(GetDirectoryRoot)); appServerController.AddAction(MessagePath + "/directoryById", new Action(s => GetDirectory(s))); appServerController.AddAction(MessagePath + "/directorySearch", new Action(s => DirectorySearch(s))); + appServerController.AddAction(MessagePath + "/getCallHistory", new Action(GetCallHistory)); appServerController.AddAction(MessagePath + "/privacyModeOn", new Action(Codec.PrivacyModeOn)); appServerController.AddAction(MessagePath + "/privacyModeOff", new Action(Codec.PrivacyModeOff)); appServerController.AddAction(MessagePath + "/privacyModeToggle", new Action(Codec.PrivacyModeToggle)); @@ -110,6 +135,24 @@ namespace PepperDash.Essentials.AppServer.Messengers appServerController.AddAction(MessagePath + "/standbyOff", new Action(Codec.StandbyDeactivate)); } + void GetCallHistory() + { + var codec = (Codec as IHasCallHistory); + + if (codec != null) + { + var recents = codec.CallHistory.RecentCalls; + + if (recents != null) + { + PostStatusMessage(new + { + recentCalls = recents + }); + } + } + } + public void GetFullStatusMessage() { @@ -227,7 +270,9 @@ namespace PepperDash.Essentials.AppServer.Messengers sipURI = info.SipUri }, showSelfViewByDefault = Codec.ShowSelfViewByDefault, - hasDirectory = Codec is IHasDirectory + hasDirectory = Codec is IHasDirectory, + hasRecents = Codec is IHasCallHistory, + hasCameras = Codec is IHasCameraControl }); } } diff --git a/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs b/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs index f02d6098..fdd73989 100644 --- a/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/CotijaDdvc01RoomBridge.cs @@ -56,7 +56,10 @@ namespace PepperDash.Essentials.Room.Cotija /// 1 /// public const uint MasterVolumeMuteToggle = 1; - + /// + /// 1 + /// + public const uint VolumeMutesJoinStart = 1; /// /// 61 /// @@ -72,7 +75,7 @@ namespace PepperDash.Essentials.Room.Cotija /// /// 72 /// - public const uint SourceHasChanged = 72; + public const uint SourceHasChanged = 71; /// /// 261 - The start of the range of speed dial visibles /// @@ -81,6 +84,11 @@ namespace PepperDash.Essentials.Room.Cotija /// 501 /// public const uint ConfigIsReady = 501; + /// + /// 601 + /// + public const uint SourceShareDisableStartJoin = 601; + } public class UshortJoin @@ -89,15 +97,26 @@ namespace PepperDash.Essentials.Room.Cotija /// 1 /// public const uint MasterVolumeLevel = 1; - + /// + /// 1 + /// + public const uint VolumeSlidersJoinStart = 1; /// /// 61 /// public const uint ShutdownPromptDuration = 61; + /// + /// 101 + /// + public const uint VolumeSliderCount = 101; } public class StringJoin { + /// + /// 1 + /// + public const uint VolumeSliderNamesJoinStart = 1; /// /// 71 /// @@ -141,6 +160,38 @@ namespace PepperDash.Essentials.Room.Cotija /// 402 /// public const uint ServerUrl = 402; + /// + /// 512 + /// + public const uint RoomSpeedDialNamesJoinStart = 512; + /// + /// 516 + /// + public const uint RoomSpeedDialNumberssJoinStart = 516; + /// + /// 601 + /// + public const uint SourceNameJoinStart = 601; + /// + /// 621 + /// + public const uint SourceIconJoinStart = 621; + /// + /// 641 + /// + public const uint SourceKeyJoinStart = 641; + /// + /// 661 + /// + public const uint SourceTypeJoinStart = 661; + /// + /// 761 + /// + public const uint CameraNearNameStart = 761; + /// + /// 770 - presence of this name on the input will cause the camera to be added + /// + public const uint CameraFarName = 770; } /// @@ -166,6 +217,7 @@ namespace PepperDash.Essentials.Room.Cotija CotijaDdvc01DeviceBridge SourceBridge; Ddvc01AtcMessenger AtcMessenger; + Ddvc01VtcMessenger VtcMessenger; /// @@ -204,9 +256,14 @@ namespace PepperDash.Essentials.Room.Cotija SetupFunctions(); SetupFeedbacks(); - AtcMessenger = new Ddvc01AtcMessenger(EISC, "/device/audioCodec"); + var atcKey = string.Format("atc-{0}-{1}", this.Key, Parent.Key); + AtcMessenger = new Ddvc01AtcMessenger(atcKey, EISC, "/device/audioCodec"); AtcMessenger.RegisterWithAppServer(Parent); + var vtcKey = string.Format("atc-{0}-{1}", this.Key, Parent.Key); + VtcMessenger = new Ddvc01VtcMessenger(vtcKey, EISC, "/device/videoCodec"); + VtcMessenger.RegisterWithAppServer(Parent); + EISC.SigChange += EISC_SigChange; EISC.OnlineStatusChange += (o, a) => { @@ -250,7 +307,6 @@ 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))); @@ -384,8 +440,8 @@ namespace PepperDash.Essentials.Room.Cotija //Room //if (co.Rooms == null) // always start fresh in case simpl changed - co.Rooms = new List(); - var rm = new EssentialsRoomConfig(); + co.Rooms = new List(); + var rm = new DeviceConfig(); if (co.Rooms.Count == 0) { Debug.Console(0, this, "Adding room to config"); @@ -396,7 +452,7 @@ namespace PepperDash.Essentials.Room.Cotija Debug.Console(0, this, "Replacing Room[0] in config"); co.Rooms[0] = rm; } - rm.Name = EISC.StringOutput[501].StringValue; + rm.Name = EISC.StringOutput[StringJoin.ConfigRoomName].StringValue; rm.Key = "room1"; rm.Type = "ddvc01"; @@ -407,15 +463,17 @@ namespace PepperDash.Essentials.Room.Cotija rmProps = JsonConvert.DeserializeObject(rm.Properties.ToString()); rmProps.Help = new EssentialsHelpPropertiesConfig(); - rmProps.Help.CallButtonText = EISC.StringOutput[503].StringValue; - rmProps.Help.Message = EISC.StringOutput[502].StringValue; + rmProps.Help.CallButtonText = EISC.StringOutput[StringJoin.ConfigHelpNumber].StringValue; + rmProps.Help.Message = EISC.StringOutput[StringJoin.ConfigHelpMessage].StringValue; rmProps.Environment = new EssentialsEnvironmentPropertiesConfig(); // enabled defaults to false - rmProps.RoomPhoneNumber = EISC.StringOutput[504].StringValue; - rmProps.RoomURI = EISC.StringOutput[505].StringValue; + rmProps.RoomPhoneNumber = EISC.StringOutput[StringJoin.ConfigRoomPhoneNumber].StringValue; + rmProps.RoomURI = EISC.StringOutput[StringJoin.ConfigRoomURI].StringValue; rmProps.SpeedDials = new List(); // add speed dials as long as there are more - up to 4 + +#warning fix speed dials - 512-515 names, 516-519 numbers for (uint i = 512; i <= 519; i = i + 2) { var num = EISC.StringOutput[i].StringValue; @@ -427,10 +485,10 @@ namespace PepperDash.Essentials.Room.Cotija // This MAY need a check rmProps.AudioCodecKey = "audioCodec"; - rmProps.VideoCodecKey = null; // "videoCodec"; + rmProps.VideoCodecKey = "videoCodec"; // volume control names - var volCount = EISC.UShortOutput[701].UShortValue; + var volCount = EISC.UShortOutput[UshortJoin.VolumeSliderCount].UShortValue; //// use Volumes object or? //rmProps.VolumeSliderNames = new List(); @@ -458,24 +516,39 @@ namespace PepperDash.Essentials.Room.Cotija co.SourceLists = new Dictionary>(); var newSl = new Dictionary(); - // add sources... - for (uint i = 0; i<= 19; i++) + // add "none" source if VTC present + + if (!string.IsNullOrEmpty(rmProps.VideoCodecKey)) { - var name = EISC.StringOutput[601 + i].StringValue; + var codecOsd = new SourceListItem() + { + Name = "None", + IncludeInSourceList = true, + Order = 1, + Type = eSourceListItemType.Route, + SourceKey = "" + }; + newSl.Add("Source-None", codecOsd); + } + // add sources... + for (uint i = 0; i <= 19; i++) + { + var name = EISC.StringOutput[StringJoin.SourceNameJoinStart + i].StringValue; if(string.IsNullOrEmpty(name)) break; - var icon = EISC.StringOutput[651 + i].StringValue; - var key = EISC.StringOutput[671 + i].StringValue; - - var type = EISC.StringOutput[701 + i].StringValue; + var icon = EISC.StringOutput[StringJoin.SourceIconJoinStart + i].StringValue; + var key = EISC.StringOutput[StringJoin.SourceKeyJoinStart + i].StringValue; + var type = EISC.StringOutput[StringJoin.SourceTypeJoinStart + i].StringValue; + var disableShare = EISC.BooleanOutput[BoolJoin.SourceShareDisableStartJoin + i].BoolValue; Debug.Console(0, this, "Adding source {0} '{1}'", key, name); var newSLI = new SourceListItem{ Icon = icon, Name = name, - Order = (int)i + 1, + Order = (int)i + 10, SourceKey = key, - Type = eSourceListItemType.Route + Type = eSourceListItemType.Route, + DisableCodecSharing = disableShare, }; newSl.Add(key, newSLI); @@ -535,7 +608,68 @@ namespace PepperDash.Essentials.Room.Cotija Properties = JToken.FromObject(acProps) }; co.Devices.Add(acConf); - } + } + + if (!string.IsNullOrEmpty(rmProps.VideoCodecKey)) + { +#warning Break out these video codec favs + var favs = new List(); + for (uint i = 0; i < 4; i++) + { + if (!EISC.GetBool(BoolJoin.SpeedDialVisibleStartJoin + i)) + { + break; + } + favs.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 + }); + } + + + + // cameras + var camsProps = new List(); + for (uint i = 0; i < 9; i++) + { + var name = EISC.GetString(i + StringJoin.CameraNearNameStart); + if (!string.IsNullOrEmpty(name)) + { + camsProps.Add(new + { + name = name, + selector = "camera" + (i + 1), + }); + } + } + var farName = EISC.GetString(StringJoin.CameraFarName); + if (!string.IsNullOrEmpty(farName)) + { + camsProps.Add(new + { + name = farName, + selector = "cameraFar", + }); + } + + var props = new + { + favorites = favs, + cameras = camsProps, + }; + var str = "videoCodec"; + var conf = new DeviceConfig() + { + Group = str, + Key = str, + Name = str, + Type = str, + Properties = JToken.FromObject(props) + }; + co.Devices.Add(conf); + } Debug.Console(0, this, "******* CONFIG FROM DDVC: \r{0}", JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented)); diff --git a/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs b/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs index 35acefc6..525983fe 100644 --- a/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs +++ b/PepperDashEssentials/AppServer/RoomBridges/CotijaEssentialsHuddleSpaceRoomBridge.cs @@ -12,6 +12,7 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Room.Cotija; using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Devices.Common.AudioCodec; namespace PepperDash.Essentials { @@ -22,6 +23,8 @@ namespace PepperDash.Essentials public VideoCodecBaseMessenger VCMessenger { get; private set; } + public AudioCodecBaseMessenger ACMessenger { get; private set; } + /// /// /// @@ -92,29 +95,25 @@ namespace PepperDash.Essentials sscRoom.CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSingleSourceChange); var vcRoom = Room as IHasVideoCodec; - if (vcRoom != null) + if (vcRoom != null && vcRoom.VideoCodec != null) { var codec = vcRoom.VideoCodec; - VCMessenger = new VideoCodecBaseMessenger(vcRoom.VideoCodec, "/device/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 acRoom = Room as IHasAudioCodec; + if (acRoom != null && acRoom.AudioCodec != null) + { + var codec = acRoom.AudioCodec; + var key = acRoom.AudioCodec.Key + "-" + parent.Key; + ACMessenger = new AudioCodecBaseMessenger(key, acRoom.AudioCodec, "/device/audioCodec"); + ACMessenger.RegisterWithAppServer(Parent); + } + var defCallRm = Room as IRunDefaultCallRoute; if (defCallRm != null) { @@ -169,18 +168,18 @@ namespace PepperDash.Essentials }); } - /// - /// Handler for codec changes - /// - void codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) - { - PostStatusMessage(new - { - calls = GetCallsMessageObject(), - //vtc = GetVtcCallsMessageObject() - }); + ///// + ///// Handler for codec changes + ///// + //void codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) + //{ + // PostStatusMessage(new + // { + // calls = GetCallsMessageObject(), + // //vtc = GetVtcCallsMessageObject() + // }); - } + //} /// /// Helper for posting status message @@ -417,7 +416,7 @@ namespace PepperDash.Essentials if (rmVc != null) { var vc = rmVc.CurrentVolumeControls as IBasicVolumeWithFeedback; - if (rmVc != null) + if (vc != null) { volumes.Master = new Volume("master", vc.VolumeLevelFeedback.UShortValue, vc.MuteFeedback.BoolValue, "Volume", true, ""); } @@ -425,52 +424,52 @@ namespace PepperDash.Essentials PostStatusMessage(new { - calls = GetCallsMessageObject(), + //calls = GetCallsMessageObject(), isOn = room.OnFeedback.BoolValue, selectedSourceKey = sourceKey, - vtc = GetVtcCallsMessageObject(), + //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 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; - } + ///// + ///// Helper method to build call status for vtc + ///// + ///// + //object GetVtcCallsMessageObject() + //{ + // var callRm = Room as IHasVideoCodec; + // object vtc = null; + // if (callRm != null) + // { + // var codec = callRm.VideoCodec; + // vtc = new + // { + // isInCall = codec.IsInCall, + // calls = codec.ActiveCalls + // }; + // } + // return vtc; + //} } /// diff --git a/PepperDashEssentials/Bridges/._EssentialsLightsBridge.cs b/PepperDashEssentials/Bridges/._EssentialsLightsBridge.cs new file mode 100644 index 00000000..069fb4b1 Binary files /dev/null and b/PepperDashEssentials/Bridges/._EssentialsLightsBridge.cs differ diff --git a/PepperDashEssentials/Bridges/BridgeBase.cs b/PepperDashEssentials/Bridges/BridgeBase.cs index cf2d2f27..79f96c71 100644 --- a/PepperDashEssentials/Bridges/BridgeBase.cs +++ b/PepperDashEssentials/Bridges/BridgeBase.cs @@ -5,9 +5,17 @@ using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro.EthernetCommunication; +using Newtonsoft.Json; + using PepperDash.Core; using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Lighting; using PepperDash.Essentials.Core.Devices; +using PepperDash.Essentials.Devices.Common; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.CrestronIO; +using PepperDash.Essentials.DM; +//using PepperDash.Essentials.Devices.Common.Cameras; namespace PepperDash.Essentials.Bridges { @@ -44,17 +52,93 @@ namespace PepperDash.Essentials.Bridges /// public class EiscApi : BridgeApi { + public EiscApiPropertiesConfig PropertiesConfig { get; private set; } + public ThreeSeriesTcpIpEthernetIntersystemCommunications Eisc { get; private set; } - public EiscApi(string key, uint ipid, string hostname) : - base(key) + public EiscApi(DeviceConfig dc) : + base(dc.Key) { - Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(ipid, hostname, Global.ControlSystem); + PropertiesConfig = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(PropertiesConfig.Control.IpIdInt, PropertiesConfig.Control.TcpSshProperties.Address, Global.ControlSystem); Eisc.SigChange += new Crestron.SimplSharpPro.DeviceSupport.SigEventHandler(Eisc_SigChange); Eisc.Register(); + + AddPostActivationAction( () => + { + Debug.Console(1, this, "Linking Devices..."); + + foreach (var d in PropertiesConfig.Devices) + { + var device = DeviceManager.GetDeviceForKey(d.DeviceKey); + + if (device != null) + { + 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 CameraBase) + //{ + // (device as CameraBase).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + // continue; + //} + //else if (device is PepperDash.Essentials.Core.TwoWayDisplayBase) + //{ + // (device as TwoWayDisplayBase).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; + } + //else if (device is LightingBase) + //{ + // (device as LightingBase).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + // continue; + //} + //else if (device is DigitalLogger) + //{ + // (device as DigitalLogger).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + // continue; + //} + } + } + + Debug.Console(1, this, "Devices Linked."); + }); } /// @@ -65,7 +149,7 @@ namespace PepperDash.Essentials.Bridges void Eisc_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) { if (Debug.Level >= 1) - Debug.Console(1, this, "BridgeApi EISC change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); + Debug.Console(1, this, "EiscApi 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); @@ -76,138 +160,27 @@ namespace PepperDash.Essentials.Bridges } } - - - - /// - /// Defines each type and it's matching API type - /// - public static class DeviceApiFactory + public class EiscApiPropertiesConfig { - public static Dictionary TypeMap = new Dictionary + [JsonProperty("control")] + public EssentialsControlPropertiesConfig Control { get; set; } + + [JsonProperty("devices")] + public List Devices { get; set; } + + + public class ApiDevice { - { typeof(DmChassisController), typeof(DmChassisControllerApi) }, - { typeof(IBasicCommunication), typeof(IBasicCommunicationApi) } - //{ typeof(SomeShittyDisplayController), typeof(SomeShittyDisplayControllerApi) } - }; - } + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + [JsonProperty("joinStart")] + public uint JoinStart { get; set; } - /// - /// 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 } - }; + [JsonProperty("joinMapKey")] + public string JoinMapKey { get; set; } } - /// - /// 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); - } - } - - - - public class DmChassisController : Device - { - public DmChassisController(string key) - : base(key) - { - - } - - public void SetInput(int input) - { - Debug.Console(2, this, "Dm Chassis {0}, input {1}", Key, input); - } - } - - /// - /// 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); - } } diff --git a/PepperDashEssentials/Bridges/BridgeFactory.cs b/PepperDashEssentials/Bridges/BridgeFactory.cs index 97cc6fce..8b9ba75c 100644 --- a/PepperDashEssentials/Bridges/BridgeFactory.cs +++ b/PepperDashEssentials/Bridges/BridgeFactory.cs @@ -9,176 +9,133 @@ 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(PepperDash.Essentials.Core.Config.DeviceConfig dc) - { - // ? why is this static JTA 2018-06-13? +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 { }; - JsonConvert.DeserializeAnonymousType(dc.Properties.ToString(), propAnon); + 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(); + var typeName = dc.Type.ToLower(); + var groupName = dc.Group.ToLower(); - Debug.Console(0, "Name {0}, Key {1}, Type {2}, Properties {3}", name, key, type, properties.ToString()); - if (typeName == "dm") - { - return new DmBridge(key, name, properties); + //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 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); } - else if (typeName == "comm") - { - return new CommBridge(key, name, properties); - } - else - return null; } - } - public class DmBridge : Device - { - public EiscBridgeProperties Properties { get; private set; } + foreach (var deviceKey in Properties.CommDevices) + { + var device = DeviceManager.GetDeviceForKey(deviceKey); - 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)); - } + if (device != null) + { + Debug.Console(0, "deviceKey {0} Found in Device Manager", device.Key); + CommDevices.Add(device as IBasicCommunication); } - - 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); - } + else + { + Debug.Console(0, "deviceKey {0} Not Found in Device Manager", deviceKey); } - - Debug.Console(0, "Bridge {0} Activated", this.Name); - return true; } + + // Iterate through all the CommDevices and link up their Actions and Feedbacks + + 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 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 EiscProperties + { + public string IpId { get; set; } + public string Hostname { get; set; } } +} - public class CommBridgeProperties : EiscBridgeProperties - { - public List CommDevices { get; set; } - } +public class CommBridgeProperties : EiscBridgeProperties +{ + public List CommDevices { get; set; } +} - public enum eApiType { Eisc = 0 } +public enum eApiType { Eisc = 0 } - public class BridgeApiEisc - { - public uint Ipid { get; private set; } - public ThreeSeriesTcpIpEthernetIntersystemCommunications Eisc { get; private set; } +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); - } + 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); } -} \ No newline at end of file + 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/Bridges.BridgeFactory.cs b/PepperDashEssentials/Bridges/Bridges.BridgeFactory.cs new file mode 100644 index 00000000..18dd0c71 --- /dev/null +++ b/PepperDashEssentials/Bridges/Bridges.BridgeFactory.cs @@ -0,0 +1,90 @@ +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 Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.EthernetCommunication; +using PepperDash.Essentials.Bridges; + +namespace PepperDash.Essentials { + public class BridgeFactory { + public static IKeyed GetDevice(PepperDash.Essentials.Core.Config.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 { }; + JsonConvert.DeserializeAnonymousType(dc.Properties.ToString(), propAnon); + + 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 == "essentialdm") + { + return new EssentialDM(key, name, properties); + } + else if (typeName == "essentialcomm") + { + Debug.Console(2, "Launch Essential Comm"); + return new EssentialComm(key, name, properties); + } + else if (typeName == "essentialdsp") + { + Debug.Console(2, "Launch EssentialDsp"); + return new EssentialDsp(key, name, properties); + } + else if (typeName == "essentialstvone") + { + Debug.Console(2, "Launch essentialstvone"); + return new EssentialsTVOne(key, name, properties); + } + else if (typeName == "essentialslighting") + { + Debug.Console(2, "Launch essentialslighting"); + return new EssentialsLightsBridge(key, name, properties); + } + else if (typeName == "eiscapi") + { + return new EiscApi(dc); + } + return null; + } + } + public class BridgeApiEisc { + public uint Ipid; + public ThreeSeriesTcpIpEthernetIntersystemCommunications Eisc; + public BridgeApiEisc(string ipid) { + Ipid = (UInt32)int.Parse(ipid, System.Globalization.NumberStyles.HexNumber); + Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(Ipid, "127.0.0.2", Global.ControlSystem); + Eisc.Register(); + Eisc.SigChange += Eisc_SigChange; + Debug.Console(2, "BridgeApiEisc Created at Ipid {0}", ipid); + } + void Eisc_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) { + if (Debug.Level >= 1) + Debug.Console(2, "DDVC EISC 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/CameraControllerBridge.cs b/PepperDashEssentials/Bridges/CameraControllerBridge.cs new file mode 100644 index 00000000..a0bdfdd2 --- /dev/null +++ b/PepperDashEssentials/Bridges/CameraControllerBridge.cs @@ -0,0 +1,187 @@ +//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.Devices.Common; + +//namespace PepperDash.Essentials.Bridges +//{ +// public static class CameraControllerApiExtensions +// { + +// public static BasicTriList _TriList; +// public static CameraControllerJoinMap JoinMap; +// public static void LinkToApi(this PepperDash.Essentials.Devices.Common.Cameras.CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey) +// { +// JoinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as CameraControllerJoinMap; + +// _TriList = trilist; +// if (JoinMap == null) +// { +// JoinMap = new CameraControllerJoinMap(); +// } + +// JoinMap.OffsetJoinNumbers(joinStart); +// Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); +// Debug.Console(0, "Linking to Bridge Type {0}", cameraDevice.GetType().Name.ToString()); + +// var commMonitor = cameraDevice as ICommunicationMonitor; +// commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[JoinMap.IsOnline]); + + +// trilist.SetBoolSigAction(JoinMap.Left, (b) => +// { +// if (b) +// { +// cameraDevice.PanLeft(); +// } +// else +// { +// cameraDevice.Stop(); +// } +// }); +// trilist.SetBoolSigAction(JoinMap.Right, (b) => +// { +// if (b) +// { +// cameraDevice.PanRight(); +// } +// else +// { +// cameraDevice.Stop(); +// } +// }); + +// trilist.SetBoolSigAction(JoinMap.Up, (b) => +// { +// if (b) +// { +// cameraDevice.TiltUp(); +// } +// else +// { +// cameraDevice.Stop(); +// } +// }); +// trilist.SetBoolSigAction(JoinMap.Down, (b) => +// { +// if (b) +// { +// cameraDevice.TiltDown(); +// } +// else +// { +// cameraDevice.Stop(); +// } +// }); + +// trilist.SetBoolSigAction(JoinMap.ZoomIn, (b) => +// { +// if (b) +// { +// cameraDevice.ZoomIn(); +// } +// else +// { +// cameraDevice.Stop(); +// } +// }); + +// trilist.SetBoolSigAction(JoinMap.ZoomOut, (b) => +// { +// if (b) +// { +// cameraDevice.ZoomOut(); +// } +// else +// { +// cameraDevice.Stop(); +// } +// }); + + +// if (cameraDevice.GetType().Name.ToString().ToLower() == "cameravisca") +// { +// var viscaCamera = cameraDevice as PepperDash.Essentials.Devices.Common.Cameras.CameraVisca; +// trilist.SetSigTrueAction(JoinMap.PowerOn, () => viscaCamera.PowerOn()); +// trilist.SetSigTrueAction(JoinMap.PowerOff, () => viscaCamera.PowerOff()); + +// viscaCamera.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[JoinMap.PowerOn]); +// viscaCamera.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[JoinMap.PowerOff]); + +// viscaCamera.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[JoinMap.IsOnline]); +// for (int i = 0; i < JoinMap.NumberOfPresets; i++) +// { +// int tempNum = i; +// trilist.SetSigTrueAction((ushort)(JoinMap.PresetRecallOffset + tempNum), () => +// { +// viscaCamera.RecallPreset(tempNum); +// }); +// trilist.SetSigTrueAction((ushort)(JoinMap.PresetSaveOffset + tempNum), () => +// { +// viscaCamera.SavePreset(tempNum); +// }); +// } +// } + + + +// } + + +// } +// public class CameraControllerJoinMap : JoinMapBase +// { +// public uint IsOnline { get; set; } +// public uint PowerOff { get; set; } +// public uint PowerOn { get; set; } +// public uint Up { get; set; } +// public uint Down { get; set; } +// public uint Left { get; set; } +// public uint Right { get; set; } +// public uint ZoomIn { get; set; } +// public uint ZoomOut { get; set; } +// public uint PresetRecallOffset { get; set; } +// public uint PresetSaveOffset { get; set; } +// public uint NumberOfPresets { get; set; } + +// public CameraControllerJoinMap() +// { +// // Digital +// IsOnline = 9; +// PowerOff = 8; +// PowerOn = 7; +// Up = 1; +// Down = 2; +// Left = 3; +// Right = 4; +// ZoomIn = 5; +// ZoomOut = 6; +// PresetRecallOffset = 10; +// PresetSaveOffset = 30; +// NumberOfPresets = 5; +// // Analog +// } + +// public override void OffsetJoinNumbers(uint joinStart) +// { +// var joinOffset = joinStart - 1; + +// IsOnline = IsOnline + joinOffset; +// PowerOff = PowerOff + joinOffset; +// PowerOn = PowerOn + joinOffset; +// Up = Up + joinOffset; +// Down = Down + joinOffset; +// Left = Left + joinOffset; +// Right = Right + joinOffset; +// ZoomIn = ZoomIn + joinOffset; +// ZoomOut = ZoomOut + joinOffset; +// PresetRecallOffset = PresetRecallOffset + joinOffset; +// PresetSaveOffset = PresetSaveOffset + joinOffset; +// } +// } +//} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DigitalLoggerBridge.cs b/PepperDashEssentials/Bridges/DigitalLoggerBridge.cs new file mode 100644 index 00000000..ef529165 --- /dev/null +++ b/PepperDashEssentials/Bridges/DigitalLoggerBridge.cs @@ -0,0 +1,77 @@ +//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.Devices.Common; + +//namespace PepperDash.Essentials.Bridges +//{ +// public static class DigitalLoggerApiExtensions +// { +// public static void LinkToApi(this DigitalLogger DigitalLogger, BasicTriList trilist, uint joinStart, string joinMapKey) +// { +// var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as DigitalLoggerJoinMap; + +// if (joinMap == null) +// joinMap = new DigitalLoggerJoinMap(); + +// joinMap.OffsetJoinNumbers(joinStart); +// Debug.Console(1, DigitalLogger, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); +// for (uint i = 1; i <= DigitalLogger.CircuitCount; i++) +// { +// var circuit = i; +// DigitalLogger.CircuitNameFeedbacks[circuit - 1].LinkInputSig(trilist.StringInput[joinMap.CircuitNames + circuit]); +// DigitalLogger.CircuitIsCritical[circuit - 1].LinkInputSig(trilist.BooleanInput[joinMap.CircuitIsCritical + circuit]); +// DigitalLogger.CircuitState[circuit - 1].LinkInputSig(trilist.BooleanInput[joinMap.CircuitState + circuit]); +// trilist.SetSigTrueAction(joinMap.CircuitCycle + circuit, () => DigitalLogger.CycleCircuit(circuit - 1)); +// trilist.SetSigTrueAction(joinMap.CircuitOnCmd + circuit, () => DigitalLogger.TurnOnCircuit(circuit - 1)); +// trilist.SetSigTrueAction(joinMap.CircuitOffCmd + circuit, () => DigitalLogger.TurnOffCircuit(circuit - 1)); + +// } +// } +// } +// public class DigitalLoggerJoinMap : JoinMapBase +// { +// public uint IsOnline { get; set; } +// public uint CircuitNames { get; set; } +// public uint CircuitState { get; set; } +// public uint CircuitCycle { get; set; } +// public uint CircuitIsCritical { get; set; } +// public uint CircuitOnCmd { get; set; } +// public uint CircuitOffCmd { get; set; } +// public DigitalLoggerJoinMap() +// { +// // Digital +// IsOnline = 9; +// CircuitState = 0; +// CircuitCycle = 0; +// CircuitIsCritical = 10; +// CircuitOnCmd = 10; +// CircuitOffCmd = 20; +// // Serial +// CircuitNames = 0; +// // Analog +// } + +// public override void OffsetJoinNumbers(uint joinStart) +// { +// var joinOffset = joinStart - 1; + +// IsOnline = IsOnline + joinOffset; +// CircuitNames = CircuitNames + joinOffset; +// CircuitState = CircuitState + joinOffset; +// CircuitCycle = CircuitCycle + joinOffset; +// CircuitIsCritical = CircuitIsCritical + joinOffset; +// CircuitOnCmd = CircuitOnCmd + joinOffset; +// CircuitOffCmd = CircuitOffCmd + joinOffset; + + + +// } +// } + +//} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DisplayControllerBridge.cs b/PepperDashEssentials/Bridges/DisplayControllerBridge.cs new file mode 100644 index 00000000..da91ee87 --- /dev/null +++ b/PepperDashEssentials/Bridges/DisplayControllerBridge.cs @@ -0,0 +1,171 @@ +//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.Devices.Common; + +//namespace PepperDash.Essentials.Bridges +//{ +// public static class DisplayControllerApiExtensions +// { + +// public static BasicTriList _TriList; +// public static DisplayControllerJoinMap JoinMap; +// public static int InputNumber; +// public static IntFeedback InputNumberFeedback; +// public static List InputKeys = new List(); +// public static void LinkToApi(this PepperDash.Essentials.Core.TwoWayDisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey) +// { +// JoinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as DisplayControllerJoinMap; +// _TriList = trilist; + +// if (JoinMap == null) +// { +// JoinMap = new DisplayControllerJoinMap(); +// } + +// JoinMap.OffsetJoinNumbers(joinStart); +// Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); +// Debug.Console(0, "Linking to Bridge Type {0}", displayDevice.GetType().Name.ToString()); + +// trilist.StringInput[JoinMap.Name].StringValue = displayDevice.GetType().Name.ToString(); + +// InputNumberFeedback = new IntFeedback(() => { return InputNumber;}); +// InputNumberFeedback.LinkInputSig(trilist.UShortInput[JoinMap.InputSelect]); +// var commMonitor = displayDevice as ICommunicationMonitor; +// commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[JoinMap.IsOnline]); + +// // Poewer Off +// trilist.SetSigTrueAction(JoinMap.PowerOff, () => +// { +// InputNumber = 102; +// InputNumberFeedback.FireUpdate(); +// displayDevice.PowerOff(); +// }); + +// displayDevice.PowerIsOnFeedback.OutputChange += new EventHandler(PowerIsOnFeedback_OutputChange); +// displayDevice.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[JoinMap.PowerOff]); + +// // Poewer On +// trilist.SetSigTrueAction(JoinMap.PowerOn, () => +// { +// InputNumber = 0; +// InputNumberFeedback.FireUpdate(); +// displayDevice.PowerOn(); +// }); + + +// displayDevice.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[JoinMap.PowerOn]); + +// int count = 1; +// foreach (var input in displayDevice.InputPorts) +// { +// InputKeys.Add(input.Key.ToString()); +// var tempKey = InputKeys.ElementAt(count - 1); +// trilist.SetSigTrueAction((ushort)(JoinMap.InputSelectOffset + count), () => { displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector); }); +// trilist.StringInput[(ushort)(JoinMap.InputNamesOffset + count)].StringValue = input.Key.ToString(); +// count++; +// } + +// displayDevice.CurrentInputFeedback.OutputChange += new EventHandler(CurrentInputFeedback_OutputChange); +// trilist.SetUShortSigAction(JoinMap.InputSelect, (a) => +// { +// if (a == 0) +// { +// displayDevice.PowerOff(); +// InputNumber = 0; +// } +// else if (a > 0 && a < displayDevice.InputPorts.Count && a != InputNumber) +// { +// displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(a - 1).Selector); +// InputNumber = a; +// } +// else if (a == 102) +// { +// displayDevice.PowerToggle(); + +// } +// InputNumberFeedback.FireUpdate(); +// }); + +// } + +// static void CurrentInputFeedback_OutputChange(object sender, FeedbackEventArgs e) +// { + +// Debug.Console(0, "CurrentInputFeedback_OutputChange {0}", e.StringValue); + +// } + +// static void PowerIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) +// { + +// // Debug.Console(0, "PowerIsOnFeedback_OutputChange {0}", e.BoolValue); +// if (!e.BoolValue) +// { +// InputNumber = 102; +// InputNumberFeedback.FireUpdate(); + +// } +// else +// { +// InputNumber = 0; +// InputNumberFeedback.FireUpdate(); +// } +// } + + + + +// } +// public class DisplayControllerJoinMap : JoinMapBase +// { +// public uint Name { get; set; } +// public uint InputNamesOffset { get; set; } +// public uint InputSelectOffset { get; set; } +// public uint IsOnline { get; set; } +// public uint PowerOff { get; set; } +// public uint InputSelect { get; set; } +// public uint PowerOn { get; set; } +// public uint SelectScene { get; set; } +// public uint LightingSceneOffset { get; set; } +// public uint ButtonVisibilityOffset { get; set; } +// public uint IntegrationIdSet { get; set; } + +// public DisplayControllerJoinMap() +// { +// // Digital +// IsOnline = 50; +// PowerOff = 1; +// PowerOn = 2; +// InputSelect = 4; +// IntegrationIdSet = 1; +// LightingSceneOffset = 10; +// ButtonVisibilityOffset = 40; +// Name = 1; +// InputNamesOffset = 10; +// InputSelectOffset = 4; +// // Analog +// } + +// public override void OffsetJoinNumbers(uint joinStart) +// { +// var joinOffset = joinStart - 1; + +// IsOnline = IsOnline + joinOffset; +// PowerOff = PowerOff + joinOffset; +// PowerOn = PowerOn + joinOffset; +// SelectScene = SelectScene + joinOffset; +// LightingSceneOffset = LightingSceneOffset + joinOffset; +// ButtonVisibilityOffset = ButtonVisibilityOffset + joinOffset; +// Name = Name + joinOffset; +// InputNamesOffset = InputNamesOffset + joinOffset; +// InputSelectOffset = InputSelectOffset + joinOffset; + +// } +// } +//} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs b/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs new file mode 100644 index 00000000..d864787c --- /dev/null +++ b/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs @@ -0,0 +1,130 @@ +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))); + if (dmChassis.TxDictionary.ContainsKey(ioSlot)) + { + Debug.Console(2, "Creating Tx Feedbacks {0}", ioSlot); + var TxKey = dmChassis.TxDictionary[ioSlot]; + var TxDevice = DeviceManager.GetDeviceForKey(TxKey) as DmTxControllerBase; + TxDevice.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.InputEndpointOnline + ioSlot]); + // TxDevice.AnyVideoInput.VideoStatus.VideoSyncFeedback.LinkInputSig(trilist.BooleanInput[joinMap.InputEndpointOnline + ioSlot]); + // trilist.SetUShortSigAction((ApiMap.HdcpSupport[ioSlot]), u => TxDevice.SetHdcpSupportAll((ePdtHdcpSupport)(u))); + // TxDevice.HdcpSupportAllFeedback.LinkInputSig(trilist.UShortInput[joinMap. + ioSlot]); + // trilist.UShortInput[ApiMap.HdcpSupportCapability[ioSlot]].UShortValue = TxDevice.HdcpSupportCapability; + } + else + { + // dmChassis.VideoInputSyncFeedbacks[ioSlot].LinkInputSig(trilist.BooleanInput[ApiMap.TxVideoSyncStatus[ioSlot]]); + } + if (dmChassis.RxDictionary.ContainsKey(ioSlot)) + { + Debug.Console(2, "Creating Rx Feedbacks {0}", ioSlot); + var RxKey = dmChassis.RxDictionary[ioSlot]; + var RxDevice = DeviceManager.GetDeviceForKey(RxKey) as DmRmcControllerBase; + RxDevice.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.OutputEndpointOnline + ioSlot]); + } + // 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/DspControllerBridge.cs b/PepperDashEssentials/Bridges/DspControllerBridge.cs new file mode 100644 index 00000000..9dd018fb --- /dev/null +++ b/PepperDashEssentials/Bridges/DspControllerBridge.cs @@ -0,0 +1,120 @@ +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.Devices.Common; + +namespace PepperDash.Essentials.Bridges +{ + public static class SamsungDisplayControllerApiExtensions + { + public static void LinkToApi(this PepperDash.Essentials.Core.TwoWayDisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as DisplayControllerJoinMap; + + if (joinMap == null) + { + joinMap = new DisplayControllerJoinMap(); + } + + joinMap.OffsetJoinNumbers(joinStart); + Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + Debug.Console(0, "Linking to lighting Type {0}", displayDevice.GetType().Name.ToString()); + + var commMonitor = displayDevice as ICommunicationMonitor; + commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline]); + + + // Poewer Off + trilist.SetSigTrueAction(joinMap.PowerOff, () => displayDevice.PowerOff()); + displayDevice.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff]); + + // Poewer On + trilist.SetSigTrueAction(joinMap.PowerOn, () => displayDevice.PowerOn()); + displayDevice.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn]); + + // GenericLighitng Actions & FeedBack + + // int sceneIndex = 1; + /* + foreach (var scene in displayDevice.LightingScenes) + { + var tempIndex = sceneIndex - 1; + //trilist.SetSigTrueAction((uint)(joinMap.LightingSceneOffset + sceneIndex), () => displayDevice.SelectScene(displayDevice.LightingScenes[tempIndex])); + scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.LightingSceneOffset + sceneIndex)]); + trilist.StringInput[(uint)(joinMap.LightingSceneOffset + sceneIndex)].StringValue = scene.Name; + trilist.BooleanInput[(uint)(joinMap.ButtonVisibilityOffset + sceneIndex)].BoolValue = true; + sceneIndex++; + } + + if (displayDevice.GetType().Name.ToString() == "LutronQuantumArea") + { + var lutronDevice = displayDevice as PepperDash.Essentials.Devices.Common.Environment.Lutron.LutronQuantumArea; + lutronDevice.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline]); + trilist.SetStringSigAction(joinMap.IntegrationIdSet, s => lutronDevice.IntegrationId = s); + } + */ + //ApiEisc.Eisc.SetStringSigAction(ApiMap.integrationID, (s) => { lutronLights.IntegrationId = s; }); + + + /* + var lutronLights = displayDevice as PepperDash.Essentials.Devices.Common.Environment.Lutron.LutronQuantumArea; + + + for (uint i = 1; i <= lightingBase.CircuitCount; i++) + { + var circuit = i; + lightingBase.CircuitNameFeedbacks[circuit - 1].LinkInputSig(trilist.StringInput[joinMap.CircuitNames + circuit]); + lightingBase.CircuitIsCritical[circuit - 1].LinkInputSig(trilist.BooleanInput[joinMap.CircuitIsCritical + circuit]); + lightingBase.CircuitState[circuit - 1].LinkInputSig(trilist.BooleanInput[joinMap.CircuitState + circuit]); + trilist.SetSigTrueAction(joinMap.CircuitCycle + circuit, () => lightingBase.CycleCircuit(circuit - 1)); + trilist.SetSigTrueAction(joinMap.CircuitOnCmd + circuit, () => lightingBase.TurnOnCircuit(circuit - 1)); + trilist.SetSigTrueAction(joinMap.CircuitOffCmd + circuit, () => lightingBase.TurnOffCircuit(circuit - 1)); + + } + */ + } + } + public class DisplayControllerJoinMap : JoinMapBase + { + public uint IsOnline { get; set; } + public uint PowerOff { get; set; } + public uint PowerOn { get; set; } + public uint SelectScene { get; set; } + public uint LightingSceneOffset { get; set; } + public uint ButtonVisibilityOffset { get; set; } + public uint IntegrationIdSet { get; set; } + + public DisplayControllerJoinMap() + { + // Digital + IsOnline = 1; + PowerOff = 1; + PowerOn = 2; + SelectScene = 1; + IntegrationIdSet = 1; + LightingSceneOffset = 10; + ButtonVisibilityOffset = 40; + // Analog + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + IsOnline = IsOnline + joinOffset; + PowerOff = PowerOff + joinOffset; + PowerOn = PowerOn + joinOffset; + SelectScene = SelectScene + joinOffset; + LightingSceneOffset = LightingSceneOffset + joinOffset; + ButtonVisibilityOffset = ButtonVisibilityOffset + joinOffset; + + + + } + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/EssentialComms.cs b/PepperDashEssentials/Bridges/EssentialComms.cs new file mode 100644 index 00000000..461715fc --- /dev/null +++ b/PepperDashEssentials/Bridges/EssentialComms.cs @@ -0,0 +1,144 @@ +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 Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.EthernetCommunication; +using Crestron.SimplSharpPro.CrestronThread; + +namespace PepperDash.Essentials { + public class EssentialCommConfig { + public string[] EiscApiIpids; + public EssentialCommCommConnectionConfigs[] CommConnections; + } + public class EssentialCommCommConnectionConfigs { + public uint joinNumber {get; set; } + public EssentialsControlPropertiesConfig control { get; set; } + } + + public class EssentialCommsPort { + public IBasicCommunication Comm; + public IntFeedback StatusFeedback; + public BoolFeedback ConnectedFeedback; + public List Outputs = new List(); + public String RxBuffer; + public EssentialCommsPort(EssentialsControlPropertiesConfig config, string keyPrefix) { + Comm = CommFactory.CreateCommForConfig(config, keyPrefix); + // var PortGather = new CommunicationGather(Comm, config.EndOfLineChar); + Comm.TextReceived += new EventHandler(Communication_TextReceived); + + var socket = Comm as ISocketStatus; + StatusFeedback = new IntFeedback(() => { return (int)socket.ClientStatus; }); + ConnectedFeedback = new BoolFeedback(() => { return Comm.IsConnected; }); + + if (socket != null) { + socket.ConnectionChange += new EventHandler(socket_ConnectionChange); + } else { + } + + } + void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) { + StatusFeedback.FireUpdate(); + ConnectedFeedback.FireUpdate(); + if (e.Client.IsConnected) { + // Tasks on connect + } else { + // Cleanup items from this session + } + } + void Communication_TextReceived(object sender, GenericCommMethodReceiveTextArgs args) { + try { + foreach (var Output in Outputs) { + Output.Api.Eisc.StringInput[Output.Join].StringValue = args.Text; + } + + } + catch (Exception) { + throw new FormatException(string.Format("ERROR:{0}")); + } + } + } + + public class EssentialComm : Device { + public EssentialCommConfig Properties; + + public CommunicationGather PortGather { get; private set; } + public List Apis {get; set;} + public Dictionary CommFeedbacks {get; private set; } + public StatusMonitorBase CommunicationMonitor { get; private set; } + public Dictionary CommDictionary { get; private set; } + + public EssentialComm(string key, string name, JToken properties) : base(key, name) { + Properties = JsonConvert.DeserializeObject(properties.ToString()); + CommFeedbacks = new Dictionary(); + CommDictionary = new Dictionary(); + Apis = new List(); + int commNumber = 1; + foreach (var commConfig in Properties.CommConnections) { + var commPort = new EssentialCommsPort(commConfig.control, string.Format("{0}-{1}", this.Key, commConfig.joinNumber)); + CommDictionary.Add(commConfig.joinNumber, commPort); + + commNumber++; + } + + foreach (var Ipid in Properties.EiscApiIpids) { + var ApiEisc = new BridgeApiEisc(Ipid); + Apis.Add(ApiEisc); + foreach (var commConnection in CommDictionary) { + Debug.Console(2, "Joining Api{0} to comm {1}", Ipid, commConnection.Key); + var tempComm = commConnection.Value; + var tempJoin = (uint)commConnection.Key; + EssentialComApiMap ApiMap = new EssentialComApiMap(ApiEisc, (uint)tempJoin); + + tempComm.Outputs.Add(ApiMap); + // Check for ApiMap Overide Values here + + ApiEisc.Eisc.SetBoolSigAction(tempJoin, b => {if (b) { tempComm.Comm.Connect(); } else { tempComm.Comm.Disconnect(); }}); + ApiEisc.Eisc.SetStringSigAction(tempJoin, s => tempComm.Comm.SendText(s)); + + tempComm.StatusFeedback.LinkInputSig(ApiEisc.Eisc.UShortInput[tempJoin]); + tempComm.ConnectedFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[tempJoin]); + + + + } + ApiEisc.Eisc.Register(); + } + } + + + + public override bool CustomActivate() + { + try { + + + + Debug.Console(2, "Name {0} Activated", this.Name); + return true; + } + catch (Exception e) { + Debug.Console(0, "Bridge {0}", e); + return false; + } + } + + + } + public class EssentialComApiMap { + public uint Join; + public BridgeApiEisc Api; + public uint connectJoin; + public EssentialComApiMap(BridgeApiEisc api, uint join) { + Join = join; + Api = api; + } + } + } \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/EssentialDM.cs b/PepperDashEssentials/Bridges/EssentialDM.cs new file mode 100644 index 00000000..f03f174b --- /dev/null +++ b/PepperDashEssentials/Bridges/EssentialDM.cs @@ -0,0 +1,150 @@ +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.Essentials.DM; +using PepperDash.Core; +using PepperDash.Essentials.Core.Routing; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.EthernetCommunication; +using Crestron.SimplSharpPro.DM; + +namespace PepperDash.Essentials { + public class EssentialDM : PepperDash.Core.Device { + public EssentialDMProperties Properties; + public List BridgeApiEiscs; + private PepperDash.Essentials.DM.DmChassisController DmSwitch; + private EssentialDMApiMap ApiMap = new EssentialDMApiMap(); + public EssentialDM(string key, string name, JToken properties) + : base(key, name) { + Properties = JsonConvert.DeserializeObject(properties.ToString()); + + + } + public override bool CustomActivate() { + // Create EiscApis + try { + foreach (var device in DeviceManager.AllDevices) { + if (device.Key == this.Properties.connectionDeviceKey) { + Debug.Console(2, "deviceKey {0} Matches", device.Key); + DmSwitch = DeviceManager.GetDeviceForKey(device.Key) as PepperDash.Essentials.DM.DmChassisController; + + } + + + else { + Debug.Console(2, "deviceKey {0} doesn't match", device.Key); + } + } + if (Properties.EiscApiIpids != null) { + + + foreach (string Ipid in Properties.EiscApiIpids) { + var ApiEisc = new BridgeApiEisc(Ipid); + for (uint x = 1; x <= DmSwitch.Chassis.NumberOfInputs;x++ ) { + uint tempX = x; + Debug.Console(2, "Creating EiscActions {0}", tempX); + + + ApiEisc.Eisc.SetUShortSigAction(ApiMap.OutputVideoRoutes[tempX], u => DmSwitch.ExecuteSwitch(u, tempX, eRoutingSignalType.Video)); + ApiEisc.Eisc.SetUShortSigAction(ApiMap.OutputAudioRoutes[tempX], u => DmSwitch.ExecuteSwitch(u, tempX, eRoutingSignalType.Audio)); + + + if (DmSwitch.TxDictionary.ContainsKey(tempX)) { + Debug.Console(2, "Creating Tx Feedbacks {0}", tempX); + var TxKey = DmSwitch.TxDictionary[tempX]; + var TxDevice = DeviceManager.GetDeviceForKey(TxKey) as DmTxControllerBase; + TxDevice.IsOnline.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.TxOnlineStatus[tempX]]); + TxDevice.AnyVideoInput.VideoStatus.VideoSyncFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.TxVideoSyncStatus[tempX]]); + ApiEisc.Eisc.SetUShortSigAction((ApiMap.HdcpSupport[tempX]), u => TxDevice.SetHdcpSupportAll((ePdtHdcpSupport)(u))); + TxDevice.HdcpSupportAllFeedback.LinkInputSig(ApiEisc.Eisc.UShortInput[ApiMap.HdcpSupport[tempX]]); + ApiEisc.Eisc.UShortInput[ApiMap.HdcpSupportCapability[tempX]].UShortValue = TxDevice.HdcpSupportCapability; + } + else { + DmSwitch.VideoInputSyncFeedbacks[tempX].LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.TxVideoSyncStatus[tempX]]); + } + if (DmSwitch.RxDictionary.ContainsKey(tempX)) { + Debug.Console(2, "Creating Rx Feedbacks {0}", tempX); + var RxKey = DmSwitch.RxDictionary[tempX]; + var RxDevice = DeviceManager.GetDeviceForKey(RxKey) as DmRmcControllerBase; + RxDevice.IsOnline.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.RxOnlineStatus[tempX]]); + } + // DmSwitch.InputEndpointOnlineFeedbacks[(ushort)tempOutputNum].LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.OutputVideoRoutes[tempOutputNum]]); + DmSwitch.VideoOutputFeedbacks[(ushort)tempX].LinkInputSig(ApiEisc.Eisc.UShortInput[ApiMap.OutputVideoRoutes[tempX]]); + DmSwitch.AudioOutputFeedbacks[(ushort)tempX].LinkInputSig(ApiEisc.Eisc.UShortInput[ApiMap.OutputAudioRoutes[tempX]]); + DmSwitch.InputNameFeedbacks[(ushort)tempX].LinkInputSig(ApiEisc.Eisc.StringInput[ApiMap.InputNames[tempX]]); + DmSwitch.OutputNameFeedbacks[(ushort)tempX].LinkInputSig(ApiEisc.Eisc.StringInput[ApiMap.OutputNames[tempX]]); + DmSwitch.OutputRouteNameFeedbacks[(ushort)tempX].LinkInputSig(ApiEisc.Eisc.StringInput[ApiMap.OutputRouteNames[tempX]]); + } + DmSwitch.IsOnline.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.ChassisOnline]); + ApiEisc.Eisc.Register(); + } + } + + + + Debug.Console(2, "Name {0} Activated", this.Name); + return true; + } + catch (Exception e) { + Debug.Console(2, "BRidge {0}", e); + return false; + } + } + } + public class EssentialDMProperties { + public string connectionDeviceKey; + public string[] EiscApiIpids; + + + } + + + public class EssentialDMApiMap { + public ushort ChassisOnline = 11; + public Dictionary OutputVideoRoutes; + public Dictionary OutputAudioRoutes; + public Dictionary TxOnlineStatus; + public Dictionary RxOnlineStatus; + public Dictionary TxVideoSyncStatus; + public Dictionary InputNames; + public Dictionary OutputNames; + public Dictionary OutputRouteNames; + public Dictionary HdcpSupport; + public Dictionary HdcpSupportCapability; + + public EssentialDMApiMap() { + OutputVideoRoutes = new Dictionary(); + OutputAudioRoutes = new Dictionary(); + TxOnlineStatus = new Dictionary(); + RxOnlineStatus = new Dictionary(); + TxVideoSyncStatus = new Dictionary(); + InputNames = new Dictionary(); + OutputNames = new Dictionary(); + OutputRouteNames = new Dictionary(); + HdcpSupport = new Dictionary(); + HdcpSupportCapability = new Dictionary(); + + for (uint x = 1; x <= 200; x++) { + // Debug.Console(0, "Init Value {0}", x); + uint tempNum = x; + HdcpSupportCapability[tempNum] = (ushort)(tempNum + 1200); + HdcpSupport[tempNum] = (ushort)(tempNum + 1000); + OutputVideoRoutes[tempNum] = (ushort)(tempNum + 100); + OutputAudioRoutes[tempNum] = (ushort)(tempNum + 300); + TxOnlineStatus[tempNum] = (ushort)(tempNum + 500); + RxOnlineStatus[tempNum] = (ushort)(tempNum + 700); + TxVideoSyncStatus[tempNum] = (ushort)(tempNum + 100); + InputNames[tempNum] = (ushort)(tempNum + 100); + OutputNames[tempNum] = (ushort)(tempNum + 300); + OutputRouteNames[tempNum] = (ushort)(tempNum + 2000); + } + } + } + } + \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/EssentialDsp.cs b/PepperDashEssentials/Bridges/EssentialDsp.cs new file mode 100644 index 00000000..d8aed442 --- /dev/null +++ b/PepperDashEssentials/Bridges/EssentialDsp.cs @@ -0,0 +1,217 @@ +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.Essentials.DM; +using PepperDash.Core; +using PepperDash.Essentials.Core.Routing; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.EthernetCommunication; +using Crestron.SimplSharpPro.DM; + +namespace PepperDash.Essentials { + public class EssentialDsp : PepperDash.Core.Device { + public EssentialDspProperties Properties; + public List BridgeApiEiscs; + private PepperDash.Essentials.Devices.Common.DSP.QscDsp Dsp; + private EssentialDspApiMap ApiMap = new EssentialDspApiMap(); + public EssentialDsp(string key, string name, JToken properties) + : base(key, name) { + Properties = JsonConvert.DeserializeObject(properties.ToString()); + + + } + public override bool CustomActivate() { + // Create EiscApis + + try + { + ICommunicationMonitor comm = null; + foreach (var device in DeviceManager.AllDevices) + { + if (device.Key == this.Properties.connectionDeviceKey) + { + if (!(device is ICommunicationMonitor)) + { + comm = device as ICommunicationMonitor; + } + Debug.Console(2, "deviceKey {0} Matches", device.Key); + Dsp = DeviceManager.GetDeviceForKey(device.Key) as PepperDash.Essentials.Devices.Common.DSP.QscDsp; + break; + } + else + { + Debug.Console(2, "deviceKey {0} doesn't match", device.Key); + + } + } + if (Properties.EiscApiIpids != null && Dsp != null) + { + foreach (string Ipid in Properties.EiscApiIpids) + { + var ApiEisc = new BridgeApiEisc(Ipid); + Debug.Console(2, "Connecting EiscApi {0} to {1}", ApiEisc.Ipid, Dsp.Name); + ushort x = 1; + if (comm != null) + { + comm.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.Online]); + } + foreach (var channel in Dsp.LevelControlPoints) + { + //var QscChannel = channel.Value as PepperDash.Essentials.Devices.Common.DSP.QscDspLevelControl; + Debug.Console(2, "QscChannel {0} connect", x); + + var genericChannel = channel.Value as IBasicVolumeWithFeedback; + if (channel.Value.Enabled) + { + ApiEisc.Eisc.StringInput[ApiMap.channelName[x]].StringValue = channel.Value.LevelCustomName; + ApiEisc.Eisc.UShortInput[ApiMap.channelType[x]].UShortValue = (ushort)channel.Value.Type; + + genericChannel.MuteFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.channelMuteToggle[x]]); + genericChannel.VolumeLevelFeedback.LinkInputSig(ApiEisc.Eisc.UShortInput[ApiMap.channelVolume[x]]); + + ApiEisc.Eisc.SetSigTrueAction(ApiMap.channelMuteToggle[x], () => genericChannel.MuteToggle()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.channelMuteOn[x], () => genericChannel.MuteOn()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.channelMuteOff[x], () => genericChannel.MuteOff()); + + ApiEisc.Eisc.SetBoolSigAction(ApiMap.channelVolumeUp[x], b => genericChannel.VolumeUp(b)); + ApiEisc.Eisc.SetBoolSigAction(ApiMap.channelVolumeDown[x], b => genericChannel.VolumeDown(b)); + + ApiEisc.Eisc.SetUShortSigAction(ApiMap.channelVolume[x], u => genericChannel.SetVolume(u)); + ApiEisc.Eisc.SetStringSigAction(ApiMap.presetString, s => Dsp.RunPreset(s)); + } + x++; + + } + x = 1; + foreach (var preset in Dsp.PresetList) + { + ApiEisc.Eisc.StringInput[ApiMap.presets[x]].StringValue = preset.label; + ApiEisc.Eisc.SetSigTrueAction(ApiMap.presets[x], () => Dsp.RunPresetNumber(x)); + x++; + } + foreach (var dialer in Dsp.Dialers) + { + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad0, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num0)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad1, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num1)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad2, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num2)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad3, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num3)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad4, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num4)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad5, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num5)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad6, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num6)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad7, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num7)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad8, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num8)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Keypad9, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Num9)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.KeypadStar, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Star)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.KeypadPound, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Pound)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.KeypadClear, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Clear)); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.KeypadBackspace, () => dialer.Value.SendKeypad(PepperDash.Essentials.Devices.Common.DSP.QscDspDialer.eKeypadKeys.Backspace)); + + ApiEisc.Eisc.SetSigTrueAction(ApiMap.Dial, () => dialer.Value.Dial()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.DoNotDisturbToggle, () => dialer.Value.DoNotDisturbToggle()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.DoNotDisturbOn, () => dialer.Value.DoNotDisturbOn()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.DoNotDisturbOff, () => dialer.Value.DoNotDisturbOff()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.AutoAnswerToggle, () => dialer.Value.AutoAnswerToggle()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.AutoAnswerOn, () => dialer.Value.AutoAnswerOn()); + ApiEisc.Eisc.SetSigTrueAction(ApiMap.AutoAnswerOff, () => dialer.Value.AutoAnswerOff()); + + dialer.Value.DoNotDisturbFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.DoNotDisturbToggle]); + dialer.Value.DoNotDisturbFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.DoNotDisturbOn]); + dialer.Value.DoNotDisturbFeedback.LinkComplementInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.DoNotDisturbOff]); + + dialer.Value.AutoAnswerFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.AutoAnswerToggle]); + dialer.Value.AutoAnswerFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.AutoAnswerOn]); + dialer.Value.AutoAnswerFeedback.LinkComplementInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.AutoAnswerOff]); + + dialer.Value.OffHookFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.Dial]); + dialer.Value.DialStringFeedback.LinkInputSig(ApiEisc.Eisc.StringInput[ApiMap.DialString]); + } + } + } + + + + + Debug.Console(2, "Name {0} Activated", this.Name); + return true; + } + catch (Exception e) { + Debug.Console(0, "Bridge {0}", e); + return false; + } + } + } + public class EssentialDspProperties { + public string connectionDeviceKey; + public string[] EiscApiIpids; + + + } + + + public class EssentialDspApiMap { + public ushort Online = 1; + public ushort presetString = 2000; + public Dictionary channelMuteToggle; + public Dictionary channelMuteOn; + public Dictionary channelMuteOff; + public Dictionary channelVolume; + public Dictionary channelType; + public Dictionary channelName; + public Dictionary channelVolumeUp; + public Dictionary channelVolumeDown; + public Dictionary presets; + public ushort DialString = 3100; + public ushort Keypad0 = 3110; + public ushort Keypad1 = 3111; + public ushort Keypad2 = 3112; + public ushort Keypad3 = 3113; + public ushort Keypad4 = 3114; + public ushort Keypad5 = 3115; + public ushort Keypad6 = 3116; + public ushort Keypad7 = 3117; + public ushort Keypad8 = 3118; + public ushort Keypad9 = 3119; + public ushort KeypadStar = 3120; + public ushort KeypadPound = 3121; + public ushort KeypadClear = 3122; + public ushort KeypadBackspace = 3123; + public ushort Dial = 3124; + public ushort DoNotDisturbToggle = 3132; + public ushort DoNotDisturbOn = 3133; + public ushort DoNotDisturbOff = 3134; + public ushort AutoAnswerToggle = 3127; + public ushort AutoAnswerOn = 3125; + public ushort AutoAnswerOff = 3126; + + public EssentialDspApiMap() { + channelMuteToggle = new Dictionary(); + channelMuteOn = new Dictionary(); + channelMuteOff = new Dictionary(); + channelVolume = new Dictionary(); + channelName = new Dictionary(); + channelType = new Dictionary(); + presets = new Dictionary(); + channelVolumeUp = new Dictionary(); + channelVolumeDown = new Dictionary(); + for (uint x = 1; x <= 100; x++) { + uint tempNum = x; + presets[tempNum] = (ushort)(tempNum + 100); + channelMuteToggle[tempNum] = (ushort)(tempNum + 400); + channelMuteOn[tempNum] = (ushort)(tempNum + 600); + channelMuteOff[tempNum] = (ushort)(tempNum + 800); + channelVolume[tempNum] = (ushort)(tempNum + 200); + channelName[tempNum] = (ushort)(tempNum + 200); + channelType[tempNum] = (ushort)(tempNum + 400); + channelVolumeUp[tempNum] = (ushort)(tempNum + 1000); + channelVolumeDown[tempNum] = (ushort)(tempNum + 1200); + } + } + } + } + \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/EssentialTVOne.cs b/PepperDashEssentials/Bridges/EssentialTVOne.cs new file mode 100644 index 00000000..12fb0c49 --- /dev/null +++ b/PepperDashEssentials/Bridges/EssentialTVOne.cs @@ -0,0 +1,98 @@ +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.Essentials.DM; +using PepperDash.Core; +using PepperDash.Essentials.Core.Routing; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.EthernetCommunication; +using Crestron.SimplSharpPro.DM; + +namespace PepperDash.Essentials +{ + public class EssentialsTVOne : PepperDash.Core.Device + { + public EssentialTVOneProperties Properties; + public List BridgeApiEiscs; + private PepperDash.Essentials.Devices.Common.TVOneCorio TVOneCorio; + private EssentialsTVOneApiMap ApiMap = new EssentialsTVOneApiMap(); + public EssentialsTVOne(string key, string name, JToken properties) + : base(key, name) + { + Properties = JsonConvert.DeserializeObject(properties.ToString()); + + + } + public override bool CustomActivate() { + // Create EiscApis + try + { + foreach (var device in DeviceManager.AllDevices) + { + if (device.Key == this.Properties.connectionDeviceKey) + { + Debug.Console(2, "deviceKey {0} Matches", device.Key); + TVOneCorio = DeviceManager.GetDeviceForKey(device.Key) as PepperDash.Essentials.Devices.Common.TVOneCorio; + break; + } + else + { + Debug.Console(2, "deviceKey {0} doesn't match", device.Key); + + } + } + if (Properties.EiscApiIpids != null && TVOneCorio != null) + { + foreach (string Ipid in Properties.EiscApiIpids) + { + var ApiEisc = new BridgeApiEisc(Ipid); + Debug.Console(2, "Connecting EiscApi {0} to {1}", ApiEisc.Ipid, TVOneCorio.Name); + ushort x = 1; + TVOneCorio.OnlineFeedback.LinkInputSig(ApiEisc.Eisc.BooleanInput[ApiMap.Online]); + ApiEisc.Eisc.SetUShortSigAction(ApiMap.CallPreset, u => TVOneCorio.CallPreset(u)); + TVOneCorio.PresetFeedback.LinkInputSig(ApiEisc.Eisc.UShortInput[ApiMap.PresetFeedback]); + + } + } + + + + + Debug.Console(2, "Name {0} Activated", this.Name); + return true; + } + catch (Exception e) { + Debug.Console(0, "Bridge {0}", e); + return false; + } + } + } + public class EssentialTVOneProperties + { + public string connectionDeviceKey; + public string[] EiscApiIpids; + + + } + + + public class EssentialsTVOneApiMap + { + public ushort CallPreset = 1; + public ushort PresetFeedback = 1; + public ushort Online = 1; + + public EssentialsTVOneApiMap() + { + + + } + + } +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/GenericLightingBridge.cs b/PepperDashEssentials/Bridges/GenericLightingBridge.cs new file mode 100644 index 00000000..be7d88b3 --- /dev/null +++ b/PepperDashEssentials/Bridges/GenericLightingBridge.cs @@ -0,0 +1,103 @@ +//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.Devices.Common; + +//namespace PepperDash.Essentials.Bridges +//{ +// public static class GenericLightingApiExtensions +// { +// public static void LinkToApi(this PepperDash.Essentials.Core.Lighting.LightingBase lightingDevice, BasicTriList trilist, uint joinStart, string joinMapKey) +// { +// var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as GenericLightingJoinMap; + +// if (joinMap == null) +// joinMap = new GenericLightingJoinMap(); + +// joinMap.OffsetJoinNumbers(joinStart); +// Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + + +// Debug.Console(0, "Linking to lighting Type {0}", lightingDevice.GetType().Name.ToString()); + +// // GenericLighitng Actions & FeedBack +// trilist.SetUShortSigAction(joinMap.SelectScene, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u])); + +// int sceneIndex = 1; +// foreach (var scene in lightingDevice.LightingScenes) +// { +// var tempIndex = sceneIndex - 1; +// trilist.SetSigTrueAction((uint)(joinMap.LightingSceneOffset + sceneIndex), () => lightingDevice.SelectScene(lightingDevice.LightingScenes[tempIndex])); +// scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.LightingSceneOffset + sceneIndex)]); +// trilist.StringInput[(uint)(joinMap.LightingSceneOffset + sceneIndex)].StringValue = scene.Name; +// trilist.BooleanInput[(uint)(joinMap.ButtonVisibilityOffset + sceneIndex)].BoolValue = true; +// sceneIndex++; +// } + +// if (lightingDevice.GetType().Name.ToString() == "LutronQuantumArea") +// { +// var lutronDevice = lightingDevice as PepperDash.Essentials.Devices.Common.Environment.Lutron.LutronQuantumArea; +// lutronDevice.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline]); +// trilist.SetStringSigAction(joinMap.IntegrationIdSet, s => lutronDevice.IntegrationId = s); +// } + +// //ApiEisc.Eisc.SetStringSigAction(ApiMap.integrationID, (s) => { lutronLights.IntegrationId = s; }); + + +// /* +// var lutronLights = lightingDevice as PepperDash.Essentials.Devices.Common.Environment.Lutron.LutronQuantumArea; + + +// for (uint i = 1; i <= lightingBase.CircuitCount; i++) +// { +// var circuit = i; +// lightingBase.CircuitNameFeedbacks[circuit - 1].LinkInputSig(trilist.StringInput[joinMap.CircuitNames + circuit]); +// lightingBase.CircuitIsCritical[circuit - 1].LinkInputSig(trilist.BooleanInput[joinMap.CircuitIsCritical + circuit]); +// lightingBase.CircuitState[circuit - 1].LinkInputSig(trilist.BooleanInput[joinMap.CircuitState + circuit]); +// trilist.SetSigTrueAction(joinMap.CircuitCycle + circuit, () => lightingBase.CycleCircuit(circuit - 1)); +// trilist.SetSigTrueAction(joinMap.CircuitOnCmd + circuit, () => lightingBase.TurnOnCircuit(circuit - 1)); +// trilist.SetSigTrueAction(joinMap.CircuitOffCmd + circuit, () => lightingBase.TurnOffCircuit(circuit - 1)); + +// } +// */ +// } +// } +// public class GenericLightingJoinMap : JoinMapBase +// { +// public uint IsOnline { get; set; } +// public uint SelectScene { get; set; } +// public uint LightingSceneOffset { get; set; } +// public uint ButtonVisibilityOffset { get; set; } +// public uint IntegrationIdSet { get; set; } + +// public GenericLightingJoinMap() +// { +// // Digital +// IsOnline = 1; +// SelectScene = 1; +// IntegrationIdSet = 1; +// LightingSceneOffset = 10; +// ButtonVisibilityOffset = 40; +// // Analog +// } + +// public override void OffsetJoinNumbers(uint joinStart) +// { +// var joinOffset = joinStart - 1; + +// IsOnline = IsOnline + joinOffset; +// SelectScene = SelectScene + joinOffset; +// LightingSceneOffset = LightingSceneOffset + joinOffset; +// ButtonVisibilityOffset = ButtonVisibilityOffset + 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 new file mode 100644 index 00000000..d2072a91 --- /dev/null +++ b/PepperDashEssentials/Bridges/IBasicCommunicationBridge.cs @@ -0,0 +1,107 @@ +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; + +namespace PepperDash.Essentials.Bridges +{ + public static class IBasicCommunicationApiExtensions + { + public static void LinkToApi(this GenericComm comm, BasicTriList trilist, uint joinStart, string joinMapKey) + { + var joinMap = JoinMapHelper.GetJoinMapForDevice(joinMapKey) as IBasicCommunicationJoinMap; + + if (joinMap == null) + joinMap = new IBasicCommunicationJoinMap(); + + joinMap.OffsetJoinNumbers(joinStart); + + if (comm.CommPort == null) + { + Debug.Console(1, comm, "Unable to link device '{0}'. CommPort is null", comm.Key); + return; + } + + Debug.Console(1, comm, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + // this is a permanent event handler. This cannot be -= from event + comm.CommPort.TextReceived += (s, a) => + { + Debug.Console(2, comm, "RX: {0}", a.Text); + trilist.SetString(joinMap.TextReceived, a.Text); + }; + trilist.SetStringSigAction(joinMap.SendText, new Action(s => comm.CommPort.SendText(s))); + trilist.SetStringSigAction(joinMap.SetPortConfig + 1, new Action(s => comm.SetPortConfig(s))); + + + var sComm = comm.CommPort as ISocketStatus; + if (sComm != null) + { + sComm.ConnectionChange += (s, a) => + { + trilist.SetUshort(joinMap.Status, (ushort)(a.Client.ClientStatus)); + trilist.SetBool(joinMap.Connected, a.Client.ClientStatus == + Crestron.SimplSharp.CrestronSockets.SocketStatus.SOCKET_STATUS_CONNECTED); + }; + + trilist.SetBoolSigAction(joinMap.Connect, new Action(b => + { + if (b) + { + sComm.Connect(); + } + else + { + sComm.Disconnect(); + } + })); + } + } + + + + public class IBasicCommunicationJoinMap : JoinMapBase + { + //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 IBasicCommunicationJoinMap() + { + TextReceived = 1; + SendText = 1; + SetPortConfig = 2; + Connect = 1; + Connected = 1; + Status = 1; + } + + public override void OffsetJoinNumbers(uint joinStart) + { + var joinOffset = joinStart - 1; + + TextReceived = TextReceived + joinOffset; + SendText = SendText + joinOffset; + SetPortConfig = SetPortConfig + joinOffset; + Connect = Connect + joinOffset; + Connected = Connected + joinOffset; + Status = Status + joinOffset; + } + } + } + +} \ 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/JoinMapBase.cs b/PepperDashEssentials/Bridges/JoinMapBase.cs new file mode 100644 index 00000000..13d08a3f --- /dev/null +++ b/PepperDashEssentials/Bridges/JoinMapBase.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Bridges +{ + public static class JoinMapHelper + { + /// + /// Attempts to get the join map from config + /// + /// + /// + public static JoinMapBase GetJoinMapForDevice(string joinMapKey) + { + if (!string.IsNullOrEmpty(joinMapKey)) + return null; + + // FUTURE TODO: Get the join map from the ConfigReader.ConfigObject + + return null; + } + } + + + public abstract class JoinMapBase + { + /// + /// Modifies all the join numbers by adding the offset. This should never be called twice + /// + /// + public abstract void OffsetJoinNumbers(uint joinStart); + + + } + + +} \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/SystemMonitorBridge.cs b/PepperDashEssentials/Bridges/SystemMonitorBridge.cs new file mode 100644 index 00000000..4b8829b2 --- /dev/null +++ b/PepperDashEssentials/Bridges/SystemMonitorBridge.cs @@ -0,0 +1,150 @@ +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 fdd6e837..00000000 --- a/PepperDashEssentials/Config/EssentialsConfig.cs +++ /dev/null @@ -1,69 +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 - { - if (string.IsNullOrEmpty(SystemUrl)) - return "missing url"; - - var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*"); - string uuid = result.Groups[1].Value; - return uuid; - } - } - - [JsonProperty("templateUuid")] - public string TemplateUuid - { - get - { - if (string.IsNullOrEmpty(TemplateUrl)) - return "missing template url"; - - 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/Configuration Original/Factories/DmFactory.cs b/PepperDashEssentials/Configuration Original/Factories/DmFactory.cs index 646cf1f3..ef2d1a03 100644 --- a/PepperDashEssentials/Configuration Original/Factories/DmFactory.cs +++ b/PepperDashEssentials/Configuration Original/Factories/DmFactory.cs @@ -80,6 +80,16 @@ namespace PepperDash.Essentials var dm = new DmMd8x8(ipId, Global.ControlSystem); //dev = new DmChassisController(devKey, devName, dm); } + else if (devType.Equals("dmmd16x16", StringComparison.OrdinalIgnoreCase)) + { + var dm = new DmMd16x16(ipId, Global.ControlSystem); + //dev = new DmChassisController(devKey, devName, dm); + } + else if (devType.Equals("dmmd32x32", StringComparison.OrdinalIgnoreCase)) + { + var dm = new DmMd32x32(ipId, Global.ControlSystem); + //dev = new DmChassisController(devKey, devName, dm); + } } catch (Exception e) { diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index 72954503..8463390a 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 @@ -35,6 +39,7 @@ namespace PepperDash.Essentials //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); @@ -86,20 +91,40 @@ namespace PepperDash.Essentials directoryPrefix = Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory(); - //directoryPrefix = ""; - - if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) + if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows OS { - 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; + // Check if User/ProgramX exists + if(Directory.Exists(directoryPrefix + dirSeparator + "User" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + Debug.Console(0, @"User/program{0} directory found", InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + "User" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // Check if Nvram/Programx exists + else if (Directory.Exists(directoryPrefix + dirSeparator + "Nvram" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + Debug.Console(0, @"Nvram/program{0} directory found", InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + "Nvram" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // If neither exists, set path to User/ProgramX + else + { + Debug.Console(0, @"No previous directory found. Using User/program{0}", InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + "User" + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + } + else // Handles Linux OS (Virtual Control) + { Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on Virtual Control Server", versionString); + + // Set path to User/ + filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; } Global.SetFilePathPrefix(filePathPrefix); @@ -142,16 +167,22 @@ 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; + } /// - /// Verifies filesystem is set up. IR, SGD, and program1 folders + /// Verifies filesystem is set up. IR, SGD, and programX folders /// bool SetupFilesystem() { @@ -204,16 +235,39 @@ namespace PepperDash.Essentials LoadLogoServer(); DeviceManager.ActivateAll(); + + LinkSystemMonitorToAppServer(); } + 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 /// public void LoadDevices() { - // Build the processor wrapper class - DeviceManager.AddDevice(new PepperDash.Essentials.Core.Devices.CrestronProcessor("processor")); + // 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) { @@ -232,17 +286,23 @@ 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) newDev = PepperDash.Essentials.DM.DeviceFactory.GetDevice(devConf); if (newDev == null) newDev = PepperDash.Essentials.Devices.Displays.DisplayDeviceFactory.GetDevice(devConf); - + if (newDev == null) + newDev = PepperDash.Essentials.BridgeFactory.GetDevice(devConf); if (newDev != null) DeviceManager.AddDevice(newDev); else @@ -257,6 +317,7 @@ namespace PepperDash.Essentials } + /// /// Helper method to load tie lines. This should run after devices have loaded /// @@ -296,7 +357,7 @@ namespace PepperDash.Essentials foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) { - var room = roomConfig.GetRoomObject() as EssentialsRoomBase; + var room = EssentialsRoomConfigHelper.GetRoomObject(roomConfig) as EssentialsRoomBase; if (room != null) { if (room is EssentialsHuddleSpaceRoom) 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 92% rename from PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs rename to PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index f446a596..e7623975 100644 --- a/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/PepperDashEssentials/FOR REFERENCE UI/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -31,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 = @@ -425,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 @@ -470,6 +484,7 @@ namespace PepperDash.Essentials.Fusion FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQuery.StringValue = actionRequest; + GetCustomProperties(); // Request current Fusion Server Time RequestLocalDateTime(null); @@ -753,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; + } + } /// @@ -1466,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 dd374586..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,16 +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;"; - - SetUpEthernetValues(); - - 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 1bb81720..10fad395 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -99,17 +99,36 @@ False ..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll + + False + ..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpTimerEventInterface.dll + - + + + + + + + + + + + + + + + + @@ -124,22 +143,22 @@ - - - - - - + + + + + - + + diff --git a/PepperDashEssentials/Properties/AssemblyInfo.cs b/PepperDashEssentials/Properties/AssemblyInfo.cs index 4cd22c15..97d14f56 100644 --- a/PepperDashEssentials/Properties/AssemblyInfo.cs +++ b/PepperDashEssentials/Properties/AssemblyInfo.cs @@ -4,5 +4,5 @@ [assembly: AssemblyCompany("PepperDash Technology Corp")] [assembly: AssemblyProduct("PepperDashEssentials")] [assembly: AssemblyCopyright("Copyright © PepperDash Technology Corp 2018")] -[assembly: AssemblyVersion("1.2.11.*")] +[assembly: AssemblyVersion("1.3.2.*")] diff --git a/PepperDashEssentials/Properties/ControlSystem.cfg b/PepperDashEssentials/Properties/ControlSystem.cfg index 4772f8fd..d2c3b2c7 100644 --- a/PepperDashEssentials/Properties/ControlSystem.cfg +++ b/PepperDashEssentials/Properties/ControlSystem.cfg @@ -1,7 +1,7 @@ - - - Test RMC3 -
auto 192.168.1.40
- Program01 - Internal Flash + + + 192.168.10.1 +
auto 192.168.10.1
+ Program01 + Internal Flash
\ No newline at end of file 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/EssentialsRoomConfig.cs b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs index eeb1e9cb..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/Types/EssentialsHuddleSpaceRoom.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs index 5b7bdfde..63f0bb29 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs @@ -1,465 +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, 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 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(); - - RunDefaultPresentRoute(); - - CrestronEnvironment.Sleep(1000); - - RunRouteAction("roomOff"); - } - - /// - /// Routes the default source item, if any - /// - public bool RunDefaultPresentRoute() - { - if(DefaultSourceItem == null) - return false; - - RunRouteAction(DefaultSourceItem); - 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 => - { - 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, IHasCurrentVolumeControls + { + 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 a5bdb1a6..62b11e49 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs @@ -1,573 +1,647 @@ -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, 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 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; +using PepperDash.Essentials.Devices.Common.AudioCodec; + +namespace PepperDash.Essentials +{ + public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange, + IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec + { + 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 AudioCodecBase AudioCodec { get; private set; } + + public bool ExcludeFromGlobalFunctions { get; set; } + + /// + /// The config name of the source list + /// + public string SourceListKey { get; set; } + + public string DefaultSourceItem { get; set; } + + 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"); + + AudioCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.AudioCodecKey) as + PepperDash.Essentials.Devices.Common.AudioCodec.AudioCodecBase; + if (AudioCodec == null) + Debug.Console(0, this, "No Audio Codec Found"); + + DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IBasicVolumeControls; + + Initialize(); + } + catch (Exception e) + { + Debug.Console(1, this, "Error building room: \n{0}", e); + } + } + + void Initialize() + { + if (DefaultAudioDevice is IBasicVolumeControls) + DefaultVolumeControls = DefaultAudioDevice as IBasicVolumeControls; + else if (DefaultAudioDevice is IHasVolumeDevice) + DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice; + CurrentVolumeControls = DefaultVolumeControls; + + + var 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(); + }; + } + + + // Combines call feedback from both codecs if available + InCallFeedback = new BoolFeedback(() => + { + bool inAudioCall = false; + bool inVideoCall = false; + + if(AudioCodec != null) + inAudioCall = AudioCodec.IsInCall; + + if(VideoCodec != null) + inVideoCall = AudioCodec.IsInCall; + + if (inAudioCall || inVideoCall) + return true; + else + return false; + }); + + VideoCodec.CallStatusChange += (o, a) => this.InCallFeedback.FireUpdate(); + + if (AudioCodec != null) + AudioCodec.CallStatusChange += (o, a) => this.InCallFeedback.FireUpdate(); + + IsSharingFeedback = new BoolFeedback(() => VideoCodec.SharingContentIsOnFeedback.BoolValue); + VideoCodec.SharingContentIsOnFeedback.OutputChange += (o, a) => this.IsSharingFeedback.FireUpdate(); + + // link privacy to VC (for now?) + PrivacyModeIsOnFeedback = new BoolFeedback(() => VideoCodec.PrivacyModeIsOnFeedback.BoolValue); + VideoCodec.PrivacyModeIsOnFeedback.OutputChange += (o, a) => this.PrivacyModeIsOnFeedback.FireUpdate(); + + CallTypeFeedback = new IntFeedback(() => 0); + + SourceListKey = "default"; + EnablePowerOnToLastSource = true; + } + + 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/EssentialsRoomBase.cs b/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs index 686d18f3..fc177b51 100644 --- a/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs +++ b/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs @@ -1,264 +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 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/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddle/EssentialsHuddlePanelAvFunctionsDriver.cs index e18801ea..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) { @@ -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,20 +722,19 @@ 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; @@ -746,40 +744,40 @@ namespace PepperDash.Essentials _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/VC/EssentialsVideoCodecUiDriver.cs b/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs index be65410e..13bf6a85 100644 --- a/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs +++ b/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs @@ -484,9 +484,9 @@ namespace PepperDash.Essentials.UIDrivers.VC TriList.SetString(timeTextOffset + i, timeText); string iconName = null; - if (c.OccurenceType == eCodecOccurrenceType.Received) + if (c.OccurrenceType == eCodecOccurrenceType.Received) iconName = "Misc-18_Light"; - else if (c.OccurenceType == eCodecOccurrenceType.Placed) + else if (c.OccurrenceType == eCodecOccurrenceType.Placed) iconName = "Misc-17_Light"; else iconName = "Delete"; diff --git a/Release Package/PepperDashEssentials.cpz b/Release Package/PepperDashEssentials.cpz deleted file mode 100644 index 2ca1e7da..00000000 Binary files a/Release Package/PepperDashEssentials.cpz and /dev/null differ diff --git a/Release Package/PepperDashEssentials.dll b/Release Package/PepperDashEssentials.dll deleted file mode 100644 index a818be1b..00000000 Binary files a/Release Package/PepperDashEssentials.dll and /dev/null differ diff --git a/devjson commands.json b/devjson commands.json index d8f5f401..4f0aa69e 100644 --- a/devjson commands.json +++ b/devjson commands.json @@ -12,7 +12,7 @@ devjson:1 {"deviceKey":"timer","methodName":"Cancel" } devjson:1 {"deviceKey":"timer","methodName":"Reset" } -devjson:10 {"deviceKey":"room1","methodName":"Shutdown" } +devjson:1 {"deviceKey":"room1","methodName":"Shutdown" } devjson:1 {"deviceKey":"mockVc-1", "methodName":"TestIncomingVideoCall", "params": ["123-456-7890"]} @@ -30,8 +30,8 @@ devjson:1 {"deviceKey":"room1.InCallFeedback","methodName":"SetTestValue", "para devjson:1 {"deviceKey":"room1.InCallFeedback","methodName":"ClearTestValue", "params": []} +devjson:3 {"deviceKey":"room1.RoomOccupancy.RoomIsOccupiedFeedback","methodName":"SetTestValue", "params": [ true ]} +devjson:2 {"deviceKey":"codec-comms-ssh", "methodName":"SendText", "params": ["xcommand dial number: 10.11.50.211\r"]} -devjson:10 {"deviceKey":"mobileControlBridge-essentialsHuddle","methodName":"TestOne"} -devjson:10 {"deviceKey":"mobileControlBridge-essentialsHuddle","methodName":"TestMulti"} - devjson:10 {"deviceKey":"room1","methodName":"RunDefaultPresentRoute"} +devjson:2 {"deviceKey":"codec-comms-ssh", "methodName":"Connect", "params": []} \ No newline at end of file diff --git a/essentials-framework b/essentials-framework index f9747d73..c40f3b40 160000 --- a/essentials-framework +++ b/essentials-framework @@ -1 +1 @@ -Subproject commit f9747d73346cbc25c2107e9e8a5afd478935c2a6 +Subproject commit c40f3b40d57a82b9191d4f8f6dbf89a4756af4e4