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