Compare commits

...

39 Commits

Author SHA1 Message Date
Neil Dorin
aa503f3b29 feat: Allows for the ItemKey to be left undefined or empty and the ParentDeviceKey to be used on it's own instead. 2024-08-28 13:16:27 -06:00
Neil Dorin
90251d92df fix: adds condition to handle legacy and current portal URL structures and adds null check for getting list types in basic config helper methods 2024-08-26 12:47:31 -06:00
Andrew Welker
941e537d53 Merge pull request #1196 from PepperDash/feature-2.0.0/routing-cooldown-fixes
Feature 2.0.0/routing cooldown fixes
2024-08-01 15:36:36 -04:00
Andrew Welker
f7c5e18af8 fix: add input port matching to route descriptors 2024-07-26 11:19:43 -05:00
Andrew Welker
ab73bbf979 fix: remove call to remove routerequest
The routerequest is being removed if the route is successfully made after the display has cooled down. This was an extraneous removal
2024-07-26 06:53:18 -05:00
Andrew Welker
1fb1947158 fix: ReleaseRoute callse ExecuteSwitch with null for input selector
Most devices that implement IRouting will now need to handle the possiblity of a null as the input selector, with the idea being that a null input selector should clear the route to whatever device is selected as the input selector. This may change to a typed value in the future.
2024-07-26 06:51:45 -05:00
Andrew Welker
e374f7b50f fix: add some options to destination type enum 2024-07-26 06:48:23 -05:00
Andrew Welker
7a263a644a fix: devcommstatus response now prints correctly 2024-07-26 06:47:48 -05:00
Andrew Welker
97bd30e9c9 fix: add async/await patterns for activation/deactivation 2024-07-26 06:47:13 -05:00
Andrew Welker
c56841d95b fix: attempt to catch a null ref happening in the Route Descriptor 2024-07-22 11:19:34 -05:00
Andrew Welker
64d6df70b0 fix: deactivate all rooms on startup
As part of the Essentials startup process, ALL rooms are activated, meaning there are unnecessary rooms activated. Deactiving them all prior to determining a combination scenario helps keep unecessary activity from happening.
2024-07-22 11:19:08 -05:00
Andrew Welker
d970d806c9 fix: add mutex to prevent multiple scenarios from running at once 2024-07-19 13:09:47 -05:00
Andrew Welker
5a9b876d80 fix: remove route request once the request has been handled 2024-07-18 13:46:40 -05:00
Andrew Welker
fb60683af6 feat: add async method for devjson 2024-07-18 13:44:49 -05:00
Andrew Welker
b63996b9e6 fix: set partition state to match physical partitions when changing from manual to auto mode 2024-07-08 13:22:18 -05:00
Andrew Welker
bc217a2008 chore: add logging for current scenario searching 2024-07-08 08:44:45 -05:00
Andrew Welker
fec6b0d385 fix: change source for PartitionPresent depending on mode
Added `FireUpdate` calls for feedback on mode change in order to get correct values when changing modes.
2024-07-08 08:41:03 -05:00
Neil Dorin
f3b4c0aa02 fix: resolves issue with incorrect partition state feedback in test mode 2024-06-28 13:56:42 -06:00
Neil Dorin
a3351812cd fix: fixes for room combination in manual/auto mode with actual crestron partition sensors 2024-06-28 13:20:19 -06:00
Neil Dorin
71815eff17 fix: updates mode for partition sensors when mode of room combiner changes 2024-06-28 12:57:06 -06:00
Neil Dorin
c7f4bf1fb2 fix: fixes backing values for eLevelControlType 2024-06-28 12:42:43 -06:00
Neil Dorin
bec3ab8e73 fix: Adds missing initializer for CameraLists 2024-06-28 11:27:22 -06:00
Neil Dorin
c499d2a2eb fix: corrects spelling mistake 2024-06-28 09:01:59 -06:00
Neil Dorin
d9721a362e feat: adds method to set input source type and corresponding enum to ICiscoCodecCameraConfig 2024-06-28 08:55:18 -06:00
Neil Dorin
5fb6f3e117 fix: Fixes namespace for CameraListItem 2024-06-25 22:20:13 -06:00
Neil Dorin
c2fb44a662 feat: adds CameraListKey to EssentialsAvRoomPropertiesConfig 2024-06-25 21:22:49 -06:00
Neil Dorin
0f9bddf4dd fix: updates EssentialsRoomBase to add CameraListKey 2024-06-25 17:58:44 -06:00
Neil Dorin
ddc2491664 feat: updates to IEssentialsRoom for CameraListKey and adds helper method to get camera list from config 2024-06-25 17:15:45 -06:00
Neil Dorin
9a6209f50a feat: Adds basic CamerLists config structure and CameraListItems class 2024-06-25 16:08:44 -06:00
Andrew Welker
49852d25e8 Merge pull request #1194 from PepperDash/feature-2.0.0/routing-extension-changes
Feature 2.0.0/routing extension changes
2024-06-24 16:01:37 -04:00
Andrew Welker
ba4ca20936 fix: add tieline type to output for console & CWS 2024-06-24 14:57:07 -05:00
Andrew Welker
c50726f813 feat: separate audio & video route descriptors
Also cleaned up logging some
2024-06-24 14:54:37 -05:00
Neil Dorin
0aafe8a62e fix: add message to indicate no handler registered when debugging stream data 2024-06-24 13:45:07 -06:00
Neil Dorin
71005940ac fix: catches last condition for destinationPort match 2024-06-21 15:00:17 -06:00
Neil Dorin
e5e79316a6 fix: fixes conditions for specific port and device matches 2024-06-21 14:53:03 -06:00
Neil Dorin
5aa1f85df5 feat: adds try catch to devjson execution thread and adds port names for sdi in/out 2024-06-20 11:57:32 -06:00
Andrew Welker
7bac65002d fix: correct issues with method calls 2024-06-19 15:01:25 -05:00
Andrew Welker
ed0141a536 fix: change ports and what's used where 2024-06-19 14:37:04 -05:00
Andrew Welker
25ebcdfb5d feat: update routing methods
Routing methods will now take a source port and destination port. This should solve the issue where a device could have multiple input ports defined in tielines and allow Essentials routing to find a path correctly.
2024-06-19 14:09:59 -05:00
26 changed files with 1400 additions and 977 deletions

View File

