diff --git a/PepperDashEssentials/AppServer/CotijaSystemController.cs b/PepperDashEssentials/AppServer/CotijaSystemController.cs index 1785163e..70beec0e 100644 --- a/PepperDashEssentials/AppServer/CotijaSystemController.cs +++ b/PepperDashEssentials/AppServer/CotijaSystemController.cs @@ -1,733 +1,733 @@ -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 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; - - /// - /// 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, "showactionpaths", "Prints the paths in teh Action Dictionary", ConsoleAccessLevelEnum.AccessOperator); - - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - } - - /// - /// Sends message to server to indicate the system is shutting down - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping && WSClient.Connected) - { - SendMessageToServer(JObject.FromObject( new - { - type = "/system/close" - })); - - } - } - - 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"); - RegisterSystemToServer(); - } - } - - /// - /// - /// - /// - /// - void bridge_ConfigurationIsReady(object sender, EventArgs e) - { - Debug.Console(1, this, "Bridge ready. Registering"); - // send the configuration object to the server - RegisterSystemToServer(); - } - - /// - /// - /// - /// - void ReconnectToServerTimerCallback(object o) - { - RegisterSystemToServer(); - } - - /// - /// Verifies system connection with servers - /// - /// - void AuthorizeSystem(string code) - { - if (string.IsNullOrEmpty(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."); - RegisterSystemToServer(); - } - else if (r.Code == 404) - { - if (r.ContentString.Contains("codeNotFound")) - { - Debug.Console(0, "Authorization failed, code not found for system UUID {0}", SystemUuid); - } - else if (r.ContentString.Contains("uuidNotFound")) - { - Debug.Console(0, "Authorization failed, uuid {0} not found. Check Essentials configuration is correct", - SystemUuid); - } - } - } - else - Debug.Console(0, this, "Error {0} in authorizing system", e); - }); - } - catch (Exception e) - { - Debug.Console(0, this, "Error in authorizing: {0}", e); - } - } - - /// - /// Dumps info in response to console command. - /// - void ShowInfo() - { - var url = Config != null ? Config.ServerUrl : "No config"; - string name; - string code; - if (RoomBridges != null && RoomBridges.Count > 0) - { - name = RoomBridges[0].RoomName; - code = RoomBridges[0].UserCode; - } - else - { - name = "No config"; - code = "Not available"; - } - var conn = WSClient == null ? "No client" : (WSClient.Connected ? "Yes" : "No"); - - CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: - Server address: {0} - System Name: {1} - System UUID: {2} - System User code: {3} - Connected?: {4}", url, name, SystemUuid, - code, conn); - } - - /// - /// Registers the room with the server - /// - /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" - void RegisterSystemToServer() - { - - - var ready = RegisterLockEvent.Wait(20000); - if (!ready) - { - Debug.Console(1, this, "RegisterSystemToServer failed to enter after 20 seconds. Ignoring"); - return; - } - RegisterLockEvent.Reset(); - - try - { - var confObject = ConfigReader.ConfigObject; - confObject.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; - var version = Assembly.GetExecutingAssembly().GetName().Version; - confObject.Info.RuntimeInfo.AssemblyVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); - - string postBody = JsonConvert.SerializeObject(confObject); - SystemUuid = confObject.SystemUuid; - - if (string.IsNullOrEmpty(postBody)) - { - Debug.Console(1, this, "ERROR: Config body is empty. Cannot register with server."); - } - else - { - var regClient = new HttpClient(); - regClient.Verbose = true; - regClient.KeepAlive = true; - - string url = string.Format("http://{0}/api/system/join/{1}", Config.ServerUrl, SystemUuid); - Debug.Console(1, this, "Joining server at {0}", url); - - HttpClientRequest request = new HttpClientRequest(); - request.Url.Parse(url); - request.RequestType = RequestType.Post; - request.Header.SetHeaderValue("Content-Type", "application/json"); - request.ContentString = postBody; - - var err = regClient.DispatchAsync(request, RegistrationConnectionCallback); - } - - } - catch (Exception e) - { - Debug.Console(0, this, "ERROR: Initilizing Room: {0}", e); - RegisterLockEvent.Set(); - StartReconnectTimer(); - } - - } - - /// - /// Sends a message to the server from a room - /// - /// room from which the message originates - /// object to be serialized and sent in post body - public void SendMessageToServer(JObject o) - { - - if (WSClient != null && WSClient.Connected) - { - string message = JsonConvert.SerializeObject(o, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - Debug.Console(1, this, "Message TX: {0}", message); - var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); - WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - //WSClient.SendAsync(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); - } - - } - - /// - /// Disconnects the SSE Client and stops the heartbeat timer - /// - /// - void DisconnectStreamClient(string command) - { - //if(SseClient != null) - // SseClient.Disconnect(); - - if (WSClient != null && WSClient.Connected) - WSClient.Disconnect(); - - if (ServerHeartbeatCheckTimer != null) - { - ServerHeartbeatCheckTimer.Stop(); - - ServerHeartbeatCheckTimer = null; - } - } - - /// - /// The callback that fires when we get a response from our registration attempt - /// - /// - /// - void RegistrationConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err) - { - CheckHttpDebug(resp, err); - Debug.Console(1, this, "RegistrationConnectionCallback: {0}", err); - try - { - if (resp != null && resp.Code == 200) - { - if(ServerReconnectTimer != null) - { - ServerReconnectTimer.Stop(); - ServerReconnectTimer = null; - } - - // Success here! - ConnectStreamClient(); - } - else - { - if (resp != null) - Debug.Console(1, this, "Response from server: {0}\n{1}", resp.Code, err); - else - { - Debug.Console(1, this, "Null response received from server."); - } - StartReconnectTimer(); - } - } - catch (Exception e) - { - Debug.Console(1, this, "Error Initializing Stream Client: {0}", e); - StartReconnectTimer(); - } - RegisterLockEvent.Set(); - } - - /// - /// Executes when we don't get a heartbeat message in time. Triggers reconnect. - /// - /// For CTimer callback. Not used - void HeartbeatExpiredTimerCallback(object o) - { - Debug.Console(1, this, "Heartbeat Timer Expired."); - if (ServerHeartbeatCheckTimer != null) - { - ServerHeartbeatCheckTimer.Stop(); - ServerHeartbeatCheckTimer = null; - } - StartReconnectTimer(); - } - - /// - /// - /// - /// - /// - void StartReconnectTimer() - { - // Start the reconnect timer - if (ServerReconnectTimer == null) - { - ServerReconnectTimer = new CTimer(ReconnectToServerTimerCallback, null, ServerReconnectInterval, ServerReconnectInterval); - Debug.Console(1, this, "Reconnect Timer Started."); - } - ServerReconnectTimer.Reset(ServerReconnectInterval, ServerReconnectInterval); - } - - /// - /// - /// - /// - /// - void ResetOrStartHearbeatTimer() - { - if (ServerHeartbeatCheckTimer == null) - { - ServerHeartbeatCheckTimer = new CTimer(HeartbeatExpiredTimerCallback, null, ServerHeartbeatInterval, ServerHeartbeatInterval); - - Debug.Console(1, this, "Heartbeat Timer Started."); - } - - ServerHeartbeatCheckTimer.Reset(ServerHeartbeatInterval, ServerHeartbeatInterval); - } - - - /// - /// Connects the SSE Client - /// - /// - void ConnectStreamClient() - { - Debug.Console(0, this, "Initializing Stream client to server."); - - if (WSClient == null) - { - WSClient = new WebSocketClient(); - } - WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); - WSClient.Connect(); - Debug.Console(0, this, "Websocket connected"); - WSClient.ReceiveCallBack = WebsocketReceiveCallback; - //WSClient.SendCallBack = WebsocketSendCallback; - WSClient.ReceiveAsync(); - } - - /// - /// Resets reconnect timer and updates usercode - /// - /// - void HandleHeartBeat(JToken content) - { - SendMessageToServer(JObject.FromObject(new - { - type = "/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) - { - Debug.Console(0, this, "------ Begin HTTP Debug ---------------------------------------"); - Debug.Console(0, this, "HTTP Response URL: {0}", r.ResponseUrl.ToString()); - Debug.Console(0, this, "HTTP Response 'error' {0}", e); - Debug.Console(0, this, "HTTP Response code: {0}", r.Code); - Debug.Console(0, this, "HTTP Response content: \r{0}", r.ContentString); - Debug.Console(0, this, "------ End HTTP Debug -----------------------------------------"); - } - } - - /// - /// - /// - /// - /// - /// - /// - int WebsocketReceiveCallback(byte[] data, uint length, WebSocketClient.WEBSOCKET_PACKET_TYPES opcode, - WebSocketClient.WEBSOCKET_RESULT_CODES err) - { - var rx = System.Text.Encoding.UTF8.GetString(data, 0, (int)length); - if(rx.Length > 0) - ParseStreamRx(rx); - WSClient.ReceiveAsync(); - return 1; - } - - /// - /// Callback to catch possible errors in sending via the websocket - /// - /// - /// - int WebsocketSendCallback(Crestron.SimplSharp.CrestronWebSocketClient.WebSocketClient.WEBSOCKET_RESULT_CODES result) - { - Debug.Console(1, this, "SendCallback result: {0}", result); - - return 1; - } - - /// - /// - /// - /// - /// - void ParseStreamRx(string message) - { - if(string.IsNullOrEmpty(message)) - return; - - Debug.Console(1, this, "Message RX: '{0}'", message); - try - { - var messageObj = JObject.Parse(message); - - var type = messageObj["type"].Value(); - - if (type == "hello") - { - ResetOrStartHearbeatTimer(); - } - else if (type == "/system/heartbeat") - { - HandleHeartBeat(messageObj["content"]); - } - else if (type == "close") - { - WSClient.Disconnect(); - - ServerHeartbeatCheckTimer.Stop(); - // Start the reconnect timer - StartReconnectTimer(); - } - else - { - // Check path against Action dictionary - if (ActionDictionary.ContainsKey(type)) - { - var action = ActionDictionary[type]; - - if (action is Action) - { - (action as Action)(); - } - else if (action is PressAndHoldAction) - { - var stateString = messageObj["content"]["state"].Value(); - - // Look for a button press event - if (!string.IsNullOrEmpty(stateString)) - { - switch (stateString) - { - case "true": - { - if (!PushedActions.ContainsKey(type)) - { - PushedActions.Add(type, new CTimer(o => - { - (action as PressAndHoldAction)(false); - PushedActions.Remove(type); - }, null, ButtonHeartbeatInterval, ButtonHeartbeatInterval)); - } - // Maybe add an else to reset the timer - break; - } - case "held": - { - if (!PushedActions.ContainsKey(type)) - { - PushedActions[type].Reset(ButtonHeartbeatInterval, ButtonHeartbeatInterval); - } - return; - } - case "false": - { - if (PushedActions.ContainsKey(type)) - { - PushedActions[type].Stop(); - PushedActions.Remove(type); - } - break; - } - } - - (action as PressAndHoldAction)(stateString == "true"); - } - } - else if (action is Action) - { - var stateString = messageObj["content"]["state"].Value(); - - if (!string.IsNullOrEmpty(stateString)) - { - (action as Action)(stateString == "true"); - } - } - else if (action is Action) - { - (action as Action)(messageObj["content"]["value"].Value()); - } - else if (action is Action) - { - (action as Action)(messageObj["content"]["value"].Value()); - } - else if (action is Action) - { - (action as Action)(messageObj["content"] - .ToObject()); - } - } - else - { - Debug.Console(1, this, "-- Warning: Incoming message has no registered handler"); - } - } - } - catch (Exception err) - { - //Debug.Console(1, "SseMessageLengthBeforeFailureCount: {0}", SseMessageLengthBeforeFailureCount); - //SseMessageLengthBeforeFailureCount = 0; - Debug.Console(1, this, "Unable to parse message: {0}", err); - } - } - - void TestHttpRequest(string s) - { - { - s = s.Trim(); - if (string.IsNullOrEmpty(s)) - { - PrintTestHttpRequestUsage(); - return; - } - var tokens = s.Split(' '); - if (tokens.Length < 2) - { - CrestronConsole.ConsoleCommandResponse("Too few paramaters\r"); - PrintTestHttpRequestUsage(); - return; - } - - try - { - var url = tokens[1]; - if (tokens[0].ToLower() == "get") - { - var resp = new HttpClient().Get(url); - CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); - } - else if (tokens[0].ToLower() == "post") - { - var resp = new HttpClient().Post(url, new byte[] { }); - CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); - } - - else - { - CrestronConsole.ConsoleCommandResponse("Only get or post supported\r"); - PrintTestHttpRequestUsage(); - } - } - catch (HttpException e) - { - CrestronConsole.ConsoleCommandResponse("Exception in request:\r"); - CrestronConsole.ConsoleCommandResponse("Response URL: {0}\r", e.Response.ResponseUrl); - CrestronConsole.ConsoleCommandResponse("Response Error Code: {0}\r", e.Response.Code); - CrestronConsole.ConsoleCommandResponse("Response body: {0}\r", e.Response.ContentString); - } - - } - } - - void PrintTestHttpRequestUsage() - { - CrestronConsole.ConsoleCommandResponse("Usage: mobilehttprequest:N get/post url\r"); - } - } +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Room.Cotija; + +namespace PepperDash.Essentials +{ + public class CotijaSystemController : Device + { + WebSocketClient WSClient; + + /// + /// Prevents post operations from stomping on each other and getting lost + /// + CEvent PostLockEvent = new CEvent(true, true); + + CEvent RegisterLockEvent = new CEvent(true, true); + + public CotijaConfig Config { get; private set; } + + Dictionary ActionDictionary = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + Dictionary PushedActions = new Dictionary(); + + CTimer ServerHeartbeatCheckTimer; + + long ServerHeartbeatInterval = 20000; + + CTimer ServerReconnectTimer; + + long ServerReconnectInterval = 5000; + + string SystemUuid; + + List RoomBridges = new List(); + + long ButtonHeartbeatInterval = 1000; + + /// + /// Used for tracking HTTP debugging + /// + bool HttpDebugEnabled; + + /// + /// + /// + /// + /// + /// + public CotijaSystemController(string key, string name, CotijaConfig config) : base(key, name) + { + Config = config; + Debug.Console(0, this, "Mobile UI controller initializing for server:{0}", config.ServerUrl); + + CrestronConsole.AddNewConsoleCommand(AuthorizeSystem, + "mobileauth", "Authorizes system to talk to cotija server", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => ShowInfo(), + "mobileinfo", "Shows information for current mobile control session", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => { + s = s.Trim(); + if(!string.IsNullOrEmpty(s)) + { + HttpDebugEnabled = (s.Trim() != "0"); + } + CrestronConsole.ConsoleCommandResponse("HTTP Debug {0}", HttpDebugEnabled ? "Enabled" : "Disabled"); + }, + "mobilehttpdebug", "1 enables more verbose HTTP response debugging", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(TestHttpRequest, + "mobilehttprequest", "Tests an HTTP get to URL given", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(PrintActionDictionaryPaths, "showactionpaths", "Prints the paths in teh Action Dictionary", ConsoleAccessLevelEnum.AccessOperator); + + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + } + + /// + /// Sends message to server to indicate the system is shutting down + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping && WSClient.Connected) + { + SendMessageToServer(JObject.FromObject( new + { + type = "/system/close" + })); + + } + } + + 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"); + RegisterSystemToServer(); + } + } + + /// + /// + /// + /// + /// + void bridge_ConfigurationIsReady(object sender, EventArgs e) + { + Debug.Console(1, this, "Bridge ready. Registering"); + // send the configuration object to the server + RegisterSystemToServer(); + } + + /// + /// + /// + /// + void ReconnectToServerTimerCallback(object o) + { + RegisterSystemToServer(); + } + + /// + /// Verifies system connection with servers + /// + /// + void AuthorizeSystem(string code) + { + if (string.IsNullOrEmpty(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."); + RegisterSystemToServer(); + } + else if (r.Code == 404) + { + if (r.ContentString.Contains("codeNotFound")) + { + Debug.Console(0, "Authorization failed, code not found for system UUID {0}", SystemUuid); + } + else if (r.ContentString.Contains("uuidNotFound")) + { + Debug.Console(0, "Authorization failed, uuid {0} not found. Check Essentials configuration is correct", + SystemUuid); + } + } + } + else + Debug.Console(0, this, "Error {0} in authorizing system", e); + }); + } + catch (Exception e) + { + Debug.Console(0, this, "Error in authorizing: {0}", e); + } + } + + /// + /// Dumps info in response to console command. + /// + void ShowInfo() + { + var url = Config != null ? Config.ServerUrl : "No config"; + string name; + string code; + if (RoomBridges != null && RoomBridges.Count > 0) + { + name = RoomBridges[0].RoomName; + code = RoomBridges[0].UserCode; + } + else + { + name = "No config"; + code = "Not available"; + } + var conn = WSClient == null ? "No client" : (WSClient.Connected ? "Yes" : "No"); + + CrestronConsole.ConsoleCommandResponse(@"Mobile Control Information: + Server address: {0} + System Name: {1} + System UUID: {2} + System User code: {3} + Connected?: {4}", url, name, SystemUuid, + code, conn); + } + + /// + /// Registers the room with the server + /// + /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" + void RegisterSystemToServer() + { + + + var ready = RegisterLockEvent.Wait(20000); + if (!ready) + { + Debug.Console(1, this, "RegisterSystemToServer failed to enter after 20 seconds. Ignoring"); + return; + } + RegisterLockEvent.Reset(); + + try + { + var confObject = ConfigReader.ConfigObject; + confObject.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; + var version = Assembly.GetExecutingAssembly().GetName().Version; + confObject.Info.RuntimeInfo.AssemblyVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); + + string postBody = JsonConvert.SerializeObject(confObject); + SystemUuid = confObject.SystemUuid; + + if (string.IsNullOrEmpty(postBody)) + { + Debug.Console(1, this, "ERROR: Config body is empty. Cannot register with server."); + } + else + { + var regClient = new HttpClient(); + regClient.Verbose = true; + regClient.KeepAlive = true; + + string url = string.Format("http://{0}/api/system/join/{1}", Config.ServerUrl, SystemUuid); + Debug.Console(1, this, "Joining server at {0}", url); + + HttpClientRequest request = new HttpClientRequest(); + request.Url.Parse(url); + request.RequestType = RequestType.Post; + request.Header.SetHeaderValue("Content-Type", "application/json"); + request.ContentString = postBody; + + var err = regClient.DispatchAsync(request, RegistrationConnectionCallback); + } + + } + catch (Exception e) + { + Debug.Console(0, this, "ERROR: Initilizing Room: {0}", e); + RegisterLockEvent.Set(); + StartReconnectTimer(); + } + + } + + /// + /// Sends a message to the server from a room + /// + /// room from which the message originates + /// object to be serialized and sent in post body + public void SendMessageToServer(JObject o) + { + + if (WSClient != null && WSClient.Connected) + { + string message = JsonConvert.SerializeObject(o, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + Debug.Console(1, this, "Message TX: {0}", message); + var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); + WSClient.Send(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); + //WSClient.SendAsync(messageBytes, (uint)messageBytes.Length, WebSocketClient.WEBSOCKET_PACKET_TYPES.LWS_WS_OPCODE_07__TEXT_FRAME); + } + + } + + /// + /// Disconnects the SSE Client and stops the heartbeat timer + /// + /// + void DisconnectStreamClient(string command) + { + //if(SseClient != null) + // SseClient.Disconnect(); + + if (WSClient != null && WSClient.Connected) + WSClient.Disconnect(); + + if (ServerHeartbeatCheckTimer != null) + { + ServerHeartbeatCheckTimer.Stop(); + + ServerHeartbeatCheckTimer = null; + } + } + + /// + /// The callback that fires when we get a response from our registration attempt + /// + /// + /// + void RegistrationConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err) + { + CheckHttpDebug(resp, err); + Debug.Console(1, this, "RegistrationConnectionCallback: {0}", err); + try + { + if (resp != null && resp.Code == 200) + { + if(ServerReconnectTimer != null) + { + ServerReconnectTimer.Stop(); + ServerReconnectTimer = null; + } + + // Success here! + ConnectStreamClient(); + } + else + { + if (resp != null) + Debug.Console(1, this, "Response from server: {0}\n{1}", resp.Code, err); + else + { + Debug.Console(1, this, "Null response received from server."); + } + StartReconnectTimer(); + } + } + catch (Exception e) + { + Debug.Console(1, this, "Error Initializing Stream Client: {0}", e); + StartReconnectTimer(); + } + RegisterLockEvent.Set(); + } + + /// + /// Executes when we don't get a heartbeat message in time. Triggers reconnect. + /// + /// For CTimer callback. Not used + void HeartbeatExpiredTimerCallback(object o) + { + Debug.Console(1, this, "Heartbeat Timer Expired."); + if (ServerHeartbeatCheckTimer != null) + { + ServerHeartbeatCheckTimer.Stop(); + ServerHeartbeatCheckTimer = null; + } + StartReconnectTimer(); + } + + /// + /// + /// + /// + /// + void StartReconnectTimer() + { + // Start the reconnect timer + if (ServerReconnectTimer == null) + { + ServerReconnectTimer = new CTimer(ReconnectToServerTimerCallback, null, ServerReconnectInterval, ServerReconnectInterval); + Debug.Console(1, this, "Reconnect Timer Started."); + } + ServerReconnectTimer.Reset(ServerReconnectInterval, ServerReconnectInterval); + } + + /// + /// + /// + /// + /// + void ResetOrStartHearbeatTimer() + { + if (ServerHeartbeatCheckTimer == null) + { + ServerHeartbeatCheckTimer = new CTimer(HeartbeatExpiredTimerCallback, null, ServerHeartbeatInterval, ServerHeartbeatInterval); + + Debug.Console(1, this, "Heartbeat Timer Started."); + } + + ServerHeartbeatCheckTimer.Reset(ServerHeartbeatInterval, ServerHeartbeatInterval); + } + + + /// + /// Connects the SSE Client + /// + /// + void ConnectStreamClient() + { + Debug.Console(0, this, "Initializing Stream client to server."); + + if (WSClient == null) + { + WSClient = new WebSocketClient(); + } + WSClient.URL = string.Format("wss://{0}/system/join/{1}", Config.ServerUrl, this.SystemUuid); + WSClient.Connect(); + Debug.Console(0, this, "Websocket connected"); + WSClient.ReceiveCallBack = WebsocketReceiveCallback; + //WSClient.SendCallBack = WebsocketSendCallback; + WSClient.ReceiveAsync(); + } + + /// + /// Resets reconnect timer and updates usercode + /// + /// + void HandleHeartBeat(JToken content) + { + SendMessageToServer(JObject.FromObject(new + { + type = "/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) + { + Debug.Console(0, this, "------ Begin HTTP Debug ---------------------------------------"); + Debug.Console(0, this, "HTTP Response URL: {0}", r.ResponseUrl.ToString()); + Debug.Console(0, this, "HTTP Response 'error' {0}", e); + Debug.Console(0, this, "HTTP Response code: {0}", r.Code); + Debug.Console(0, this, "HTTP Response content: \r{0}", r.ContentString); + Debug.Console(0, this, "------ End HTTP Debug -----------------------------------------"); + } + } + + /// + /// + /// + /// + /// + /// + /// + int WebsocketReceiveCallback(byte[] data, uint length, WebSocketClient.WEBSOCKET_PACKET_TYPES opcode, + WebSocketClient.WEBSOCKET_RESULT_CODES err) + { + var rx = System.Text.Encoding.UTF8.GetString(data, 0, (int)length); + if(rx.Length > 0) + ParseStreamRx(rx); + WSClient.ReceiveAsync(); + return 1; + } + + /// + /// Callback to catch possible errors in sending via the websocket + /// + /// + /// + int WebsocketSendCallback(Crestron.SimplSharp.CrestronWebSocketClient.WebSocketClient.WEBSOCKET_RESULT_CODES result) + { + Debug.Console(1, this, "SendCallback result: {0}", result); + + return 1; + } + + /// + /// + /// + /// + /// + void ParseStreamRx(string message) + { + if(string.IsNullOrEmpty(message)) + return; + + Debug.Console(1, this, "Message RX: '{0}'", message); + try + { + var messageObj = JObject.Parse(message); + + var type = messageObj["type"].Value(); + + if (type == "hello") + { + ResetOrStartHearbeatTimer(); + } + else if (type == "/system/heartbeat") + { + HandleHeartBeat(messageObj["content"]); + } + else if (type == "close") + { + WSClient.Disconnect(); + + ServerHeartbeatCheckTimer.Stop(); + // Start the reconnect timer + StartReconnectTimer(); + } + else + { + // Check path against Action dictionary + if (ActionDictionary.ContainsKey(type)) + { + var action = ActionDictionary[type]; + + if (action is Action) + { + (action as Action)(); + } + else if (action is PressAndHoldAction) + { + var stateString = messageObj["content"]["state"].Value(); + + // Look for a button press event + if (!string.IsNullOrEmpty(stateString)) + { + switch (stateString) + { + case "true": + { + if (!PushedActions.ContainsKey(type)) + { + PushedActions.Add(type, new CTimer(o => + { + (action as PressAndHoldAction)(false); + PushedActions.Remove(type); + }, null, ButtonHeartbeatInterval, ButtonHeartbeatInterval)); + } + // Maybe add an else to reset the timer + break; + } + case "held": + { + if (!PushedActions.ContainsKey(type)) + { + PushedActions[type].Reset(ButtonHeartbeatInterval, ButtonHeartbeatInterval); + } + return; + } + case "false": + { + if (PushedActions.ContainsKey(type)) + { + PushedActions[type].Stop(); + PushedActions.Remove(type); + } + break; + } + } + + (action as PressAndHoldAction)(stateString == "true"); + } + } + else if (action is Action) + { + var stateString = messageObj["content"]["state"].Value(); + + if (!string.IsNullOrEmpty(stateString)) + { + (action as Action)(stateString == "true"); + } + } + else if (action is Action) + { + (action as Action)(messageObj["content"]["value"].Value()); + } + else if (action is Action) + { + (action as Action)(messageObj["content"]["value"].Value()); + } + else if (action is Action) + { + (action as Action)(messageObj["content"] + .ToObject()); + } + } + else + { + Debug.Console(1, this, "-- Warning: Incoming message has no registered handler"); + } + } + } + catch (Exception err) + { + //Debug.Console(1, "SseMessageLengthBeforeFailureCount: {0}", SseMessageLengthBeforeFailureCount); + //SseMessageLengthBeforeFailureCount = 0; + Debug.Console(1, this, "Unable to parse message: {0}", err); + } + } + + void TestHttpRequest(string s) + { + { + s = s.Trim(); + if (string.IsNullOrEmpty(s)) + { + PrintTestHttpRequestUsage(); + return; + } + var tokens = s.Split(' '); + if (tokens.Length < 2) + { + CrestronConsole.ConsoleCommandResponse("Too few paramaters\r"); + PrintTestHttpRequestUsage(); + return; + } + + try + { + var url = tokens[1]; + if (tokens[0].ToLower() == "get") + { + var resp = new HttpClient().Get(url); + CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); + } + else if (tokens[0].ToLower() == "post") + { + var resp = new HttpClient().Post(url, new byte[] { }); + CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); + } + + else + { + CrestronConsole.ConsoleCommandResponse("Only get or post supported\r"); + PrintTestHttpRequestUsage(); + } + } + catch (HttpException e) + { + CrestronConsole.ConsoleCommandResponse("Exception in request:\r"); + CrestronConsole.ConsoleCommandResponse("Response URL: {0}\r", e.Response.ResponseUrl); + CrestronConsole.ConsoleCommandResponse("Response Error Code: {0}\r", e.Response.Code); + CrestronConsole.ConsoleCommandResponse("Response body: {0}\r", e.Response.ContentString); + } + + } + } + + void PrintTestHttpRequestUsage() + { + CrestronConsole.ConsoleCommandResponse("Usage: mobilehttprequest:N get/post url\r"); + } + } } \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/BridgeBase.cs b/PepperDashEssentials/Bridges/BridgeBase.cs index db5f2f98..d5d86c93 100644 --- a/PepperDashEssentials/Bridges/BridgeBase.cs +++ b/PepperDashEssentials/Bridges/BridgeBase.cs @@ -75,7 +75,12 @@ namespace PepperDash.Essentials.Bridges if (device != null) { - if (device is GenericComm) + if (device is PepperDash.Essentials.Core.Monitoring.SystemMonitorController) + { + (device as PepperDash.Essentials.Core.Monitoring.SystemMonitorController).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); + continue; + } + else if (device is GenericComm) { (device as GenericComm).LinkToApi(Eisc, d.JoinStart, d.JoinMapKey); continue; @@ -98,10 +103,12 @@ namespace PepperDash.Essentials.Bridges 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; } } } @@ -137,6 +144,7 @@ namespace PepperDash.Essentials.Bridges [JsonProperty("devices")] public List Devices { get; set; } + public class ApiDevice { [JsonProperty("deviceKey")] @@ -152,108 +160,4 @@ namespace PepperDash.Essentials.Bridges } - ///// - ///// API class for IBasicCommunication devices - ///// - //public class IBasicCommunicationApi : DeviceApiBase - //{ - // public IBasicCommunication Device { get; set; } - - // SerialFeedback TextReceivedFeedback; - - // public IBasicCommunicationApi(IBasicCommunication dev) - // { - // TextReceivedFeedback = new SerialFeedback(); - - // Device = dev; - - // SetupFeedbacks(); - - // ActionApi = new Dictionary - // { - // { "connect", new Action(Device.Connect) }, - // { "disconnect", new Action(Device.Disconnect) }, - // { "connectstate", new Action( b => ConnectByState(b) ) }, - // { "sendtext", new Action( s => Device.SendText(s) ) } - - // }; - - // FeedbackApi = new Dictionary - // { - // { "isconnected", new BoolFeedback( () => Device.IsConnected ) }, - // { "textrecieved", TextReceivedFeedback } - // }; - // } - - // /// - // /// Controls connection based on state of input - // /// - // /// - // void ConnectByState(bool state) - // { - // if (state) - // Device.Connect(); - // else - // Device.Disconnect(); - // } - - // void SetupFeedbacks() - // { - // Device.TextReceived += new EventHandler(Device_TextReceived); - - // if(Device is ISocketStatus) - // (Device as ISocketStatus).ConnectionChange += new EventHandler(IBasicCommunicationApi_ConnectionChange); - // } - - // void IBasicCommunicationApi_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) - // { - // FeedbackApi["isconnected"].FireUpdate(); - // } - - // void Device_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) - // { - // TextReceivedFeedback.FireUpdate(e.Text); - // } - //} - - - - ///// - ///// Each flavor of API is a static class with static properties and a static constructor that - ///// links up the things to do. - ///// - //public class DmChassisControllerApi : DeviceApiBase - //{ - // IntFeedback Output1Feedback; - // IntFeedback Output2Feedback; - - // public DmChassisControllerApi(DmChassisController dev) - // { - // Output1Feedback = new IntFeedback( new Func(() => 1)); - // Output2Feedback = new IntFeedback( new Func(() => 2)); - - // ActionApi = new Dictionary - // { - - // }; - - // FeedbackApi = new Dictionary - // { - // { "Output-1/fb", Output1Feedback }, - // { "Output-2/fb", Output2Feedback } - // }; - // } - - // /// - // /// Factory method - // /// - // /// - // /// - // public static DmChassisControllerApi GetActionApiForDevice(DmChassisController dev) - // { - // return new DmChassisControllerApi(dev); - // } - //} - - } \ No newline at end of file diff --git a/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs b/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs index 037a7a5b..cccf5fb4 100644 --- a/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs +++ b/PepperDashEssentials/Bridges/DmChassisControllerBridge.cs @@ -47,8 +47,6 @@ namespace PepperDash.Essentials.Bridges 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]); - - } } diff --git a/PepperDashEssentials/Bridges/SystemMonitorBridge.cs b/PepperDashEssentials/Bridges/SystemMonitorBridge.cs index 27199e03..a7ab1e1d 100644 --- a/PepperDashEssentials/Bridges/SystemMonitorBridge.cs +++ b/PepperDashEssentials/Bridges/SystemMonitorBridge.cs @@ -23,23 +23,79 @@ namespace PepperDash.Essentials.Bridges 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))); + //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.BACnetAppVersion]); - foreach (var p in SystemMonitor.ProgramCollection) + // 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]); + + p.Value.ProgramNameFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.ProgramName]); + p.Value.ProgramCompileTimeFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.ProgramCompiledTime]); + p.Value.CrestronDataBaseVersionFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.ProgramCrestronDatabaseVersion]); + p.Value.EnvironmentVersionFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.ProgramEnvironmentVersion]); + p.Value.AggregatedProgramInfoFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.AggregatedProgramInfo]); + + programSlotJoinStart = programSlotJoinStart + joinMap.ProgramOffsetJoin; } + + Debug.Console(1, systemMonitorController, "*****************************Manually Firing Feedback Updates....*****************************"); + + systemMonitorController.ControllerVersionFeedback.FireUpdate(); + systemMonitorController.TimeZoneFeedback.FireUpdate(); + systemMonitorController.TimeZoneTextFeedback.FireUpdate(); + systemMonitorController.IOControllerVersionFeedback.FireUpdate(); + systemMonitorController.SnmpVersionFeedback.FireUpdate(); + systemMonitorController.BACnetAppVersionFeedback.FireUpdate(); } + + } 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; } @@ -60,7 +116,7 @@ namespace PepperDash.Essentials.Bridges public uint ProgramCompiledTime { get; set; } public uint ProgramCrestronDatabaseVersion { get; set; } public uint ProgramEnvironmentVersion { get; set; } - + public uint AggregatedProgramInfo { get; set; } public SystemMonitorJoinMap() { @@ -72,15 +128,22 @@ namespace PepperDash.Essentials.Bridges BACnetAppVersion = 4; ControllerVersion = 5; - ProgramStart = 11; - ProgramStop = 12; - ProgramRegister = 13; - ProgramUnregister = 14; + + ProgramStartJoin = 10; + + ProgramOffsetJoin = 5; - ProgramName = 11; - ProgramCompiledTime = 12; - ProgramCrestronDatabaseVersion = 13; - ProgramEnvironmentVersion = 14; + // 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) @@ -95,15 +158,8 @@ namespace PepperDash.Essentials.Bridges BACnetAppVersion = BACnetAppVersion + joinOffset; ControllerVersion = ControllerVersion + joinOffset; - ProgramStart = ProgramStart + joinOffset; - ProgramStop = ProgramStop + joinOffset; - ProgramRegister = ProgramRegister + joinOffset; - ProgramUnregister = ProgramUnregister; - - ProgramName = ProgramName + joinOffset; - ProgramCompiledTime = ProgramCompiledTime + joinOffset; - ProgramCrestronDatabaseVersion = ProgramCrestronDatabaseVersion + joinOffset; - ProgramEnvironmentVersion = ProgramEnvironmentVersion + 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/essentials-framework b/essentials-framework index 389352d4..dc0b482a 160000 --- a/essentials-framework +++ b/essentials-framework @@ -1 +1 @@ -Subproject commit 389352d4dadcf9e54eb6ad141901b1bced0788ea +Subproject commit dc0b482acbc3f055821c3cc70955d224676b873d