diff --git a/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.projectinfo b/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.projectinfo index 33a04665..aedc307d 100644 Binary files a/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.projectinfo and b/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.projectinfo differ diff --git a/Essentials DM/Essentials_DM/Essentials_DM.projectinfo b/Essentials DM/Essentials_DM/Essentials_DM.projectinfo index dbf3a423..0c8e39d2 100644 Binary files a/Essentials DM/Essentials_DM/Essentials_DM.projectinfo and b/Essentials DM/Essentials_DM/Essentials_DM.projectinfo differ diff --git a/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.projectinfo b/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.projectinfo index 8a29be86..814b9dc6 100644 Binary files a/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.projectinfo and b/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.projectinfo differ diff --git a/Essentials/PepperDashEssentials/Config/ConfigReader.cs b/Essentials/PepperDashEssentials/Config/ConfigReader.cs index abf788f6..b633f40d 100644 --- a/Essentials/PepperDashEssentials/Config/ConfigReader.cs +++ b/Essentials/PepperDashEssentials/Config/ConfigReader.cs @@ -37,6 +37,8 @@ namespace PepperDash.Essentials { ConfigObject.TemplateUrl= doubleObj["template_url"].Value(); } + + } } catch (Exception e) diff --git a/Essentials/PepperDashEssentials/Config/DeviceFactory.cs b/Essentials/PepperDashEssentials/Config/DeviceFactory.cs index c5cd8686..0a3bc4f6 100644 --- a/Essentials/PepperDashEssentials/Config/DeviceFactory.cs +++ b/Essentials/PepperDashEssentials/Config/DeviceFactory.cs @@ -54,6 +54,12 @@ namespace PepperDash.Essentials var props = JsonConvert.DeserializeObject( properties.ToString()); return new ConsoleCommMockDevice(key, name, props, comm); + } + + else if (typeName == "webserver") + { + var props = JsonConvert.DeserializeObject(properties.ToString()); + return new CotijaSystemController(key, name, props); } return null; diff --git a/Essentials/PepperDashEssentials/Config/EssentialsConfig.cs b/Essentials/PepperDashEssentials/Config/EssentialsConfig.cs index e158c810..b9ba2bc8 100644 --- a/Essentials/PepperDashEssentials/Config/EssentialsConfig.cs +++ b/Essentials/PepperDashEssentials/Config/EssentialsConfig.cs @@ -17,6 +17,8 @@ namespace PepperDash.Essentials public string SystemUrl { get; set; } public string TemplateUrl { get; set; } + public CotijaConfig Cotija { get; private set; } + public string SystemUuid { get @@ -53,5 +55,7 @@ namespace PepperDash.Essentials public EssentialsConfig System { get; set; } public EssentialsConfig Template { get; set; } + + //public CotijaConfig Cotija { get; set; } } } \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/ControlSystem.cs b/Essentials/PepperDashEssentials/ControlSystem.cs index 7080c443..4a270d62 100644 --- a/Essentials/PepperDashEssentials/ControlSystem.cs +++ b/Essentials/PepperDashEssentials/ControlSystem.cs @@ -29,8 +29,8 @@ namespace PepperDash.Essentials /// public override void InitializeSystem() { - CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Reloads configuration file", - ConsoleAccessLevelEnum.AccessOperator); + //CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Reloads configuration file", + // ConsoleAccessLevelEnum.AccessOperator); //CrestronConsole.AddNewConsoleCommand(s => TearDown(), "ungo", "Reloads configuration file", // ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(s => @@ -57,11 +57,6 @@ namespace PepperDash.Essentials //PortalSync = new PepperDashPortalSyncClient(); - //Temp Cotija testing - CotijaInterfaceController CotijaInterface = new CotijaInterfaceController("WebClient1"); - - //CotijaInterface.InitializeClientAndEventStream("http://localhost:3000/api/system/stream/abcdefgh"); - Debug.Console(0, "Starting Essentials load from configuration"); ConfigReader.LoadConfig2(); LoadDevices(); @@ -69,6 +64,10 @@ namespace PepperDash.Essentials LoadRooms(); // FUSION - should go per room I believe. See CreateSystems in original Configuration.cs // ??? + + //Temp Cotija testing + //CotijaSystemController CotijaInterface = new CotijaSystemController("WebClient1"); + DeviceManager.ActivateAll(); Debug.Console(0, "Essentials load complete\r" + "-------------------------------------------------------------"); @@ -103,6 +102,7 @@ namespace PepperDash.Essentials Debug.Console(0, "Tear down COMPLETE"); } + /// /// Reads all devices from config and adds them to DeviceManager /// @@ -164,6 +164,13 @@ namespace PepperDash.Essentials { Debug.Console(1, "Room is EssentialsHuddleSpaceRoom, attempting to add to DeviceManager with Fusion"); DeviceManager.AddDevice(new EssentialsHuddleSpaceFusionSystemController((EssentialsHuddleSpaceRoom)room, 0xf1)); + + var cotija = DeviceManager.GetDeviceForKey("cotijaServer") as CotijaSystemController; + + if (cotija != null) + { + cotija.CotijaRooms.Add(new CotijaEssentialsHuddleSpaceRoomBridge(cotija, room as EssentialsHuddleSpaceRoom)); + } } else { diff --git a/Essentials/PepperDashEssentials/PepperDashEssentials.csproj b/Essentials/PepperDashEssentials/PepperDashEssentials.csproj index 00ef6d37..d56aabed 100644 --- a/Essentials/PepperDashEssentials/PepperDashEssentials.csproj +++ b/Essentials/PepperDashEssentials/PepperDashEssentials.csproj @@ -140,6 +140,8 @@ + + @@ -164,7 +166,7 @@ - + diff --git a/Essentials/PepperDashEssentials/PepperDashEssentials.projectinfo b/Essentials/PepperDashEssentials/PepperDashEssentials.projectinfo index e2cb3d80..b9a1020b 100644 Binary files a/Essentials/PepperDashEssentials/PepperDashEssentials.projectinfo and b/Essentials/PepperDashEssentials/PepperDashEssentials.projectinfo differ diff --git a/Essentials/PepperDashEssentials/Room/Cotija/CotijaConfig.cs b/Essentials/PepperDashEssentials/Room/Cotija/CotijaConfig.cs new file mode 100644 index 00000000..ef0bae48 --- /dev/null +++ b/Essentials/PepperDashEssentials/Room/Cotija/CotijaConfig.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials +{ + public class CotijaConfig : DeviceConfig + { + public string serverUrl { get; set; } + } +} \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs b/Essentials/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs new file mode 100644 index 00000000..7ac090f2 --- /dev/null +++ b/Essentials/PepperDashEssentials/Room/Cotija/CotijaSystemController.cs @@ -0,0 +1,336 @@ +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.SimplSharpPro; +using Crestron.SimplSharp.Net.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials +{ + public class CotijaSystemController : Device + { + GenericHttpSseClient SseClient; + + CCriticalSection FileLock; + + CotijaConfig Config; + + HttpClient Client; + + Dictionary ActionDictionary; + + CTimer Heartbeat; + + CTimer Reconnect; + + string SystemUuid; + + public List CotijaRooms { get; private set; } + + public CotijaSystemController(string key, string name, CotijaConfig config) : base(key, name) + { + Config = config; + + ActionDictionary = new Dictionary(); + + CotijaRooms = new List(); + + CrestronConsole.AddNewConsoleCommand(ConnectSseClient, "InitializeHttpClient", "Initializes a new HTTP client connection to a specified URL", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(DisconnectSseClient, "CloseHttpClient", "Closes the active HTTP client", ConsoleAccessLevelEnum.AccessOperator); + + AddPostActivationAction(() => RegisterSystemToServer(null)); + } + + public void AddAction(string key, object action) + { + // This might blow up if an action with that key already exists + ActionDictionary.Add(key, action); + } + + public void RemoveAction(string key) + { + if (ActionDictionary.ContainsKey(key)) + ActionDictionary.Remove(key); + } + + void ReconnectToServer(object o) + { + RegisterSystemToServer(null); + } + + /// + /// Registers the room with the server + /// + /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" + void RegisterSystemToServer(string command) + { + try + { + string filePath = string.Format(@"\NVRAM\Program{0}\configurationFile.json", Global.ControlSystem.ProgramNumber); + string postBody = null; + + if (string.IsNullOrEmpty(filePath)) + { + Debug.Console(0, this, "Error reading file. No path specified."); + return; + } + + FileLock = new CCriticalSection(); + + if (FileLock.TryEnter()) + { + Debug.Console(1, this, "Reading Configuration File"); + + postBody = File.ReadToEnd(filePath, Encoding.ASCII); + + Debug.Console(2, this, "{0}", postBody); + + FileLock.Leave(); + } + + if (string.IsNullOrEmpty(postBody)) + { + Debug.Console(1, "Post Body is null or empty"); + } + else + { + Client = new HttpClient(); + + HttpClientRequest request = new HttpClientRequest(); + + Client.Verbose = true; + Client.KeepAlive = true; + + SystemUuid = Essentials.ConfigReader.ConfigObject.SystemUuid; + + string url = string.Format("http://{0}/api/system/join/{1}", Config.serverUrl, SystemUuid); + + request.Url.Parse(url); + request.RequestType = RequestType.Post; + request.Header.SetHeaderValue("Content-Type", "application/json"); + request.ContentString = postBody; + + Client.DispatchAsync(request, PostConnectionCallback); + } + + } + catch (Exception e) + { + Debug.Console(0, this, "Error Initilizing Room: {0}", e); + } + + } + + /// + /// Posts 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 PostToServer(EssentialsRoomBase room, JObject o) + { + if(Client == null) + Client = new HttpClient(); + + HttpClientRequest request = new HttpClientRequest(); + + Client.Verbose = true; + Client.KeepAlive = true; + + string url = string.Format("http://{0}/api/room/{1}", Config.serverUrl, string.Format("{0}-{1}", SystemUuid, room.Key)); + + request.Url.Parse(url); + request.RequestType = RequestType.Post; + request.Header.SetHeaderValue("Content-Type", "application/json"); + request.ContentString = o.ToString(); + + Client.DispatchAsync(request, PostConnectionCallback); + } + + /// + /// Disconnects the SSE Client and stops the heartbeat timer + /// + /// + void DisconnectSseClient(string command) + { + if(SseClient != null) + SseClient.Disconnect(); + + if (Heartbeat != null) + { + Heartbeat.Stop(); + + Heartbeat = null; + } + } + + /// + /// The callback that fires when we get a response from our registration attempt + /// + /// + /// + void PostConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err) + { + try + { + if (resp != null && resp.Code == 200) + { + if (SseClient == null) + { + ConnectSseClient(null); + } + } + else + { + Debug.Console(0, this, "Unable to initialize SSE Client"); + } + } + catch (Exception e) + { + Debug.Console(1, this, "Error Initializeing SSE Client: {0}", e); + } + } + + /// + /// Executes when we don't get a heartbeat message in time. Triggers reconnect. + /// + /// + void HeartbeatExpired(object o) + { + if (Heartbeat != null) + { + Heartbeat.Stop(); + + Heartbeat = null; + } + + // Start the reconnect timer + Reconnect = new CTimer(ReconnectToServer, null, 5000, 5000); + + Reconnect.Reset(5000, 5000); + } + + + /// + /// Connects the SSE Client + /// + /// + void ConnectSseClient(object o) + { + Debug.Console(0, this, "Initializing SSE Client."); + + if (SseClient == null) + { + SseClient = new GenericHttpSseClient(string.Format("{0}-SseClient", Key), Name); + + CommunicationGather LineGathered = new CommunicationGather(SseClient, "\x0d\x0a"); + + LineGathered.LineReceived += new EventHandler(LineGathered_LineReceived); + } + else + { + if (SseClient.IsConnected) + { + SseClient.Disconnect(); + } + } + + string uuid = Essentials.ConfigReader.ConfigObject.SystemUuid; + + SseClient.Url = string.Format("http://{0}/api/system/stream/{1}", Config.serverUrl, uuid); + + SseClient.Connect(); + + //Heartbeat = new CTimer(HeartbeatExpired, null, 20000, 20000); + + //Heartbeat.Reset(20000, 20000); + } + + void LineGathered_LineReceived(object sender, GenericCommMethodReceiveTextArgs e) + { + //Debug.Console(1, this, "Received from Server: '{0}'", e.Text); + + if(e.Text.IndexOf("data:") > -1) + { + var message = e.Text.Substring(6); + + string roomId = null; + + Debug.Console(1, this, "Message: '{0}'", message); + + try + { + var messageObj = JObject.Parse(message); + + var type = messageObj["type"].Value(); + + if(type == "/system/hearbeat") + { + //Heartbeat.Reset(20000, 20000); + } + else if(type == "close") + { + SseClient.Disconnect(); + + // Start the reconnect timer + Reconnect = new CTimer(ConnectSseClient, null, 5000, 5000); + + Reconnect.Reset(5000, 5000); + } + else + { + + + // Check path against Action dictionary + if (ActionDictionary.ContainsKey(type)) + { + var action = ActionDictionary[type]; + + if (action is Action) + { + var stateString = messageObj["content"]["state"].Value(); + + // Look for a button press event + if(!string.IsNullOrEmpty(stateString)) + { +#warning deal with held state later + if (stateString == "held") + return; + + (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()); + } + } + + } + + } + catch (Exception err) + { + Debug.Console(1, this, "Unable to parse message: {0}", err); + } + } + } + } + + +} \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/UI/CotijaInterfaceController.cs b/Essentials/PepperDashEssentials/UI/CotijaInterfaceController.cs deleted file mode 100644 index 8f8bfc9c..00000000 --- a/Essentials/PepperDashEssentials/UI/CotijaInterfaceController.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; -using Crestron.SimplSharp.Net.Http; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.PageManagers; - -namespace PepperDash.Essentials -{ - public class CotijaInterfaceController : Device - { - GenericHttpSseClient SseClient; - - CCriticalSection FileLock; - - string ServerUrl; - - public CotijaInterfaceController(string key) : base(key) - { - CrestronConsole.AddNewConsoleCommand(RegisterRoomToServer, "InitializeHttpClient", "Initializes a new HTTP client connection to a specified URL", ConsoleAccessLevelEnum.AccessOperator); - } - - /// - /// Registers the room with the server - /// - /// URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port" - void RegisterRoomToServer(string url) - { - try - { - ServerUrl = url; - - string filePath = string.Format(@"\NVRAM\Program{0}\configurationFile.json", Global.ControlSystem.ProgramNumber); - string postBody = null; - - if (string.IsNullOrEmpty(filePath)) - { - Debug.Console(0, this, "Error reading file. No path specified."); - return; - } - - FileLock = new CCriticalSection(); - - if (FileLock.TryEnter()) - { - Debug.Console(1, this, "Reading Configuration File"); - - postBody = File.ReadToEnd(filePath, Encoding.ASCII); - - Debug.Console(2, this, "{0}", postBody); - - FileLock.Leave(); - } - - if (string.IsNullOrEmpty(postBody)) - { - Debug.Console(1, "Post Body is null or empty"); - } - else - { - HttpClient Client = new HttpClient(); - - HttpClientRequest Request = new HttpClientRequest(); - - Client.Verbose = true; - Client.KeepAlive = true; - - string uuid = Essentials.ConfigReader.ConfigObject.SystemUuid; - - url = string.Format("http://{0}/api/system/join/{1}", ServerUrl, uuid); - - Request.Url.Parse(url); - Request.RequestType = RequestType.Post; - Request.Header.SetHeaderValue("Content-Type", "application/json"); - Request.ContentString = postBody; - - Client.DispatchAsync(Request, PostConnectionCallback); - } - - } - catch (Exception e) - { - Debug.Console(0, this, "Error Initilizing Room: {0}", e); - } - - } - - /// - /// The callback that fires when we get a response from our registration attempt - /// - /// - /// - void PostConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err) - { - try - { - if (resp.Code == 200) - { - Debug.Console(0, this, "Initializing SSE Client."); - - if (SseClient == null) - { - SseClient = new GenericHttpSseClient(string.Format("{0}-SseClient", Key), Name); - } - else - { - if (SseClient.IsConnected) - { - SseClient.Disconnect(); - } - - string uuid = Essentials.ConfigReader.ConfigObject.SystemUuid; - - SseClient.Url = string.Format("http://{0}/api/system/stream/{1}", ServerUrl, uuid); - - SseClient.Connect(); - } - - CommunicationGather LineGathered = new CommunicationGather(SseClient, "\x0d\x0a"); - - LineGathered.LineReceived += new EventHandler(LineGathered_LineReceived); - - - } - else - { - Debug.Console(0, this, "Unable to initialize SSE Client"); - } - } - catch (Exception e) - { - Debug.Console(1, this, "Error Initializeing SSE Client: {0}", e); - } - } - - void LineGathered_LineReceived(object sender, GenericCommMethodReceiveTextArgs e) - { - Debug.Console(1, this, "Received from Node Server: '{0}'", e.Text); - } - - } -} \ No newline at end of file