@@ -101,6 +101,7 @@ namespace PepperDash.Essentials.Core
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
return;
}
var textHandler = TextReceived;
if (textHandler != null)
@@ -108,7 +109,10 @@ namespace PepperDash.Essentials.Core
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
return;
}
Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
}
public override bool Deactivate()

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Essentials.Core.Devices;
namespace PepperDash.Essentials.Core.Config
{
@@ -28,6 +29,9 @@ namespace PepperDash.Essentials.Core.Config
[JsonProperty("audioControlPointLists")]
public Dictionary<string, AudioControlPointListItem> AudioControlPointLists { get; set; }
[JsonProperty("cameraLists")]
public Dictionary<string, Dictionary<string, CameraListItem>> CameraLists { get; set; }
[JsonProperty("tieLines")]
public List<TieLineConfig> TieLines { get; set; }
@@ -41,6 +45,7 @@ namespace PepperDash.Essentials.Core.Config
SourceLists = new Dictionary<string, Dictionary<string, SourceListItem>>();
DestinationLists = new Dictionary<string, Dictionary<string, DestinationListItem>>();
AudioControlPointLists = new Dictionary<string, AudioControlPointListItem>();
CameraLists = new Dictionary<string, Dictionary<string, CameraListItem>>();
TieLines = new List<TieLineConfig>();
JoinMaps = new Dictionary<string, JObject>();
}
@@ -50,7 +55,7 @@ namespace PepperDash.Essentials.Core.Config
/// </summary>
public Dictionary<string, SourceListItem> GetSourceListForKey(string key)
{
if (string.IsNullOrEmpty(key) || !SourceLists.ContainsKey(key))
if (SourceLists == null || string.IsNullOrEmpty(key) || !SourceLists.ContainsKey(key))
return null;
return SourceLists[key];
@@ -63,7 +68,7 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>DestinationList if the key exists, null otherwise</returns>
public Dictionary<string, DestinationListItem> GetDestinationListForKey(string key)
{
if (string.IsNullOrEmpty(key) || !DestinationLists.ContainsKey(key))
if (DestinationLists == null || string.IsNullOrEmpty(key) || !DestinationLists.ContainsKey(key))
{
return null;
}
@@ -78,12 +83,23 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>AudioControlPointList if the key exists, null otherwise</returns>
public AudioControlPointListItem GetAudioControlPointListForKey(string key)
{
if (string.IsNullOrEmpty(key) || !AudioControlPointLists.ContainsKey(key))
if (AudioControlPointLists == null || string.IsNullOrEmpty(key) || !AudioControlPointLists.ContainsKey(key))
return null;
return AudioControlPointLists[key];
}
/// <summary>
/// Checks CameraLists for a given list and returns it if found. Otherwise, returns null
/// </summary>
public Dictionary<string, CameraListItem> GetCameraListForKey(string key)
{
if (CameraLists == null || string.IsNullOrEmpty(key) || !CameraLists.ContainsKey(key))
return null;
return CameraLists[key];
}
/// <summary>
/// Checks Devices for an item with a Key that matches and returns it if found. Otherwise, retunes null
/// </summary>

View File

@@ -31,9 +31,17 @@ namespace PepperDash.Essentials.Core.Config
if (string.IsNullOrEmpty(SystemUrl))
return "missing url";
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
if (SystemUrl.Contains("#"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
string uuid = result.Groups[1].Value;
return uuid;
}
}
}
@@ -44,10 +52,18 @@ namespace PepperDash.Essentials.Core.Config
{
if (string.IsNullOrEmpty(TemplateUrl))
return "missing template url";
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
if (TemplateUrl.Contains("#"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
string uuid = result.Groups[2].Value;
return uuid;
}
}
}

View File

@@ -9,9 +9,15 @@ namespace PepperDash.Essentials.Core
{
public abstract class AudioControlListItemBase
{
/// <summary>
/// Key of the parent device in the DeviceManager
/// </summary>
[JsonProperty("parentDeviceKey")]
public string ParentDeviceKey { get; set; }
/// <summary>
/// Optional key of the item in the parent device
/// </summary>
[JsonProperty("itemKey")]
public string ItemKey { get; set; }

View File

@@ -0,0 +1,76 @@
using Newtonsoft.Json;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
public class CameraListItem
{
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
/// <summary>
/// Returns the source Device for this, if it exists in DeviceManager
/// </summary>
[JsonIgnore]
public Device CameraDevice
{
get
{
if (_cameraDevice == null)
_cameraDevice = DeviceManager.GetDeviceForKey(DeviceKey) as Device;
return _cameraDevice;
}
}
Device _cameraDevice;
/// <summary>
/// Gets either the source's Name or this AlternateName property, if
/// defined. If source doesn't exist, returns "Missing source"
/// </summary>
[JsonProperty("preferredName")]
public string PreferredName
{
get
{
if (string.IsNullOrEmpty(Name))
{
if (CameraDevice == null)
return "---";
return CameraDevice.Name;
}
return Name;
}
}
/// <summary>
/// A name that will override the source's name on the UI
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Specifies and icon for the source list item
/// </summary>
[JsonProperty("icon")]
public string Icon { get; set; }
/// <summary>
/// Alternate icon
/// </summary>
[JsonProperty("altIcon", NullValueHandling = NullValueHandling.Ignore)]
public string AltIcon { get; set; }
/// <summary>
/// Indicates if the item should be included in the user facing list
/// </summary>
[JsonProperty("includeInUserList")]
public bool IncludeInUserList { get; set; }
/// <summary>
/// Used to specify the order of the items in the source list when displayed
/// </summary>
[JsonProperty("order")]
public int Order { get; set; }
}
}

View File

@@ -1,133 +1,198 @@
using Crestron.SimplSharp;
using Newtonsoft.Json;
using PepperDash.Core;
using Serilog.Events;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using System.Reflection;
using Newtonsoft.Json;
using PepperDash.Core;
using Serilog.Events;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{
public class DeviceJsonApi
{
/// <summary>
///
/// </summary>
/// <param name="json"></param>
public static void DoDeviceActionWithJson(string json)
{
if (String.IsNullOrEmpty(json))
{
CrestronConsole.ConsoleCommandResponse(
"Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted.");
return;
}
try
{
var action = JsonConvert.DeserializeObject<DeviceActionWrapper>(json);
public class DeviceJsonApi
{
/// <summary>
///
/// </summary>
/// <param name="json"></param>
public static void DoDeviceActionWithJson(string json)
{
if (String.IsNullOrEmpty(json))
{
CrestronConsole.ConsoleCommandResponse(
"Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted.");
return;
}
try
{
var action = JsonConvert.DeserializeObject<DeviceActionWrapper>(json);
DoDeviceAction(action);
}
catch (Exception ex)
{
CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}");
}
}
DoDeviceAction(action);
}
catch (Exception)
{
CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}");
}
}
/// <summary>
///
/// </summary>
/// <param name="action"></param>
public static void DoDeviceAction(DeviceActionWrapper action)
{
var key = action.DeviceKey;
var obj = FindObjectOnPath(key);
if (obj == null)
{
CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key);
return;
}
/// <summary>
///
/// </summary>
/// <param name="action"></param>
public static void DoDeviceAction(DeviceActionWrapper action)
{
var key = action.DeviceKey;
var obj = FindObjectOnPath(key);
if (obj == null)
{
CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key);
return;
}
if (action.Params == null)
{
if (action.Params == null)
{
//no params, so setting action.Params to empty array
action.Params = new object[0];
}
action.Params = new object[0];
}
Type t = obj.GetType();
try
{
var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList();
Type t = obj.GetType();
try
{
var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList();
var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length);
var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length);
if (method == null)
{
CrestronConsole.ConsoleCommandResponse(
"Unable to find method with name {0} and that matches parameters {1}", action.MethodName,
action.Params);
return;
}
if (method == null)
{
CrestronConsole.ConsoleCommandResponse(
"Unable to find method with name {0} and that matches parameters {1}", action.MethodName,
action.Params);
return;
}
var mParams = method.GetParameters();
var convertedParams = mParams
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
.ToArray();
Task.Run(() => method.Invoke(obj, convertedParams));
Task.Run(() =>
{
try
{
Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
method.Invoke(obj, convertedParams);
}
catch (Exception e)
{
Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
}
});
CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name,
action.DeviceKey);
}
catch (Exception ex)
{
CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName,
ex.Message);}
}
CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name,
action.DeviceKey);
}
catch (Exception ex)
{
CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName,
ex.Message);
}
}
private static object ConvertType(object value, Type conversionType)
{
if (!conversionType.IsEnum)
{
return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture);
}
public static async Task DoDeviceActionAsync(DeviceActionWrapper action)
{
var key = action.DeviceKey;
var obj = FindObjectOnPath(key);
if (obj == null)
{
Debug.LogMessage(LogEventLevel.Warning, "Unable to find object at path {deviceKey}", null, key);
return;
}
var stringValue = Convert.ToString(value);
if (action.Params == null)
{
//no params, so setting action.Params to empty array
action.Params = new object[0];
}
if (String.IsNullOrEmpty(stringValue))
{
throw new InvalidCastException(
String.Format("{0} cannot be converted to a string prior to conversion to enum"));
}
return Enum.Parse(conversionType, stringValue, true);
}
Type t = obj.GetType();
try
{
var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList();
/// <summary>
/// Gets the properties on a device
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string GetProperties(string deviceObjectPath)
{
var obj = FindObjectOnPath(deviceObjectPath);
if (obj == null)
return "{ \"error\":\"No Device\"}";
var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length);
Type t = obj.GetType();
// get the properties and set them into a new collection of NameType wrappers
var props = t.GetProperties().Select(p => new PropertyNameType(p, obj));
return JsonConvert.SerializeObject(props, Formatting.Indented);
}
if (method == null)
{
Debug.LogMessage(LogEventLevel.Warning,
"Unable to find method with name {methodName} and that matches parameters {@parameters}", null, action.MethodName,
action.Params);
return;
}
var mParams = method.GetParameters();
var convertedParams = mParams
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
.ToArray();
await Task.Run(() =>
{
try
{
Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params);
method.Invoke(obj, convertedParams);
}
catch (Exception e)
{
Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
}
});
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Unable to call method with name {methodName} with {@parameters}", null, action.MethodName, action.Params);
}
}
private static object ConvertType(object value, Type conversionType)
{
if (!conversionType.IsEnum)
{
return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture);
}
var stringValue = Convert.ToString(value);
if (String.IsNullOrEmpty(stringValue))
{
throw new InvalidCastException(
String.Format("{0} cannot be converted to a string prior to conversion to enum"));
}
return Enum.Parse(conversionType, stringValue, true);
}
/// <summary>
/// Gets the properties on a device
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string GetProperties(string deviceObjectPath)
{
var obj = FindObjectOnPath(deviceObjectPath);
if (obj == null)
return "{ \"error\":\"No Device\"}";
Type t = obj.GetType();
// get the properties and set them into a new collection of NameType wrappers
var props = t.GetProperties().Select(p => new PropertyNameType(p, obj));
return JsonConvert.SerializeObject(props, Formatting.Indented);
}
/// <summary>
/// Gets a property from a device path by name
@@ -138,9 +203,9 @@ namespace PepperDash.Essentials.Core
public static object GetPropertyByName(string deviceObjectPath, string propertyName)
{
var dev = FindObjectOnPath(deviceObjectPath);
if(dev == null)
if (dev == null)
return "{ \"error\":\"No Device\"}";
object prop = dev.GetType().GetType().GetProperty(propertyName).GetValue(dev, null);
// var prop = t.GetProperty(propertyName);
@@ -155,126 +220,126 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Gets the methods on a device
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string GetMethods(string deviceObjectPath)
{
var obj = FindObjectOnPath(deviceObjectPath);
if (obj == null)
return "{ \"error\":\"No Device\"}";
/// <summary>
/// Gets the methods on a device
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string GetMethods(string deviceObjectPath)
{
var obj = FindObjectOnPath(deviceObjectPath);
if (obj == null)
return "{ \"error\":\"No Device\"}";
// Package up method names using helper objects
Type t = obj.GetType();
var methods = t.GetMethods()
.Where(m => !m.IsSpecialName)
.Select(p => new MethodNameParams(p));
return JsonConvert.SerializeObject(methods, Formatting.Indented);
}
// Package up method names using helper objects
Type t = obj.GetType();
var methods = t.GetMethods()
.Where(m => !m.IsSpecialName)
.Select(p => new MethodNameParams(p));
return JsonConvert.SerializeObject(methods, Formatting.Indented);
}
public static string GetApiMethods(string deviceObjectPath)
{
var obj = FindObjectOnPath(deviceObjectPath);
if (obj == null)
return "{ \"error\":\"No Device\"}";
public static string GetApiMethods(string deviceObjectPath)
{
var obj = FindObjectOnPath(deviceObjectPath);
if (obj == null)
return "{ \"error\":\"No Device\"}";
// Package up method names using helper objects
Type t = obj.GetType();
var methods = t.GetMethods()
.Where(m => !m.IsSpecialName)
.Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any())
.Select(p => new MethodNameParams(p));
return JsonConvert.SerializeObject(methods, Formatting.Indented);
}
// Package up method names using helper objects
Type t = obj.GetType();
var methods = t.GetMethods()
.Where(m => !m.IsSpecialName)
.Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any())
.Select(p => new MethodNameParams(p));
return JsonConvert.SerializeObject(methods, Formatting.Indented);
}
/// <summary>
/// Walks down a dotted object path, starting with a Device, and returns the object
/// at the end of the path
/// </summary>
public static object FindObjectOnPath(string deviceObjectPath)
{
var path = deviceObjectPath.Split('.');
var dev = DeviceManager.GetDeviceForKey(path[0]);
if (dev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device {0} not found", path[0]);
return null;
}
/// <summary>
/// Walks down a dotted object path, starting with a Device, and returns the object
/// at the end of the path
/// </summary>
public static object FindObjectOnPath(string deviceObjectPath)
{
var path = deviceObjectPath.Split('.');
// loop through any dotted properties
object obj = dev;
if (path.Length > 1)
{
for (int i = 1; i < path.Length; i++)
{
var objName = path[i];
string indexStr = null;
var indexOpen = objName.IndexOf('[');
if (indexOpen != -1)
{
var indexClose = objName.IndexOf(']');
if (indexClose == -1)
{
Debug.LogMessage(LogEventLevel.Information, dev, "ERROR Unmatched index brackets");
return null;
}
// Get the index and strip quotes if any
indexStr = objName.Substring(indexOpen + 1, indexClose - indexOpen - 1).Replace("\"", "");
objName = objName.Substring(0, indexOpen);
Debug.LogMessage(LogEventLevel.Information, dev, " Checking for collection '{0}', index '{1}'", objName, indexStr);
}
var dev = DeviceManager.GetDeviceForKey(path[0]);
if (dev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device {0} not found", path[0]);
return null;
}
Type oType = obj.GetType();
var prop = oType.GetProperty(objName);
if (prop == null)
{
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} not found on {1}", objName, path[i - 1]);
return null;
}
// if there's an index, try to get the property
if (indexStr != null)
{
if (!typeof(ICollection).IsAssignableFrom(prop.PropertyType))
{
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} is not collection", objName);
return null;
}
var collection = prop.GetValue(obj, null) as ICollection;
// Get the indexed items "property"
var indexedPropInfo = prop.PropertyType.GetProperty("Item");
// These are the parameters for the indexing. Only care about one
var indexParams = indexedPropInfo.GetIndexParameters();
if (indexParams.Length > 0)
{
Debug.LogMessage(LogEventLevel.Information, " Indexed, param type: {0}", indexParams[0].ParameterType.Name);
var properParam = Convert.ChangeType(indexStr, indexParams[0].ParameterType,
System.Globalization.CultureInfo.InvariantCulture);
try
{
obj = indexedPropInfo.GetValue(collection, new object[] { properParam });
}
// if the index is bad, catch it here.
catch (TargetInvocationException e)
{
if (e.InnerException is ArgumentOutOfRangeException)
Debug.LogMessage(LogEventLevel.Information, " Index Out of range");
else if (e.InnerException is KeyNotFoundException)
Debug.LogMessage(LogEventLevel.Information, " Key not found");
return null;
}
}
// loop through any dotted properties
object obj = dev;
if (path.Length > 1)
{
for (int i = 1; i < path.Length; i++)
{
var objName = path[i];
string indexStr = null;
var indexOpen = objName.IndexOf('[');
if (indexOpen != -1)
{
var indexClose = objName.IndexOf(']');
if (indexClose == -1)
{
Debug.LogMessage(LogEventLevel.Information, dev, "ERROR Unmatched index brackets");
return null;
}
// Get the index and strip quotes if any
indexStr = objName.Substring(indexOpen + 1, indexClose - indexOpen - 1).Replace("\"", "");
objName = objName.Substring(0, indexOpen);
Debug.LogMessage(LogEventLevel.Information, dev, " Checking for collection '{0}', index '{1}'", objName, indexStr);
}
}
else
obj = prop.GetValue(obj, null);
}
}
return obj;
}
Type oType = obj.GetType();
var prop = oType.GetProperty(objName);
if (prop == null)
{
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} not found on {1}", objName, path[i - 1]);
return null;
}
// if there's an index, try to get the property
if (indexStr != null)
{
if (!typeof(ICollection).IsAssignableFrom(prop.PropertyType))
{
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} is not collection", objName);
return null;
}
var collection = prop.GetValue(obj, null) as ICollection;
// Get the indexed items "property"
var indexedPropInfo = prop.PropertyType.GetProperty("Item");
// These are the parameters for the indexing. Only care about one
var indexParams = indexedPropInfo.GetIndexParameters();
if (indexParams.Length > 0)
{
Debug.LogMessage(LogEventLevel.Information, " Indexed, param type: {0}", indexParams[0].ParameterType.Name);
var properParam = Convert.ChangeType(indexStr, indexParams[0].ParameterType,
System.Globalization.CultureInfo.InvariantCulture);
try
{
obj = indexedPropInfo.GetValue(collection, new object[] { properParam });
}
// if the index is bad, catch it here.
catch (TargetInvocationException e)
{
if (e.InnerException is ArgumentOutOfRangeException)
Debug.LogMessage(LogEventLevel.Information, " Index Out of range");
else if (e.InnerException is KeyNotFoundException)
Debug.LogMessage(LogEventLevel.Information, " Key not found");
return null;
}
}
}
else
obj = prop.GetValue(obj, null);
}
}
return obj;
}
/// <summary>
/// Sets a property on an object.
@@ -297,78 +362,85 @@ namespace PepperDash.Essentials.Core
//return JsonConvert.SerializeObject(props, Formatting.Indented);
}
}
public class DeviceActionWrapper
{
public string DeviceKey { get; set; }
public string MethodName { get; set; }
public object[] Params { get; set; }
}
}
public class PropertyNameType
{
object Parent;
public class DeviceActionWrapper
{
public string DeviceKey { get; set; }
public string MethodName { get; set; }
public object[] Params { get; set; }
}
[JsonIgnore]
public PropertyInfo PropInfo { get; private set; }
public string Name { get { return PropInfo.Name; } }
public string Type { get { return PropInfo.PropertyType.Name; } }
public string Value { get
public class PropertyNameType
{
private object Parent;
[JsonIgnore]
public PropertyInfo PropInfo { get; private set; }
public string Name { get { return PropInfo.Name; } }
public string Type { get { return PropInfo.PropertyType.Name; } }
public string Value
{
if (PropInfo.CanRead)
get
{
try
if (PropInfo.CanRead)
{
return PropInfo.GetValue(Parent, null).ToString();
try
{
return PropInfo.GetValue(Parent, null).ToString();
}
catch (Exception)
{
return null;
}
}
catch (Exception)
{
else
return null;
}
}
else
return null;
} }
}
public bool CanRead { get { return PropInfo.CanRead; } }
public bool CanWrite { get { return PropInfo.CanWrite; } }
public PropertyNameType(PropertyInfo info, object parent)
{
PropInfo = info;
public PropertyNameType(PropertyInfo info, object parent)
{
PropInfo = info;
Parent = parent;
}
}
}
}
public class MethodNameParams
{
[JsonIgnore]
public MethodInfo MethodInfo { get; private set; }
public class MethodNameParams
{
[JsonIgnore]
public MethodInfo MethodInfo { get; private set; }
public string Name { get { return MethodInfo.Name; } }
public IEnumerable<NameType> Params { get {
return MethodInfo.GetParameters().Select(p =>
new NameType { Name = p.Name, Type = p.ParameterType.Name });
} }
public string Name { get { return MethodInfo.Name; } }
public IEnumerable<NameType> Params
{
get
{
return MethodInfo.GetParameters().Select(p =>
new NameType { Name = p.Name, Type = p.ParameterType.Name });
}
}
public MethodNameParams(MethodInfo info)
{
MethodInfo = info;
}
}
public MethodNameParams(MethodInfo info)
{
MethodInfo = info;
}
}
public class NameType
{
public string Name { get; set; }
public string Type { get; set; }
}
public class NameType
{
public string Name { get; set; }
public string Type { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class ApiAttribute : Attribute
{
[AttributeUsage(AttributeTargets.All)]
public class ApiAttribute : Attribute
{
}
}
}

View File

@@ -1,50 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using PepperDash.Core;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace PepperDash.Essentials.Core
{
public static class DeviceManager
{
public static class DeviceManager
{
public static event EventHandler<EventArgs> AllDevicesActivated;
public static event EventHandler<EventArgs> AllDevicesRegistered;
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
//public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>();
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
//public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>();
/// <summary>
/// Returns a copy of all the devices in a list
/// </summary>
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } }
private static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
public static bool AddDeviceEnabled;
/// <summary>
/// Returns a copy of all the devices in a list
/// </summary>
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } }
public static void Initialize(CrestronControlSystem cs)
{
AddDeviceEnabled = true;
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
public static bool AddDeviceEnabled;
public static void Initialize(CrestronControlSystem cs)
{
AddDeviceEnabled = true;
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);
@@ -52,77 +51,77 @@ namespace PepperDash.Essentials.Core
CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator);
}
/// <summary>
/// Calls activate steps on all Device class items
/// </summary>
public static void ActivateAll()
{
try
{
/// <summary>
/// Calls activate steps on all Device class items
/// </summary>
public static void ActivateAll()
{
try
{
OnAllDevicesRegistered();
DeviceCriticalSection.Enter();
DeviceCriticalSection.Enter();
AddDeviceEnabled = false;
// PreActivate all devices
Debug.LogMessage(LogEventLevel.Information,"****PreActivation starting...****");
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PreActivate();
}
catch (Exception e)
{
// PreActivate all devices
Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****");
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PreActivate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
}
}
Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****");
Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****");
Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****");
// Activate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).Activate();
}
catch (Exception e)
{
// Activate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).Activate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
}
}
Debug.LogMessage(LogEventLevel.Information, "****Activation complete****");
Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****");
// PostActivate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PostActivate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
// PostActivate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PostActivate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****");
OnAllDevicesActivated();
}
finally
{
}
finally
{
DeviceCriticalSection.Leave();
}
}
}
}
private static void OnAllDevicesActivated()
{
@@ -142,77 +141,76 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Calls activate on all Device class items
/// </summary>
public static void DeactivateAll()
{
try
{
DeviceCriticalSection.Enter();
foreach (var d in Devices.Values.OfType<Device>())
{
d.Deactivate();
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
/// <summary>
/// Calls activate on all Device class items
/// </summary>
public static void DeactivateAll()
{
try
{
DeviceCriticalSection.Enter();
foreach (var d in Devices.Values.OfType<Device>())
{
d.Deactivate();
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
//static void ListMethods(string devKey)
//{
// var dev = GetDeviceForKey(devKey);
// if(dev != null)
// {
// var type = dev.GetType().GetType();
// var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance);
// var sb = new StringBuilder();
// sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length));
// foreach (var m in methods)
// {
// sb.Append(string.Format("{0}(", m.Name));
// var pars = m.GetParameters();
// foreach (var p in pars)
// sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name));
// sb.AppendLine(")");
// }
// CrestronConsole.ConsoleCommandResponse(sb.ToString());
// }
//}
//static void ListMethods(string devKey)
//{
// var dev = GetDeviceForKey(devKey);
// if(dev != null)
// {
// var type = dev.GetType().GetType();
// var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance);
// var sb = new StringBuilder();
// sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length));
// foreach (var m in methods)
// {
// sb.Append(string.Format("{0}(", m.Name));
// var pars = m.GetParameters();
// foreach (var p in pars)
// sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name));
// sb.AppendLine(")");
// }
// CrestronConsole.ConsoleCommandResponse(sb.ToString());
// }
//}
private static void ListDevices(string s)
{
Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count);
var sorted = Devices.Values.ToList();
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
private static void ListDevices(string s)
{
Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count);
var sorted = Devices.Values.ToList();
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var d in sorted)
{
var name = d is IKeyName ? (d as IKeyName).Name : "---";
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
}
}
foreach (var d in sorted)
{
var name = d is IKeyName ? (d as IKeyName).Name : "---";
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
}
}
private static void ListDeviceFeedbacks(string devKey)
{
var dev = GetDeviceForKey(devKey);
if (dev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
return;
}
var statusDev = dev as IHasFeedback;
if (statusDev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
return;
}
statusDev.DumpFeedbacksToConsole(true);
}
private static void ListDeviceFeedbacks(string devKey)
{
var dev = GetDeviceForKey(devKey);
if (dev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
return;
}
if (!(dev is IHasFeedback statusDev))
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
return;
}
statusDev.DumpFeedbacksToConsole(true);
}
//static void ListDeviceCommands(string devKey)
//static void ListDeviceCommands(string devKey)
//{
// var dev = GetDeviceForKey(devKey);
// if (dev == null)
@@ -223,134 +221,132 @@ namespace PepperDash.Essentials.Core
// Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey);
//}
private static void ListDeviceCommStatuses(string input)
{
var sb = new StringBuilder();
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
{
sb.Append(string.Format("{0}: {1}\r", dev,
dev.CommunicationMonitor.Status));
}
CrestronConsole.ConsoleCommandResponse(sb.ToString());
}
private static void ListDeviceCommStatuses(string input)
{
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
{
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}{Environment.NewLine}");
}
}
//static void DoDeviceCommand(string command)
//static void DoDeviceCommand(string command)
//{
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
//}
public static void AddDevice(IKeyed newDev)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
return;
}
// Check for device with same key
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
////// If it exists, remove or warn??
//if (existingDevice != null)
public static void AddDevice(IKeyed newDev)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
return;
}
// Check for device with same key
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
////// If it exists, remove or warn??
//if (existingDevice != null)
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed.");
return;
}
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed.");
return;
}
if (Devices.ContainsKey(newDev.Key))
{
Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager");
return;
}
Devices.Add(newDev.Key, newDev);
//if (!(_Devices.Contains(newDev)))
// _Devices.Add(newDev);
}
finally
{
DeviceCriticalSection.Leave();
}
}
if (Devices.ContainsKey(newDev.Key))
{
Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager");
return;
}
Devices.Add(newDev.Key, newDev);
//if (!(_Devices.Contains(newDev)))
// _Devices.Add(newDev);
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information,
"Currently unable to add devices to Device Manager. Please try again");
return;
}
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information,
"All devices have been activated. Adding new devices is not allowed.");
return;
}
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information,
"Currently unable to add devices to Device Manager. Please try again");
return;
}
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information,
"All devices have been activated. Adding new devices is not allowed.");
return;
}
foreach (var dev in devicesToAdd)
{
try
{
Devices.Add(dev.Key, dev);
}
catch (ArgumentException ex)
{
Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}",
dev.Key, ex.Message, ex.StackTrace);
}
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
foreach (var dev in devicesToAdd)
{
try
{
Devices.Add(dev.Key, dev);
}
catch (ArgumentException ex)
{
Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}",
dev.Key, ex.Message, ex.StackTrace);
}
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static void RemoveDevice(IKeyed newDev)
{
try
{
public static void RemoveDevice(IKeyed newDev)
{
try
{
DeviceCriticalSection.Enter();
if (newDev == null)
return;
if (Devices.ContainsKey(newDev.Key))
Devices.Remove(newDev.Key);
//if (_Devices.Contains(newDev))
// _Devices.Remove(newDev);
else
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
}
finally
{
DeviceCriticalSection.Leave();
}
}
if (newDev == null)
return;
if (Devices.ContainsKey(newDev.Key))
Devices.Remove(newDev.Key);
//if (_Devices.Contains(newDev))
// _Devices.Remove(newDev);
else
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static IEnumerable<string> GetDeviceKeys()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Keys;
}
public static IEnumerable<string> GetDeviceKeys()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Keys;
}
public static IEnumerable<IKeyed> GetDevices()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Values;
}
public static IEnumerable<IKeyed> GetDevices()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Values;
}
public static IKeyed GetDeviceForKey(string key)
{
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (key != null && Devices.ContainsKey(key))
return Devices[key];
public static IKeyed GetDeviceForKey(string key)
{
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (key != null && Devices.ContainsKey(key))
return Devices[key];
return null;
}
return null;
}
/// <summary>
/// Console handler that simulates com port data receive
@@ -367,8 +363,7 @@ namespace PepperDash.Essentials.Core
}
//Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value);
var com = GetDeviceForKey(match.Groups[1].Value) as ComPortController;
if (com == null)
if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com))
{
CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value);
return;
@@ -423,16 +418,15 @@ namespace PepperDash.Essentials.Core
var deviceKey = args[0];
var setting = args[1];
var timeout= String.Empty;
var timeout = String.Empty;
if (args.Length >= 3)
{
timeout = args[2];
}
var device = GetDeviceForKey(deviceKey) as IStreamDebugging;
if (device == null)
if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device))
{
CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey);
return;
@@ -479,13 +473,11 @@ namespace PepperDash.Essentials.Core
{
foreach (var device in AllDevices)
{
var streamDevice = device as IStreamDebugging;
if (streamDevice != null)
if (device is IStreamDebugging streamDevice)
{
streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off);
}
}
}
}
}
}

