- Added support for Node server communication implementing GenericHttpSseClient

This commit is contained in:
Neil Dorin
2017-06-08 16:00:35 -06:00
parent a2fb565524
commit 64b0e42c50
8 changed files with 319 additions and 231 deletions

View File

@@ -1,138 +1,150 @@
using System;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class ConfigReader
{
public static EssentialsConfig ConfigObject { get; private set; }
public static void LoadConfig2()
{
Debug.Console(0, "Using unmerged system/template configs.");
try
{
using (StreamReader fs = new StreamReader(string.Format(@"\NVRAM\program{0}\ConfigurationFile.json", InitialParametersClass.ApplicationNumber)))
{
var doubleObj = JObject.Parse(fs.ReadToEnd());
ConfigObject = MergeConfigs(doubleObj).ToObject<EssentialsConfig>();
}
}
catch (Exception e)
{
Debug.Console(0, "Config failed: \r{0}", e);
}
}
static JObject MergeConfigs(JObject doubleConfig)
{
var system = JObject.FromObject(doubleConfig["system"]);
var template = JObject.FromObject(doubleConfig["template"]);
var merged = new JObject();
// Put together top-level objects
if (system["info"] != null)
merged.Add("info", Merge(template["info"], system["info"]));
else
merged.Add("info", template["info"]);
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray, system["devices"] as JArray, "uid"));
if (system["rooms"] == null)
merged.Add("rooms", template["rooms"]);
else
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray, system["rooms"] as JArray, "key"));
if (system["sourceLists"] == null)
merged.Add("sourceLists", template["sourceLists"]);
else
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"]));
#warning Make tie lines merge appropriately
if (template["tieLines"] != null)
merged.Add("tieLines", template["tieLines"]);
else if (system["tieLines"] != null)
merged.Add("tieLines", system["tieLines"]);
else
merged.Add("tieLines", new JArray());
Debug.Console(0, "MERGED RESULT: \x0d\x0a{0}", merged);
return merged;
}
/// <summary>
/// Merges the contents of a base and a delta array, matching the entries on a top-level property
/// given by propertyName. Returns a merge of them. Items in the delta array that do not have
/// a matched item in base array will not be merged.
/// </summary>
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName)
{
var result = new JArray();
if (a2 == null)
result = a1;
else if (a1 != null)
{
for (int i = 0; i < a1.Count(); i++)
{
var a1Dev = a1[i];
// Try to get a system device and if found, merge it onto template
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid"));
if (a2Match != null)
{
var mergedItem = Merge(a1Dev, a2Match);// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
result.Add(mergedItem);
}
else
result.Add(a1Dev);
}
}
return result;
}
/// <summary>
/// Helper for using with JTokens. Converts to JObject
/// </summary>
static JObject Merge(JToken t1, JToken t2)
{
return Merge(JObject.FromObject(t1), JObject.FromObject(t2));
}
/// <summary>
/// Merge b ONTO a
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
static JObject Merge(JObject o1, JObject o2)
{
//Console.WriteLine("Merging {0}\ronto {1}", o2, o1);
foreach (var o2Prop in o2)
{
var o1Value = o1[o2Prop.Key];
if (o1Value == null)
o1.Add(o2Prop.Key, o2Prop.Value);
else
{
JToken replacement = null;
if (o2Prop.Value.HasValues && o1Value.HasValues) // Drill down
replacement = Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Prop.Value));
else
replacement = o2Prop.Value;
o1[o2Prop.Key].Replace(replacement);
}
}
return o1;
}
}
using System;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class ConfigReader
{
public static EssentialsConfig ConfigObject { get; private set; }
public static void LoadConfig2()
{
Debug.Console(0, "Using unmerged system/template configs.");
try
{
using (StreamReader fs = new StreamReader(string.Format(@"\NVRAM\program{0}\ConfigurationFile.json", InitialParametersClass.ApplicationNumber)))
{
var doubleObj = JObject.Parse(fs.ReadToEnd());
ConfigObject = MergeConfigs(doubleObj).ToObject<EssentialsConfig>();
// Extract SystemUrl and TemplateUrl
if (doubleObj["system_url"] != null)
{
ConfigObject.SystemUrl = doubleObj["system_url"].Value<string>();
}
if (doubleObj["template_url"] != null)
{
ConfigObject.TemplateUrl= doubleObj["template_url"].Value<string>();
}
}
}
catch (Exception e)
{
Debug.Console(0, "Config failed: \r{0}", e);
}
}
static JObject MergeConfigs(JObject doubleConfig)
{
var system = JObject.FromObject(doubleConfig["system"]);
var template = JObject.FromObject(doubleConfig["template"]);
var merged = new JObject();
// Put together top-level objects
if (system["info"] != null)
merged.Add("info", Merge(template["info"], system["info"]));
else
merged.Add("info", template["info"]);
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray, system["devices"] as JArray, "uid"));
if (system["rooms"] == null)
merged.Add("rooms", template["rooms"]);
else
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray, system["rooms"] as JArray, "key"));
if (system["sourceLists"] == null)
merged.Add("sourceLists", template["sourceLists"]);
else
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"]));
#warning Make tie lines merge appropriately
if (template["tieLines"] != null)
merged.Add("tieLines", template["tieLines"]);
else if (system["tieLines"] != null)
merged.Add("tieLines", system["tieLines"]);
else
merged.Add("tieLines", new JArray());
Debug.Console(0, "MERGED RESULT: \x0d\x0a{0}", merged);
return merged;
}
/// <summary>
/// Merges the contents of a base and a delta array, matching the entries on a top-level property
/// given by propertyName. Returns a merge of them. Items in the delta array that do not have
/// a matched item in base array will not be merged.
/// </summary>
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName)
{
var result = new JArray();
if (a2 == null)
result = a1;
else if (a1 != null)
{
for (int i = 0; i < a1.Count(); i++)
{
var a1Dev = a1[i];
// Try to get a system device and if found, merge it onto template
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid"));
if (a2Match != null)
{
var mergedItem = Merge(a1Dev, a2Match);// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
result.Add(mergedItem);
}
else
result.Add(a1Dev);
}
}
return result;
}
/// <summary>
/// Helper for using with JTokens. Converts to JObject
/// </summary>
static JObject Merge(JToken t1, JToken t2)
{
return Merge(JObject.FromObject(t1), JObject.FromObject(t2));
}
/// <summary>
/// Merge b ONTO a
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
static JObject Merge(JObject o1, JObject o2)
{
//Console.WriteLine("Merging {0}\ronto {1}", o2, o1);
foreach (var o2Prop in o2)
{
var o1Value = o1[o2Prop.Key];
if (o1Value == null)
o1.Add(o2Prop.Key, o2Prop.Value);
else
{
JToken replacement = null;
if (o2Prop.Value.HasValues && o1Value.HasValues) // Drill down
replacement = Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Prop.Value));
else
replacement = o2Prop.Value;
o1[o2Prop.Key].Replace(replacement);
}
}
return o1;
}
}
}