View File

@@ -51,8 +51,18 @@ namespace PepperDash.Essentials.Core
/// The key of the device in the DeviceManager for control
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey => DeviceManager.AllDevices.
Where(d => d.Key.Contains(ParentDeviceKey) && d.Key.Contains(ItemKey)).FirstOrDefault()?.Key ?? $"{ParentDeviceKey}--{ItemKey}";
public string DeviceKey
{
get
{
if(string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey;
else
{
return DeviceManager.AllDevices.
Where(d => d.Key.Contains(ParentDeviceKey) && d.Key.Contains(ItemKey)).FirstOrDefault()?.Key ?? $"{ParentDeviceKey}--{ItemKey}";
}
}
}
/// <summary>
/// Indicates if the item is a level, mute , or both
@@ -65,8 +75,8 @@ namespace PepperDash.Essentials.Core
[Flags]
public enum eLevelControlType
{
Level = 0,
Mute = 1,
Level = 1,
Mute = 2,
LevelAndMute = Level | Mute,
}
}

View File

@@ -1,128 +1,121 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharpPro;
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
///
/// </summary>
public enum eSourceListItemType
{
Route, Off, Other, SomethingAwesomerThanThese
}
/// <summary>
///
/// </summary>
public enum eSourceListItemType
{
Route, Off, Other, SomethingAwesomerThanThese
}
/// <summary>
/// Represents an item in a source list - can be deserialized into.
/// </summary>
public class SourceListItem
{
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
/// <summary>
/// Represents an item in a source list - can be deserialized into.
/// </summary>
public class SourceListItem
{
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
/// <summary>
/// Returns the source Device for this, if it exists in DeviceManager
/// </summary>
[JsonIgnore]
public Device SourceDevice
{
get
{
if (_SourceDevice == null)
_SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device;
return _SourceDevice;
}
}
Device _SourceDevice;
/// <summary>
/// Returns the source Device for this, if it exists in DeviceManager
/// </summary>
[JsonIgnore]
public Device SourceDevice
{
get
{
if (_SourceDevice == null)
_SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device;
return _SourceDevice;
}
}
/// <summary>
/// Gets either the source's Name or this AlternateName property, if
/// defined. If source doesn't exist, returns "Missing source"
/// </summary>
[JsonProperty("preferredName")]
public string PreferredName
{
get
{
if (string.IsNullOrEmpty(Name))
{
if (SourceDevice == null)
return "---";
return SourceDevice.Name;
}
return Name;
}
}
private Device _SourceDevice;
/// <summary>
/// A name that will override the source's name on the UI
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets either the source's Name or this AlternateName property, if
/// defined. If source doesn't exist, returns "Missing source"
/// </summary>
[JsonProperty("preferredName")]
public string PreferredName
{
get
{
if (string.IsNullOrEmpty(Name))
{
if (SourceDevice == null)
return "---";
return SourceDevice.Name;
}
return Name;
}
}
/// <summary>
/// A name that will override the source's name on the UI
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Specifies and icon for the source list item
/// </summary>
[JsonProperty("icon")]
public string Icon { get; set; }
public string Icon { get; set; }
/// <summary>
/// Alternate icon
/// </summary>
[JsonProperty("altIcon")]
public string AltIcon { get; set; }
public string AltIcon { get; set; }
/// <summary>
/// Indicates if the item should be included in the source list
/// </summary>
[JsonProperty("includeInSourceList")]
public bool IncludeInSourceList { get; set; }
public bool IncludeInSourceList { get; set; }
/// <summary>
/// Used to specify the order of the items in the source list when displayed
/// </summary>
[JsonProperty("order")]
public int Order { get; set; }
public int Order { get; set; }
/// <summary>
/// The key of the device for volume control
/// </summary>
[JsonProperty("volumeControlKey")]
public string VolumeControlKey { get; set; }
public string VolumeControlKey { get; set; }
/// <summary>
/// The type of source list item
/// </summary>
[JsonProperty("type")]
[JsonConverter(typeof(StringEnumConverter))]
public eSourceListItemType Type { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public eSourceListItemType Type { get; set; }
/// <summary>
/// The list of routes to execute for this source list item
/// </summary>
[JsonProperty("routeList")]
public List<SourceRouteListItem> RouteList { get; set; }
public List<SourceRouteListItem> RouteList { get; set; }
/// <summary>
/// Indicates if this source should be disabled for sharing to the far end call participants via codec content
/// </summary>
[JsonProperty("disableCodecSharing")]
public bool DisableCodecSharing { get; set; }
public bool DisableCodecSharing { get; set; }
/// <summary>
/// Indicates if this source should be disabled for routing to a shared output
/// </summary>
[JsonProperty("disableRoutedSharing")]
public bool DisableRoutedSharing { get; set; }
public bool DisableRoutedSharing { get; set; }
[JsonProperty("destinations")]
public List<eSourceListItemDestinationTypes> Destinations { get; set; }
@@ -156,10 +149,10 @@ namespace PepperDash.Essentials.Core
[JsonProperty("disableSimpleRouting")]
public bool DisableSimpleRouting { get; set; }
public SourceListItem()
{
Icon = "Blank";
}
public SourceListItem()
{
Icon = "Blank";
}
public override string ToString()
{
@@ -167,23 +160,23 @@ namespace PepperDash.Essentials.Core
}
}
public class SourceRouteListItem
{
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
public class SourceRouteListItem
{
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
[JsonProperty("sourcePortKey")]
public string SourcePortKey { get; set; }
[JsonProperty("destinationKey")]
public string DestinationKey { get; set; }
[JsonProperty("destinationKey")]
public string DestinationKey { get; set; }
[JsonProperty("destinationPortKey")]
public string DestinationPortKey { get; set; }
[JsonProperty("type")]
public eRoutingSignalType Type { get; set; }
}
[JsonProperty("type")]
public eRoutingSignalType Type { get; set; }
}
/// <summary>
/// Defines the valid destination types for SourceListItems in a room
@@ -193,7 +186,12 @@ namespace PepperDash.Essentials.Core
defaultDisplay,
leftDisplay,
rightDisplay,
centerDisplay,
programAudio,
codecContent
codecContent,
frontLeftDisplay,
frontRightDisplay,
rearLeftDisplay,
rearRightDisplay,
}
}

View File

@@ -1,9 +1,5 @@
using System;
using PepperDash.Core;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
@@ -26,6 +22,11 @@ namespace PepperDash.Essentials.Core
{
get
{
if (IsInAutoMode)
{
return _partitionSensor.PartitionPresentFeedback.BoolValue;
}
return _partitionPresent;
}
set
@@ -73,11 +74,11 @@ namespace PepperDash.Essentials.Core
PartitionPresentFeedback.FireUpdate();
}
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
if (IsInAutoMode)
{
PartitionPresentFeedback.FireUpdate();
PartitionPresent = e.BoolValue;
}
}
@@ -87,6 +88,8 @@ namespace PepperDash.Essentials.Core
public void SetAutoMode()
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Auto Mode", this);
IsInAutoMode = true;
if (PartitionPresentFeedback != null)
{
@@ -101,11 +104,16 @@ namespace PepperDash.Essentials.Core
{
_partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange;
_partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange;
PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue;
}
PartitionPresentFeedback.FireUpdate();
}
public void SetManualMode()
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Manual Mode", this);
IsInAutoMode = false;
if (PartitionPresentFeedback != null)
{
@@ -119,7 +127,10 @@ namespace PepperDash.Essentials.Core
if (_partitionSensor != null)
{
_partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange;
PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue;
}
PartitionPresentFeedback.FireUpdate();
}