View File

@@ -1,29 +1,57 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class EssentialsConfig : BasicConfig
{
[JsonProperty("rooms")]
public List<EssentialsRoomConfig> Rooms { get; private set; }
}
/// <summary>
///
/// </summary>
public class SystemTemplateConfigs
{
public EssentialsConfig System { get; set; }
public EssentialsConfig Template { get; set; }
}
using System;
using System.Collections.Generic;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using System.Text.RegularExpressions;
namespace PepperDash.Essentials
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class EssentialsConfig : BasicConfig
{
public string SystemUrl { get; set; }
public string TemplateUrl { get; set; }
public string SystemUuid
{
get
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/templates\/(.*)#.*");
string uuid = result.Groups[1].Value;
return uuid;
}
}
public string TemplateUuid
{
get
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)#.*");
string uuid = result.Groups[1].Value;
return uuid;
}
}
[JsonProperty("rooms")]
public List<EssentialsRoomConfig> Rooms { get; private set; }
}
/// <summary>
///
/// </summary>
public class SystemTemplateConfigs
{
public EssentialsConfig System { get; set; }
public EssentialsConfig Template { get; set; }
}
}

View File

@@ -321,7 +321,7 @@ namespace PepperDash.Essentials.Fusion
"</Parameters>\n" +
"</RequestAction>\n";
Debug.Console(1, this, "Sending Fusion ActionRequest: \n{0}", actionRequest);
Debug.Console(2, this, "Sending Fusion ActionRequest: \n{0}", actionRequest);
FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQuery.StringValue = actionRequest;
}
@@ -336,21 +336,12 @@ namespace PepperDash.Essentials.Fusion
{
DateTime now = DateTime.Today;
//string currentTime = string.Format("Current time: {0:D4}-{1:D2}-{2:D2}T{3:D2}:{4:D2}:{5:D2}", now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
string currentTime = now.ToString("s");
Debug.Console(1, this, "Current time: {0}", currentTime);
//Debug.Console(1, this, "Current time: {0}", now.ToString("d"));
//string requestTest =
// string.Format("<RequestSchedule><RequestID>{0}</RequestID><RoomID>{1}</RoomID><Start>2017-05-02T00:00:00</Start><HourSpan>24</HourSpan></RequestSchedule>", requestID, GUID);
string requestTest =
string.Format("<RequestSchedule><RequestID>FullSchedleRequest</RequestID><RoomID>{0}</RoomID><Start>{1}</Start><HourSpan>24</HourSpan></RequestSchedule>", RoomGuid, currentTime);
Debug.Console(1, this, "Sending Fusion ScheduleQuery: \n{0}", requestTest);
Debug.Console(2, this, "Sending Fusion ScheduleQuery: \n{0}", requestTest);
FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.ScheduleQuery.StringValue = requestTest;
@@ -465,7 +456,7 @@ namespace PepperDash.Essentials.Fusion
"</Event>" +
"</CreateSchedule>";
Debug.Console(1, this, "Sending CreateMeeting Request: \n{0}", createMeetingRequest);
Debug.Console(2, this, "Sending CreateMeeting Request: \n{0}", createMeetingRequest);
FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.CreateMeeting.StringValue = createMeetingRequest;
@@ -482,7 +473,7 @@ namespace PepperDash.Essentials.Fusion
/// <param name="args"></param>
void ExtenderFusionRoomDataReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args)
{
Debug.Console(1, this, "Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue);
Debug.Console(2, this, "Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue);
if (args.Sig == FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQueryResponse)
@@ -578,7 +569,7 @@ namespace PepperDash.Essentials.Fusion
/// <param name="args"></param>
void FusionRoomSchedule_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args)
{
Debug.Console(1, this, "Scehdule Response Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue);
Debug.Console(2, this, "Scehdule Response Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue);
if (args.Sig == FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.ScheduleResponse)
@@ -674,7 +665,7 @@ namespace PepperDash.Essentials.Fusion
}
else if (args.Sig == FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.CreateResponse)
{
Debug.Console(1, this, "Create Meeting Response Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue);
Debug.Console(2, this, "Create Meeting Response Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue);
}
}

View File

@@ -14,77 +14,134 @@ namespace PepperDash.Essentials
{
public class CotijaInterfaceController : Device
{
StreamReader Reader;
GenericHttpSseClient SseClient;
Crestron.SimplSharp.Net.Connection Connection;
CCriticalSection FileLock;
string ServerUrl;
public CotijaInterfaceController(string key) : base(key)
{
CrestronConsole.AddNewConsoleCommand(InitializeClientAndEventStream, "InitializeHttpClient", "Initializes a new HTTP client connection to a specified URL", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(RegisterRoomToServer, "InitializeHttpClient", "Initializes a new HTTP client connection to a specified URL", ConsoleAccessLevelEnum.AccessOperator);
}
public void InitializeClientAndEventStream(string url)
/// <summary>
/// Registers the room with the server
/// </summary>
/// <param name="url">URL of the server, including the port number, if not 80. Format: "serverUrlOrIp:port"</param>
void RegisterRoomToServer(string url)
{
try
{
HttpClient webClient = new HttpClient();
webClient.Verbose = true;
HttpClientRequest request = new HttpClientRequest();
request.Url = new UrlParser(url);
request.Header.AddHeader(new HttpHeader("Accept", "text/event-stream"));
request.KeepAlive = true;
ServerUrl = url;
/// Experimenting with grabbing connection before the request
Connection = webClient.ConnectNew("192.168.1.120", 3000);
Debug.Console(1, this, "Connection Port: {0}", Connection.LocalEndPointPort);
Reader = new StreamReader(Connection);
Connection.OnBytesReceived += new EventHandler(DataConnection_OnBytesReceived);
string filePath = string.Format(@"\NVRAM\Program{0}\configurationFile.json", Global.ControlSystem.ProgramNumber);
string postBody = null;
/// Not sure if this is starting a new connection
webClient.DispatchAsync(request, StreamConnectionCallback);
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(1, this, "Error Initializing Cotija client:\n{0}", e);
Debug.Console(0, this, "Error Initilizing Room: {0}", e);
}
}
/// <summary>
/// The callback that fires when we get a response from our registration attempt
/// </summary>
/// <param name="resp"></param>
/// <param name="err"></param>
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<GenericCommMethodReceiveTextArgs>(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);
}
}
// This callback happens but none of the events would fire (when not commented out)
void StreamConnectionCallback(HttpClientResponse resp, HTTP_CALLBACK_ERROR err)
void LineGathered_LineReceived(object sender, GenericCommMethodReceiveTextArgs e)
{
Debug.Console(1, this, "Connection Port: {0}", resp.DataConnection.LocalEndPointPort);
Debug.Console(1, this, "Connections are equal {0}", resp.DataConnection == Connection);
Debug.Console(1, this, "Callback Fired");
//resp.DataConnection.OnBytesReceived += new EventHandler(DataConnection_OnBytesReceived);
//Debug.Console(1, this, "Received: '{0}'", resp.ContentString);
//resp.OnTransferEnd += new Crestron.SimplSharp.Net.TransferEndEventHandler(resp_OnTransferEnd);
//resp.OnTransferProgress += new Crestron.SimplSharp.Net.TransferProgressEventHandler(resp_OnTransferProgress);
//resp.OnTransferStart += new Crestron.SimplSharp.Net.TransferStartEventHandler(resp_OnTransferStart);
Debug.Console(1, this, "Received from Node Server: '{0}'", e.Text);
}
// We could never get this event handler to fire
void DataConnection_OnBytesReceived(object sender, EventArgs e)
{
Debug.Console(1, this, "OnBytesReceived Event Fired.");
Debug.Console(1, this, "Event: Received: '{0}'", Reader.ReadToEnd());
}
//void resp_OnTransferStart(object sender, Crestron.SimplSharp.Net.TransferStartEventArgs e)
//{
// Debug.Console(1, this, "OnTransferStart");
//}
//void resp_OnTransferProgress(object sender, Crestron.SimplSharp.Net.TransferProgressEventArgs e)
//{
// Debug.Console(1, this, "OnTransferProgress");
//}
//void resp_OnTransferEnd(object sender, Crestron.SimplSharp.Net.TransferEndEventArgs e)
//{
// Debug.Console(1, this, "OnTransferEnd");
//}
}
}