View File

@@ -1,11 +1,12 @@
using System;
using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Core.Logging;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using PepperDash.Core;
using Serilog.Events;
using Newtonsoft.Json;
using System.Threading;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{
@@ -35,7 +36,7 @@ namespace PepperDash.Essentials.Core
}
set
{
if(value == _isInAutoMode)
if (value == _isInAutoMode)
{
return;
}
@@ -49,6 +50,8 @@ namespace PepperDash.Essentials.Core
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
private Mutex _scenarioChange = new Mutex();
public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props)
: base(key)
{
@@ -93,7 +96,7 @@ namespace PepperDash.Essentials.Core
});
}
void CreateScenarios()
private void CreateScenarios()
{
foreach (var scenarioConfig in _propertiesConfig.Scenarios)
{
@@ -102,21 +105,29 @@ namespace PepperDash.Essentials.Core
}
}
void SetRooms()
private void SetRooms()
{
_rooms = new List<IEssentialsRoom>();
foreach (var roomKey in _propertiesConfig.RoomKeys)
{
var room = DeviceManager.GetDeviceForKey(roomKey) as IEssentialsRoom;
if (room != null)
var room = DeviceManager.GetDeviceForKey(roomKey);
if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom essentialsRoom)
{
_rooms.Add(room);
_rooms.Add(essentialsRoom);
}
}
var rooms = DeviceManager.AllDevices.OfType<IEssentialsRoom>().Cast<Device>();
foreach (var room in rooms)
{
room.Deactivate();
}
}
void SetupPartitionStateProviders()
private void SetupPartitionStateProviders()
{
foreach (var pConfig in _propertiesConfig.Partitions)
{
@@ -130,18 +141,18 @@ namespace PepperDash.Essentials.Core
}
}
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
StartDebounceTimer();
}
void StartDebounceTimer()
private void StartDebounceTimer()
{
// default to 500ms for manual mode
var time = 500;
// if in auto mode, debounce the scenario change
if(IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000;
if (IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000;
if (_scenarioChangeDebounceTimer == null)
{
@@ -156,7 +167,7 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Determines the current room combination scenario based on the state of the partition sensors
/// </summary>
void DetermineRoomCombinationScenario()
private void DetermineRoomCombinationScenario()
{
if (_scenarioChangeDebounceTimer != null)
{
@@ -164,14 +175,20 @@ namespace PepperDash.Essentials.Core
_scenarioChangeDebounceTimer = null;
}
this.LogInformation("Determining Combination Scenario");
var currentScenario = RoomCombinationScenarios.FirstOrDefault((s) =>
{
this.LogDebug("Checking scenario {scenarioKey}", s.Key);
// iterate the partition states
foreach (var partitionState in s.PartitionStates)
{
this.LogDebug("checking PartitionState {partitionStateKey}", partitionState.PartitionKey);
// get the partition by key
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey));
this.LogDebug("Expected State: {partitionPresent} Actual State: {partitionState}", partitionState.PartitionPresent, partition.PartitionPresentFeedback.BoolValue);
if (partition != null && partitionState.PartitionPresent != partition.PartitionPresentFeedback.BoolValue)
{
// the partition can't be found or the state doesn't match
@@ -184,10 +201,41 @@ namespace PepperDash.Essentials.Core
if (currentScenario != null)
{
CurrentScenario = currentScenario;
this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key);
ChangeScenario(currentScenario);
}
}
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
{
if (newScenario == _currentScenario)
{
return;
}
// Deactivate the old scenario first
if (_currentScenario != null)
{
Debug.LogMessage(LogEventLevel.Information, "Deactivating scenario {currentScenario}", this, _currentScenario.Name);
await _currentScenario.Deactivate();
}
_currentScenario = newScenario;
// Activate the new scenario
if (_currentScenario != null)
{
Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this);
await _currentScenario.Activate();
}
RoomCombinationScenarioChanged?.Invoke(this, new EventArgs());
}
#region IEssentialsRoomCombiner Members
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
@@ -198,33 +246,6 @@ namespace PepperDash.Essentials.Core
{
return _currentScenario;
}
private set
{
if (value != _currentScenario)
{
// Deactivate the old scenario first
if (_currentScenario != null)
{
_currentScenario.Deactivate();
}
_currentScenario = value;
// Activate the new scenario
if (_currentScenario != null)
{
_currentScenario.Activate();
Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this);
}
var handler = RoomCombinationScenarioChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
}
public BoolFeedback IsInAutoModeFeedback { get; private set; }
@@ -232,16 +253,35 @@ namespace PepperDash.Essentials.Core
public void SetAutoMode()
{
IsInAutoMode = true;
foreach (var partition in Partitions)
{
partition.SetAutoMode();
}
DetermineRoomCombinationScenario();
}
public void SetManualMode()
{
IsInAutoMode = false;
foreach (var partition in Partitions)
{
partition.SetManualMode();
}
}
public void ToggleMode()
{
IsInAutoMode = !IsInAutoMode;
if (IsInAutoMode)
{
SetManualMode();
}
else
{
SetAutoMode();
}
}
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PepperDash.Core;
@@ -87,12 +88,12 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Activates this room combination scenario
/// </summary>
void Activate();
Task Activate();
/// <summary>
/// Deactivates this room combination scenario
/// </summary>
void Deactivate();
Task Deactivate();
/// <summary>
/// The state of the partitions that would activate this scenario

View File

@@ -1,22 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using PepperDash.Core;
using Newtonsoft.Json;
using PepperDash.Core.Logging;
using Serilog.Events;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a room combination scenario
/// </summary>
public class RoomCombinationScenario: IRoomCombinationScenario, IKeyName
public class RoomCombinationScenario : IRoomCombinationScenario, IKeyName
{
private RoomCombinationScenarioConfig _config;
@@ -40,7 +34,7 @@ namespace PepperDash.Essentials.Core
get { return _isActive; }
set
{
if(value == _isActive)
if (value == _isActive)
{
return;
}
@@ -76,33 +70,43 @@ namespace PepperDash.Essentials.Core
IsActiveFeedback = new BoolFeedback(() => _isActive);
}
public void Activate()
public async Task Activate()
{
Debug.LogMessage(LogEventLevel.Debug, "Activating Scenario: '{0}' with {1} action(s) defined", Name, activationActions.Count);
this.LogInformation("Activating Scenario {name} with {activationActionCount} action(s) defined", Name, activationActions.Count);
List<Task> tasks = new List<Task>();
if (activationActions != null)
{
foreach (var action in activationActions)
{
DeviceJsonApi.DoDeviceAction(action);
this.LogInformation("Running Activation action {@action}", action);
tasks.Add(DeviceJsonApi.DoDeviceActionAsync(action));
}
}
await Task.WhenAll(tasks);
IsActive = true;
}
public void Deactivate()
public async Task Deactivate()
{
Debug.LogMessage(LogEventLevel.Debug, "Deactivating Scenario: '{0}' with {1} action(s) defined", Name, deactivationActions.Count);
this.LogInformation("Deactivating Scenario {name} with {deactivationActionCount} action(s) defined", Name, deactivationActions.Count);
List<Task> tasks = new List<Task>();
if (deactivationActions != null)
{
foreach (var action in deactivationActions)
{
DeviceJsonApi.DoDeviceAction(action);
this.LogInformation("Running deactivation action {actionDeviceKey}:{actionMethod}", action.DeviceKey, action.MethodName);
tasks.Add( DeviceJsonApi.DoDeviceActionAsync(action));
}
}
await Task.WhenAll(tasks);
IsActive = false;
}

View File

@@ -200,6 +200,9 @@ namespace PepperDash.Essentials.Room.Config
public string DestinationListKey { get; set; }
[JsonProperty("audioControlPointListKey")]
public string AudioControlPointListKey { get; set; }
[JsonProperty("cameraListKey")]
public string CameraListKey { get; set; }
[JsonProperty("defaultSourceItem")]
public string DefaultSourceItem { get; set; }

View File

@@ -133,7 +133,28 @@ namespace PepperDash.Essentials.Core
}
}
private string _cameraListKey;
public string CameraListKey
{
get
{
if (string.IsNullOrEmpty(_cameraListKey))
{
return _defaultListKey;
}
else
{
return _cameraListKey;
}
}
protected set
{
if (value != _cameraListKey)
{
_cameraListKey = value;
}
}
}
/// <summary>
/// Timer used for informing the UIs of a shutdown

View File

@@ -31,6 +31,8 @@ namespace PepperDash.Essentials.Core
string AudioControlPointListKey { get; }
string CameraListKey { get; }
SecondsCountdownTimer ShutdownPromptTimer { get; }
int ShutdownPromptSeconds { get; }
int ShutdownVacancySeconds { get; }

View File

@@ -1,7 +1,9 @@
using PepperDash.Core;
using Serilog.Events;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Debug = PepperDash.Core.Debug;
namespace PepperDash.Essentials.Core
@@ -12,20 +14,52 @@ namespace PepperDash.Essentials.Core
/// on those destinations.
/// </summary>
public static class Extensions
{
{
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
/// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// in RouteDescriptorCollection.DefaultCollection
/// </summary>
public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
{
var routeRequest = new RouteRequest {
Destination = destination,
Source = source,
SignalType = signalType
};
/// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// in RouteDescriptorCollection.DefaultCollection
/// </summary>
public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "")
{
// Remove this line before committing!!!!!
var frame = new StackFrame(1, true);
Debug.LogMessage(LogEventLevel.Information, "ReleaseAndMakeRoute Called from {method} with params {destinationKey}:{sourceKey}:{signalType}:{destinationPortKey}:{sourcePortKey}", frame.GetMethod().Name, destination.Key, source.Key, signalType.ToString(), destinationPortKey, sourcePortKey);
var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey);
var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey);
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
}
public static void RemoveRouteRequestForDestination(string destinationKey)
{
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
var result = RouteRequests.Remove(destinationKey);
var messageTemplate = result ? "Route Request for {destination} removed" : "Route Request for {destination} not found";
Debug.LogMessage(LogEventLevel.Information, messageTemplate, null, destinationKey);
}
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (source == null) throw new ArgumentNullException(nameof(source));
if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null");
if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null");
var routeRequest = new RouteRequest
{
Destination = destination,
DestinationPort = destinationPort,
Source = source,
SourcePort = sourcePort,
SignalType = signalType
};
var coolingDevice = destination as IWarmingCooling;
@@ -39,7 +73,7 @@ namespace PepperDash.Essentials.Core
RouteRequests[destination.Key] = routeRequest;
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
return;
}
@@ -53,45 +87,58 @@ namespace PepperDash.Essentials.Core
RouteRequests.Add(destination.Key, routeRequest);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
return;
}
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
{
RouteRequests.Remove(destination.Key);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
destination.ReleaseRoute();
destination.ReleaseRoute(destinationPort?.Key ?? string.Empty);
RunRouteRequest(routeRequest);
}
RunRouteRequest(routeRequest);
}
private static void RunRouteRequest(RouteRequest request)
{
if (request.Source == null)
return;
var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType);
if (newRoute == null)
var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
if (audioOrSingleRoute == null && videoRoute == null)
return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute);
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute);
if (videoRoute != null)
{
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute);
}
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
newRoute.ExecuteRoutes();
audioOrSingleRoute.ExecuteRoutes();
videoRoute?.ExecuteRoutes();
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
public static void ReleaseRoute(this IRoutingSink destination)
{
public static void ReleaseRoute(this IRoutingInputs destination)
{
ReleaseRoute(destination, string.Empty);
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
{
Debug.LogMessage(LogEventLevel.Information, "Release route for {inputPortKey}", destination, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
{
@@ -102,138 +149,191 @@ namespace PepperDash.Essentials.Core
RouteRequests.Remove(destination.Key);
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
}
}
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
}
}
/// <summary>
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
/// of an audio/video route are discovered a route descriptor is returned. If no route is
/// discovered, then null is returned
/// </summary>
public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
{
var routeDescriptor = new RouteDescriptor(source, destination, signalType);
/// <summary>
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
/// of an audio/video route are discovered a route descriptor is returned. If no route is
/// discovered, then null is returned
/// </summary>
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
{
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
{
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType);
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
{
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key);
if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort))
singleTypeRouteDescriptor = null;
if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor))
routeDescriptor = null;
foreach (var route in singleTypeRouteDescriptor.Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "Route for device: {route}", destination, route.ToString());
}
return routeDescriptor;
}
// otherwise, audioVideo needs to be handled as two steps.
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key);
return (singleTypeRouteDescriptor, null);
}
// otherwise, audioVideo needs to be handled as two steps.
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor);
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor);
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort);
if (!videoSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
if (!audioSuccess && !videoSuccess)
routeDescriptor = null;
return routeDescriptor;
}
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Video);
/// <summary>
/// The recursive part of this. Will stop on each device, search its inputs for the
/// desired source and if not found, invoke this function for the each input port
/// hoping to find the source.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable)
{
cycle++;
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort);
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key);
if (!videoSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
RoutingInputPort goodInputPort = null;
foreach (var route in audioRouteDescriptor.Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "Audio route for device: {route}", destination, route.ToString());
}
var destinationTieLines = TieLineCollection.Default.Where(t =>
t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
foreach (var route in videoRouteDescriptor.Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "Video route for device: {route}", destination, route.ToString());
}
// find a direct tie
var directTie = destinationTieLines.FirstOrDefault(
t => t.DestinationPort.ParentDevice == destination
&& t.SourcePort.ParentDevice == source);
if (directTie != null) // Found a tie directly to the source
{
goodInputPort = directTie.DestinationPort;
}
else // no direct-connect. Walk back devices.
{
Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key);
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
if (!audioSuccess && !videoSuccess)
return (null, null);
return (audioRouteDescriptor, videoRouteDescriptor);
}
/// <summary>
/// The recursive part of this. Will stop on each device, search its inputs for the
/// desired source and if not found, invoke this function for the each input port
/// hoping to find the source.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
{
cycle++;
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString());
RoutingInputPort goodInputPort = null;
IEnumerable<TieLine> destinationTieLines;
TieLine directTie = null;
if (destinationPort == null)
{
destinationTieLines = TieLineCollection.Default.Where(t =>
t.DestinationPort.ParentDevice.Key == destination.Key && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
}
else
{
destinationTieLines = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
}
// find the TieLine without a port
if (destinationPort == null && sourcePort == null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key);
}
// find a tieLine to a specific destination port without a specific source port
else if (destinationPort != null && sourcePort == null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
}
// find a tieline to a specific source port without a specific destination port
else if (destinationPort == null & sourcePort != null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
}
// find a tieline to a specific source port and destination port
else if (destinationPort != null && sourcePort != null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
}
if (directTie != null) // Found a tie directly to the source
{
goodInputPort = directTie.DestinationPort;
}
else // no direct-connect. Walk back devices.
{
Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {sourceKey}. Walking down tie lines", destination, source.Key);
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
foreach (var inputTieToTry in attachedMidpoints)
{
var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key);
foreach (var tieLine in midpointTieLines)
{
var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingInputsOutputs;
// Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
// Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(midpointDevice))
{
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key);
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {midpointDeviceKey} on {destinationKey}, this was already checked", destination, midpointDevice.Key, destination.Key);
continue;
}
var midpointOutputPort = tieLine.SourcePort;
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {midpointDeviceKey}", destination, midpointDevice.Key);
// haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable);
var upstreamRoutingSuccess = midpointDevice.GetRouteToSource(source, midpointOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable, null, sourcePort);
if (upstreamRoutingSuccess)
{
Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination);
goodInputPort = inputTieToTry.DestinationPort;
Debug.LogMessage(LogEventLevel.Verbose, "Route found on {midpointDeviceKey}", destination, midpointDevice.Key);
Debug.LogMessage(LogEventLevel.Verbose, "TieLine: SourcePort: {SourcePort} DestinationPort: {DestinationPort}", destination, tieLine.SourcePort, tieLine.DestinationPort);
goodInputPort = tieLine.DestinationPort;
break; // Stop looping the inputs in this cycle
}
}
}
}
}
if (goodInputPort == null)
{
if (goodInputPort == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
return false;
}
}
// we have a route on corresponding inputPort. *** Do the route ***
if (outputPortToUse == null)
if (destination is IRoutingSink)
{
// it's a sink device
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
@@ -244,8 +344,8 @@ namespace PepperDash.Essentials.Core
}
else // device is merely IRoutingInputOutputs
Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination);
return true;
}
}
}
}

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
/// </summary>
public interface IRoutingSource
{
}
public interface IRoutingSource : IRoutingOutputs
{
}
}

View File

@@ -14,19 +14,27 @@ namespace PepperDash.Essentials.Core
public class RouteDescriptor
{
public IRoutingInputs Destination { get; private set; }
public RoutingInputPort InputPort { get; private set; }
public IRoutingOutputs Source { get; private set; }
public eRoutingSignalType SignalType { get; private set; }
public List<RouteSwitchDescriptor> Routes { get; private set; }
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType)
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType):this(source,destination, null, signalType)
{
Destination = destination;
Source = source;
SignalType = signalType;
Routes = new List<RouteSwitchDescriptor>();
}
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
{
Destination = destination;
Source = source;
SignalType = signalType;
InputPort = inputPort;
Routes = new List<RouteSwitchDescriptor>();
}
/// <summary>
/// Executes all routes described in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
@@ -60,10 +68,12 @@ namespace PepperDash.Essentials.Core
/// </summary>
public void ReleaseRoutes()
{
foreach (var route in Routes)
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
{
if (route.SwitchingDevice is IRouting)
if (route.SwitchingDevice is IRouting switchingDevice)
{
switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType);
// Pull the route from the port. Whatever is watching the output's in use tracker is
// responsible for responding appropriately.
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);

View File

@@ -1,8 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using PepperDash.Core;
using PepperDash.Core;
using Serilog.Events;
using System.Collections.Generic;
using System.Linq;
namespace PepperDash.Essentials.Core
@@ -33,7 +32,13 @@ namespace PepperDash.Essentials.Core
/// <param name="descriptor"></param>
public void AddRouteDescriptor(RouteDescriptor descriptor)
{
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
if (descriptor == null)
{
return;
}
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)
&& RouteDescriptors.Any(t => t.Destination == descriptor.Destination && t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key))
{
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
@@ -48,18 +53,33 @@ namespace PepperDash.Essentials.Core
/// <returns>null if no RouteDescriptor for a destination exists</returns>
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination)
{
Debug.LogMessage(LogEventLevel.Debug, "Getting route descriptor", destination);
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
}
public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey)
{
Debug.LogMessage(LogEventLevel.Debug, "Getting route descriptor for {inputPortKey}", destination, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination && rd.InputPort != null && rd.InputPort.Key == inputPortKey);
}
/// <summary>
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
/// Returns null if no route with the provided destination exists.
/// </summary>
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination)
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string inputPortKey = "")
{
var descr = GetRouteDescriptorForDestination(destination);
Debug.LogMessage(LogEventLevel.Debug, "Removing route descriptor for {inputPortKey}", destination, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
var descr = string.IsNullOrEmpty(inputPortKey)
? GetRouteDescriptorForDestination(destination)
: GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey);
if (descr != null)
RouteDescriptors.Remove(descr);
Debug.LogMessage(LogEventLevel.Debug, "Found route descriptor {routeDescriptor}", destination, descr);
return descr;
}
}

View File

@@ -1,44 +1,34 @@
namespace PepperDash.Essentials.Core
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core
{
public class RouteRequest
{
public IRoutingSink Destination {get; set;}
public IRoutingOutputs Source {get; set;}
public eRoutingSignalType SignalType {get; set;}
public RoutingInputPort DestinationPort { get; set; }
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
var coolingDevice = sender as IWarmingCooling;
if(args.BoolValue == false)
{
Destination.ReleaseAndMakeRoute(Source, SignalType);
if(sender == null) return;
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
}
}
}
/*public class RouteRequest<TInputSelector, TOutputSelector>
{
public IRoutingSink<TInputSelector> Destination { get; set; }
public IRoutingOutputs<TOutputSelector> Source { get; set; }
public RoutingOutputPort SourcePort { get; set; }
public IRoutingInputs Destination { get; set; }
public IRoutingOutputs Source { get; set; }
public eRoutingSignalType SignalType { get; set; }
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
var coolingDevice = sender as IWarmingCooling;
Debug.LogMessage(LogEventLevel.Information, "Handling cooldown route request: {destination}:{destinationPort} -> {source}:{sourcePort} {type}", null, Destination.Key, DestinationPort.Key, Source.Key, SourcePort.Key, SignalType.ToString());
if (args.BoolValue == false)
if (args.BoolValue == true)
{
Destination.ReleaseAndMakeRoute(Source, SignalType);
return;
}
if (sender == null) return;
Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination.Key, Source.Key);
Destination.ReleaseAndMakeRoute(Source, SignalType, DestinationPort?.Key ?? string.Empty, SourcePort?.Key ?? string.Empty);
if (sender is IWarmingCooling coolingDevice)
{
Debug.LogMessage(LogEventLevel.Debug, "Unsubscribing from cooling feedback for {destination}", null, Destination.Key);
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
}
}
}*/
}
}

View File

@@ -239,5 +239,13 @@ namespace PepperDash.Essentials.Core
/// HdBaseTOut
/// </summary>
public const string HdBaseTOut = "hdBaseTOut";
/// <summary>
/// SdiIn
/// </summary>
public const string SdiIn = "sdiIn";
/// <summary>
/// SdiOut
/// </summary>
public const string SdiOut = "sdiOut";
}
}

View File

@@ -1,98 +1,91 @@
using System;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
public class TieLine
{
public RoutingOutputPort SourcePort { get; private set; }
public RoutingInputPort DestinationPort { get; private set; }
//public int InUseCount { get { return DestinationUsingThis.Count; } }
public class TieLine
{
public RoutingOutputPort SourcePort { get; private set; }
public RoutingInputPort DestinationPort { get; private set; }
//public int InUseCount { get { return DestinationUsingThis.Count; } }
/// <summary>
/// Gets the type of this tie line. Will either be the type of the desination port
/// or the type of OverrideType when it is set.
/// </summary>
public eRoutingSignalType Type
{
get
{
if (OverrideType.HasValue) return OverrideType.Value;
return DestinationPort.Type;
}
}
/// <summary>
/// Gets the type of this tie line. Will either be the type of the desination port
/// or the type of OverrideType when it is set.
/// </summary>
public eRoutingSignalType Type
{
get
{
if (OverrideType.HasValue) return OverrideType.Value;
return DestinationPort.Type;
}
}
/// <summary>
/// Use this to override the Type property for the destination port. For example,
/// when the tie line is type AudioVideo, and the signal flow should be limited to
/// Audio-only or Video only, changing this type will alter the signal paths
/// available to the routing algorithm without affecting the actual Type
/// of the destination port.
/// </summary>
public eRoutingSignalType? OverrideType { get; set; }
/// <summary>
/// Use this to override the Type property for the destination port. For example,
/// when the tie line is type AudioVideo, and the signal flow should be limited to
/// Audio-only or Video only, changing this type will alter the signal paths
/// available to the routing algorithm without affecting the actual Type
/// of the destination port.
/// </summary>
public eRoutingSignalType? OverrideType { get; set; }
//List<IRoutingInputs> DestinationUsingThis = new List<IRoutingInputs>();
//List<IRoutingInputs> DestinationUsingThis = new List<IRoutingInputs>();
/// <summary>
/// For tie lines that represent internal links, like from cards to the matrix in a DM.
/// This property is true if SourcePort and DestinationPort IsInternal
/// property are both true
/// </summary>
public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } }
public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } }
public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } }
public string TypeMismatchNote { get; set; }
/// <summary>
/// For tie lines that represent internal links, like from cards to the matrix in a DM.
/// This property is true if SourcePort and DestinationPort IsInternal
/// property are both true
/// </summary>
public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } }
public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } }
public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } }
public string TypeMismatchNote { get; set; }
/// <summary>
///
/// </summary>
/// <param name="sourcePort"></param>
/// <param name="destinationPort"></param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort)
{
if (sourcePort == null || destinationPort == null)
throw new ArgumentNullException("source or destination port");
SourcePort = sourcePort;
DestinationPort = destinationPort;
}
/// <summary>
///
/// </summary>
/// <param name="sourcePort"></param>
/// <param name="destinationPort"></param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort)
{
if (sourcePort == null || destinationPort == null)
throw new ArgumentNullException("source or destination port");
SourcePort = sourcePort;
DestinationPort = destinationPort;
}
/// <summary>
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
/// </summary>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) :
this(sourcePort, destinationPort)
{
OverrideType = overrideType;
}
/// <summary>
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
/// </summary>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) :
this(sourcePort, destinationPort)
{
OverrideType = overrideType;
}
/// <summary>
/// Will link up video status from supporting inputs to connected outputs
/// </summary>
public void Activate()
{
// Now does nothing
}
/// <summary>
/// Will link up video status from supporting inputs to connected outputs
/// </summary>
public void Activate()
{
// Now does nothing
}
public void Deactivate()
{
// Now does nothing
}
public void Deactivate()
{
// Now does nothing
}
public override string ToString()
{
return string.Format("Tie line: {0}:{1} --> {2}:{3}", SourcePort.ParentDevice.Key, SourcePort.Key,
DestinationPort.ParentDevice.Key, DestinationPort.Key);
}
}
public override string ToString()
{
return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key,
DestinationPort.ParentDevice.Key, DestinationPort.Key, Type.ToString());
}
}
//********************************************************************************
@@ -109,6 +102,6 @@ namespace PepperDash.Essentials.Core
}
[JsonIgnore]
static TieLineCollection _Default;
}
private static TieLineCollection _Default;
}
}

View File

@@ -1,25 +1,24 @@
using Crestron.SimplSharp.WebScripting;
using Newtonsoft.Json;
using PepperDash.Core.Web.RequestHandlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core.Web.RequestHandlers
{
public class GetTieLinesRequestHandler:WebApiBaseRequestHandler
public class GetTieLinesRequestHandler : WebApiBaseRequestHandler
{
public GetTieLinesRequestHandler() : base(true) { }
protected override void HandleGet(HttpCwsContext context)
{
var tieLineString = JsonConvert.SerializeObject(TieLineCollection.Default.Select((tl) => new {
var tieLineString = JsonConvert.SerializeObject(TieLineCollection.Default.Select((tl) => new
{
sourceKey = tl.SourcePort.ParentDevice.Key,
sourcePort = tl.SourcePort.Key,
destinationKey = tl.DestinationPort.ParentDevice.Key,
destinationPort = tl.DestinationPort.Key
destinationPort = tl.DestinationPort.Key,
type = tl.Type.ToString(),
}));
context.Response.StatusCode = 200;

View File

@@ -0,0 +1,31 @@
using Crestron.SimplSharpPro;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Describes a cisco codec device that can allow configuration of cameras
/// </summary>
public interface ICiscoCodecCameraConfig
{
void SetCameraAssignedSerialNumber(uint cameraId, string serialNumber);
void SetCameraName(uint videoConnectorId, string name);
void SetInputSourceType(uint videoConnectorId, eCiscoCodecInputSourceType sourceType);
}
public enum eCiscoCodecInputSourceType
{
PC,
camera,
document_camera,
mediaplayer,
other,
whiteboard
}
}

View File

@@ -13,7 +13,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
{
public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IRoutingOutputs, IUsageTracking
public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IUsageTracking
{
public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } }