mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-07-02 10:38:16 +00:00
Merge remote-tracking branch 'origin/main' into tieline-visualisation
This commit is contained in:
commit
2fdd73498a
10 changed files with 553 additions and 188 deletions
|
|
@ -24,16 +24,6 @@ namespace PepperDash.Core
|
|||
/// </summary>
|
||||
public bool Enabled { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// A place to store reference to the original config object, if any. These values should
|
||||
/// NOT be used as properties on the device as they are all publicly-settable values.
|
||||
/// </summary>
|
||||
//public DeviceConfig Config { get; private set; }
|
||||
/// <summary>
|
||||
/// Helper method to check if Config exists
|
||||
/// </summary>
|
||||
//public bool HasConfig { get { return Config != null; } }
|
||||
|
||||
List<Action> _PreActivationActions;
|
||||
List<Action> _PostActivationActions;
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,6 @@ namespace PepperDash.Core.Web
|
|||
/// </summary>
|
||||
public bool IsRegistered { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Http request handler
|
||||
/// </summary>
|
||||
//public IHttpCwsHandler HttpRequestHandler
|
||||
//{
|
||||
// get { return _server.HttpRequestHandler; }
|
||||
|
|
@ -59,9 +56,6 @@ namespace PepperDash.Core.Web
|
|||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Received request event handler
|
||||
/// </summary>
|
||||
//public event EventHandler<HttpCwsRequestEventArgs> ReceivedRequestEvent
|
||||
//{
|
||||
// add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
|
||||
|
|
@ -117,7 +111,7 @@ namespace PepperDash.Core.Web
|
|||
{
|
||||
if (programEventType != eProgramStatusEventType.Stopping) return;
|
||||
|
||||
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
|
||||
this.LogInformation("Program stopping. stopping server");
|
||||
|
||||
Stop();
|
||||
}
|
||||
|
|
@ -131,11 +125,11 @@ namespace PepperDash.Core.Web
|
|||
// Re-enable the server if the link comes back up and the status should be connected
|
||||
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
|
||||
this.LogInformation("Ethernet link up. Server is alreedy registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
|
||||
this.LogInformation("Ethernet link up. Starting server");
|
||||
|
||||
Start();
|
||||
}
|
||||
|
|
@ -156,7 +150,7 @@ namespace PepperDash.Core.Web
|
|||
{
|
||||
if (route == null)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
|
||||
this.LogWarning("Failed to add route, route parameter is null");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -168,14 +162,11 @@ namespace PepperDash.Core.Web
|
|||
/// Removes a route from CWS
|
||||
/// </summary>
|
||||
/// <param name="route"></param>
|
||||
/// <summary>
|
||||
/// RemoveRoute method
|
||||
/// </summary>
|
||||
public void RemoveRoute(HttpCwsRoute route)
|
||||
{
|
||||
if (route == null)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
|
||||
this.LogWarning("Failed to remove route, route parameter is null");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -217,26 +208,24 @@ namespace PepperDash.Core.Web
|
|||
|
||||
if (_server == null)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Server is null, unable to start");
|
||||
this.LogWarning("Server is null, unable to start");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsRegistered)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Server has already been started");
|
||||
this.LogWarning("Server has already been started");
|
||||
return;
|
||||
}
|
||||
|
||||
IsRegistered = _server.Register();
|
||||
|
||||
Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
||||
this.LogInformation("Starting server, registration {registrationResult}", IsRegistered ? "was successful" : "failed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
|
||||
Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
|
||||
this.LogError("Start Exception Message: {message}", ex.Message);
|
||||
this.LogDebug(ex, "Start Exception StackTrace");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -255,23 +244,21 @@ namespace PepperDash.Core.Web
|
|||
|
||||
if (_server == null)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
|
||||
this.LogWarning("Server is null or has already been stopped");
|
||||
return;
|
||||
}
|
||||
|
||||
IsRegistered = _server.Unregister() == false;
|
||||
|
||||
Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
||||
this.LogInformation("Stopping server, unregistration {unregistrationResult}", IsRegistered ? "failed" : "was successful");
|
||||
|
||||
_server.Dispose();
|
||||
_server = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message);
|
||||
Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException);
|
||||
this.LogError("Server Stop Exception Message: {message}", ex.Message);
|
||||
this.LogDebug(ex, "Server Stop Exception StackTrace");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -292,14 +279,12 @@ namespace PepperDash.Core.Web
|
|||
try
|
||||
{
|
||||
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
||||
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
||||
this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
||||
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
|
||||
this.LogError("ReceivedRequestEventHandler Exception Message: {message}", ex.Message);
|
||||
this.LogDebug(ex, "ReceivedRequestEventHandler Exception StackTrace: {stackTrace}", ex.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,291 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the display mode for a webview event, with expected values of "Fullscreen", "Modal", or "Unknown".
|
||||
/// </summary>
|
||||
public enum eWebViewEventMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The display mode for the webview event is unknown or not specified. This value can be used as a default or fallback when the display mode is not provided or cannot be parsed into a known value.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// The webview event should be displayed in fullscreen mode, covering the entire screen and typically used for immersive experiences or when maximum screen real estate is needed. When a webview event with this display mode is shown, it will typically trigger the WebViewStatusChanged event with a status of "Fullscreen", and when it is cleared/closed, it will trigger the WebViewStatusChanged event with a status of "Cleared".
|
||||
/// </summary>
|
||||
Fullscreen,
|
||||
|
||||
/// <summary>
|
||||
/// The webview event should be displayed in modal mode, which typically means it will be shown as a dialog or overlay on top of the existing content, allowing the user to interact with it while still being able to see the underlying content. This display mode is often used for alerts, confirmations, or when the webview content is related to the current context but does not require full immersion. When a webview event with this display mode is shown, it will typically trigger the WebViewStatusChanged event with a status of "Modal", and when it is cleared/closed, it will trigger the WebViewStatusChanged event with a status of "Cleared".
|
||||
/// </summary>
|
||||
Modal,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the target for a webview event, with expected values of "OSD", "Controller", "PersistentWebApp", or "RoomScheduler".
|
||||
/// </summary>
|
||||
|
||||
public enum eWebViewTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// The target for the webview event is unknown or not specified. This value can be used as a default or fallback when the target is not provided or cannot be parsed into a known value.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// The webview event should be displayed on the On-Screen Display (OSD).
|
||||
/// </summary>
|
||||
OSD,
|
||||
|
||||
/// <summary>
|
||||
/// The webview event should be displayed on the controller.
|
||||
/// </summary>
|
||||
Controller,
|
||||
|
||||
/// <summary>
|
||||
/// The webview event should be displayed on the persistent web application.
|
||||
/// </summary>
|
||||
PersistentWebApp,
|
||||
|
||||
/// <summary>
|
||||
/// The webview event should be displayed on the room scheduler.
|
||||
/// </summary>
|
||||
RoomScheduler
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the reason for an error in a webview event, which can provide additional information about what went wrong. This class is typically only used in the Status property of a WebViewEvent when the status indicates an error, and may be null otherwise.
|
||||
/// </summary>
|
||||
public class Reason
|
||||
{
|
||||
/// <summary>
|
||||
/// The reason for an error in a webview event as a string, which can provide additional information about what went wrong. This property is typically only populated in case of an error, and may be null otherwise.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the XPath of a webview event, which can provide information about where an error occurred in the webview. This class is typically only used in the Status property of a WebViewEvent when the status indicates an error, and may be null otherwise.
|
||||
/// </summary>
|
||||
public class XPath
|
||||
{
|
||||
/// <summary>
|
||||
/// The XPath of a webview event as a string, which can provide information about where an error occurred in the webview. This property is typically only populated in case of an error, and may be null otherwise.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a base class for properties that have a string value and trigger an action when the value changes. This class can be used as a base for properties like DisplayMode and Target in the WebViewEvent, which have string values that can be set directly or parsed into enums for easier handling of expected values. The ValueChangedAction can be set to trigger any desired behavior when the value changes, such as updating the UI or triggering other events.
|
||||
/// </summary>
|
||||
public abstract class ValueProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when Value is set
|
||||
/// </summary>
|
||||
public Action ValueChangedAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the ValueChangedAction if it is set. This method should be called whenever the Value property is set to ensure that any desired behavior associated with a change in value is executed.
|
||||
/// </summary>
|
||||
protected void OnValueChanged()
|
||||
{
|
||||
var a = ValueChangedAction;
|
||||
if (a != null)
|
||||
a();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a webview event, which can include information about the status of the webview, the display parameters for the webview, and any error information if applicable. This class can be used to represent both show and clear events for a webview, with the Status property indicating the current status of the webview (e.g., "Fullscreen", "Modal", "Cleared", "Error", or "Unknown"), the Display property providing details about how the webview is being displayed (e.g., mode, URL, target, title), and the Cleared property providing details about a cleared/closed webview event (e.g., target and ID). The Id property can be used to correlate show and clear events for the same webview instance.
|
||||
/// </summary>
|
||||
public class WebViewEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier for the webview event, which can be used to correlate show and clear events for the same webview instance. This property is typically included in both show and clear events for a webview, allowing you to track the lifecycle of a specific webview instance from when it is shown to when it is cleared/closed. The Id can be any string value, but it should be unique for each webview instance to ensure proper correlation between show and clear events.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The status of the webview event, which can indicate the current state of the webview (e.g., "Fullscreen", "Modal", "Cleared", "Error", or "Unknown") as well as any error information if applicable (XPath and Reason). The Value property can be used to get or set the current status of the webview, while the XPath and Reason properties can provide additional information in case of an error. The StatusString property can be used to get or set the raw status string from the event, but it is recommended to use the Value property for easier handling of expected values. Setting the Value property will trigger the ValueChangedAction if it is set, allowing you to respond to changes in the webview status as needed.
|
||||
/// </summary>
|
||||
[JsonProperty("status")]
|
||||
public Status Status { get; set; } // /Event/UserInterface/WebView/Status
|
||||
|
||||
/// <summary>
|
||||
/// The display parameters for the webview event, which can include the display mode (e.g., "Fullscreen", "Modal", or "Unknown"), the URL to display in the webview, the target for the webview (e.g., "OSD", "Controller", "PersistentWebApp", or "RoomScheduler"), and the title to display on the webview. This property is typically included in show events for a webview, providing details about how the webview is being displayed. When a webview event with these display parameters is shown, it will typically trigger the WebViewStatusChanged event with a status of "Fullscreen" or "Modal" depending on the specified display mode, and when it is cleared/closed, it will trigger the WebViewStatusChanged event with a status of "Cleared".
|
||||
/// </summary>
|
||||
[JsonProperty("display")]
|
||||
public WebViewDisplay Display { get; set; } // /Event/UserInterface/WebView/Display
|
||||
|
||||
/// <summary>
|
||||
/// The details for a cleared/closed webview event, which can include the target for the webview that was cleared (e.g., "OSD", "Controller", "PersistentWebApp", or "RoomScheduler") and the unique identifier for the webview event that was cleared. This property is typically included in clear events for a webview, providing details about which webview instance was cleared/closed. When a webview event with this property is cleared/closed, it will typically trigger the WebViewStatusChanged event with a status of "Cleared".
|
||||
/// </summary>
|
||||
[JsonProperty("cleared")]
|
||||
public WebViewClear Cleared { get; set; } // /Event/UserInterface/WebView/Cleared
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the display parameters for a webview event, which can include the display mode (e.g., "Fullscreen", "Modal", or "Unknown"), the URL to display in the webview, the target for the webview (e.g., "OSD", "Controller", "PersistentWebApp", or "RoomScheduler"), and the title to display on the webview. This class is typically used in the Display property of a WebViewEvent to provide details about how the webview is being displayed when a show event occurs. When a webview event with these display parameters is shown, it will typically trigger the WebViewStatusChanged event with a status of "Fullscreen" or "Modal" depending on the specified display mode, and when it is cleared/closed, it will trigger the WebViewStatusChanged event with a status of "Cleared".
|
||||
/// </summary>
|
||||
public class WebViewDisplay
|
||||
{
|
||||
/// <summary>
|
||||
/// The display mode for the webview event. Expected values are "Fullscreen", "Modal", or "Unknown".
|
||||
/// </summary>
|
||||
[JsonProperty("mode")]
|
||||
public DisplayMode Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL to display in the webview.
|
||||
/// </summary>
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target for the webview. Expected values are "OSD", "Controller", "PersistentWebApp", or "RoomScheduler".
|
||||
/// </summary>
|
||||
[JsonProperty("target")]
|
||||
public Target Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title to display on the webview.
|
||||
/// </summary>
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier for the webview event, used to correlate show and clear events for the same webview instance.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the data for a webview cleared event, which indicates that a webview with the specified ID and target has been cleared/closed.
|
||||
/// </summary>
|
||||
public class WebViewClear
|
||||
{
|
||||
/// <summary>
|
||||
/// The target for the webview that was cleared. Expected values are "OSD", "Controller", "PersistentWebApp", or "RoomScheduler".
|
||||
/// </summary>
|
||||
[JsonProperty("target")]
|
||||
public Target Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier for the webview event that was cleared, used to correlate show and clear events for the same webview instance.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL that was displayed in the webview that was cleared.
|
||||
/// </summary>
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the display mode for a webview event, with a string value and a corresponding enum property for easier handling of expected values.
|
||||
/// </summary>
|
||||
public class DisplayMode : ValueProperty
|
||||
{
|
||||
private string _value;
|
||||
|
||||
/// <summary>
|
||||
/// The id of the webview event.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The string value for the display mode, which can be set directly or parsed into the WebViewEventMode enum using the WebViewEventMode property. Setting this property will also trigger the ValueChangedAction if it is set.
|
||||
/// </summary>
|
||||
public string Value { get { return _value; } set { _value = value; OnValueChanged(); } }
|
||||
|
||||
/// <summary>
|
||||
/// The display mode for the webview event as an enum, which can be used for easier handling of expected values. Expected values are Fullscreen, Modal, or Unknown.
|
||||
/// </summary>
|
||||
public eWebViewEventMode WebViewEventMode
|
||||
{
|
||||
get
|
||||
{
|
||||
eWebViewEventMode mode;
|
||||
System.Enum.TryParse(Value, true, out mode);
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the target for a webview event, with a string value and a corresponding enum property for easier handling of expected values. Setting the Value property will also trigger the ValueChangedAction if it is set.
|
||||
/// </summary>
|
||||
public class Target : ValueProperty
|
||||
{
|
||||
private string _value;
|
||||
|
||||
/// <summary>
|
||||
/// The id of the webview event.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The string value for the target, which can be set directly or parsed into the eWebViewTarget enum using the WebViewTarget property. Expected values are "OSD", "Controller", "PersistentWebApp", or "RoomScheduler". Setting this property will also trigger the ValueChangedAction if it is set.
|
||||
/// </summary>
|
||||
public string Value { get { return _value; } set { _value = value; OnValueChanged(); } }
|
||||
|
||||
/// <summary>
|
||||
/// The target for the webview event as an enum, which can be used for easier handling of expected values. Expected values are OSD, Controller, PersistentWebApp, or RoomScheduler.
|
||||
/// </summary>
|
||||
public eWebViewTarget WebViewTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
eWebViewTarget target;
|
||||
System.Enum.TryParse(Value, true, out target);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the status of a webview event, which can include error information (XPath and Reason) as well as the current status of the webview. The Value property can be used to get or set the current status of the webview, while the XPath and Reason properties can provide additional information in case of an error. The StatusString property can be used to get or set the raw status string from the event.
|
||||
/// </summary>
|
||||
public class Status
|
||||
{
|
||||
/// <summary>
|
||||
/// The XPath of the webview event, which can provide information about where an error occurred in the webview. This property is typically only populated in case of an error, and may be null otherwise.
|
||||
/// </summary>
|
||||
[JsonProperty("XPath", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public XPath XPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The reason for an error in the webview event, which can provide additional information about what went wrong. This property is typically only populated in case of an error, and may be null otherwise.
|
||||
/// </summary>
|
||||
[JsonProperty("Reason", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Reason Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw status string from the webview event, which can provide information about the current status of the webview. This property can be used to get or set the status directly, but it is recommended to use the Value property for easier handling of expected values. Setting this property will not trigger any actions, while setting the Value property will trigger the ValueChangedAction if it is set.
|
||||
/// </summary>
|
||||
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string StatusString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of the webview as a string, which can be set directly or parsed into a WebViewEventMode enum using the WebViewEventMode property. Expected values are "Fullscreen", "Modal", "Cleared", "Error", or "Unknown". Setting this property will trigger the ValueChangedAction if it is set.
|
||||
/// </summary>
|
||||
[JsonProperty("Value", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasWebView
|
||||
/// </summary>
|
||||
|
|
@ -34,6 +316,7 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|||
/// Event raised when the webview status changes
|
||||
/// </summary>
|
||||
event EventHandler<WebViewStatusChangedEventArgs> WebViewStatusChanged;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -75,6 +358,11 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|||
/// </summary>
|
||||
public string Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the WebViewEvent associated with the status change, which can provide additional information about the webview event that triggered the status change, such as display parameters or error information. This property allows you to include the full WebViewEvent in the event args, giving you access to all relevant details about the webview event when handling the WebViewStatusChanged event.
|
||||
/// </summary>
|
||||
public WebViewEvent WebView { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for WebViewStatusChangedEventArgs
|
||||
/// </summary>
|
||||
|
|
@ -83,5 +371,16 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for WebViewStatusChangedEventArgs with WebViewEvent parameter, which can provide additional information about the webview event that triggered the status change, such as display parameters or error information. This constructor allows you to include the full WebViewEvent in the event args, giving you access to all relevant details about the webview event when handling the WebViewStatusChanged event.
|
||||
/// </summary>
|
||||
/// <param name="status">the new status of the webview</param>
|
||||
/// <param name="webview">the WebViewEvent associated with the status change</param>
|
||||
public WebViewStatusChangedEventArgs(string status, WebViewEvent webview)
|
||||
{
|
||||
Status = status;
|
||||
WebView = webview;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharpPro.Keypads;
|
||||
using PepperDash.Essentials.Core.Queues;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using Serilog.Events;
|
||||
using Debug = PepperDash.Core.Debug;
|
||||
|
||||
|
||||
|
|
@ -56,8 +55,9 @@ namespace PepperDash.Essentials.Core
|
|||
/// <summary>
|
||||
/// Cache of failed route attempts to avoid re-checking impossible paths.
|
||||
/// Format: "sourceKey|destKey|signalType"
|
||||
/// Uses ConcurrentDictionary as a thread-safe set (byte value is unused).
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> _impossibleRoutes = new HashSet<string>();
|
||||
private static readonly ConcurrentDictionary<string, byte> _impossibleRoutes = new ConcurrentDictionary<string, byte>();
|
||||
|
||||
/// <summary>
|
||||
/// Indexes all TieLines by source and destination device keys for faster lookups.
|
||||
|
|
@ -67,7 +67,7 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Indexing TieLines for faster route discovery");
|
||||
Debug.LogInformation("Indexing TieLines for faster route discovery");
|
||||
|
||||
_tieLinesByDestination = TieLineCollection.Default
|
||||
.GroupBy(t => t.DestinationPort.ParentDevice.Key)
|
||||
|
|
@ -77,8 +77,8 @@ namespace PepperDash.Essentials.Core
|
|||
.GroupBy(t => t.SourcePort.ParentDevice.Key)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "TieLine indexing complete. {0} destination keys, {1} source keys",
|
||||
null, _tieLinesByDestination.Count, _tieLinesBySource.Count);
|
||||
Debug.LogInformation("TieLine indexing complete. {0} destination keys, {1} source keys",
|
||||
_tieLinesByDestination.Count, _tieLinesBySource.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -126,11 +126,13 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
/// <param name="sourceKey">Source device key</param>
|
||||
/// <param name="destKey">Destination device key</param>
|
||||
/// <param name="sourcePortKey">Source port key</param>
|
||||
/// <param name="destinationPortKey">Destination port key</param>
|
||||
/// <param name="type">Signal type</param>
|
||||
/// <returns>Cache key string</returns>
|
||||
private static string GetRouteKey(string sourceKey, string destKey, eRoutingSignalType type)
|
||||
private static string GetRouteKey(string sourceKey, string destKey, string sourcePortKey, string destinationPortKey, eRoutingSignalType type)
|
||||
{
|
||||
return string.Format("{0}|{1}|{2}", sourceKey, destKey, type);
|
||||
return $"{sourceKey}|{destKey}|{sourcePortKey}|{destinationPortKey}|{type}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -139,7 +141,7 @@ namespace PepperDash.Essentials.Core
|
|||
public static void ClearImpossibleRoutesCache()
|
||||
{
|
||||
_impossibleRoutes.Clear();
|
||||
Debug.LogMessage(LogEventLevel.Information, "Impossible routes cache cleared");
|
||||
Debug.LogInformation("Impossible routes cache cleared");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -151,7 +153,7 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
// 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);
|
||||
Debug.LogInformation("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);
|
||||
|
|
@ -209,13 +211,13 @@ namespace PepperDash.Essentials.Core
|
|||
/// <param name="destinationKey">destination device key</param>
|
||||
public static void RemoveRouteRequestForDestination(string destinationKey)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
|
||||
Debug.LogInformation("Removing route request for {destination}", 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);
|
||||
Debug.LogInformation(messageTemplate, destinationKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -231,8 +233,8 @@ namespace PepperDash.Essentials.Core
|
|||
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
|
||||
!(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio)))
|
||||
{
|
||||
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);
|
||||
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, signalType);
|
||||
Debug.LogDebug(destination, "Attempting to build source route from {sourceKey} of type {type}", source.Key, signalType);
|
||||
|
||||
if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort))
|
||||
singleTypeRouteDescriptor = null;
|
||||
|
|
@ -240,46 +242,46 @@ namespace PepperDash.Essentials.Core
|
|||
var routes = singleTypeRouteDescriptor?.Routes ?? new List<RouteSwitchDescriptor>();
|
||||
foreach (var route in routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Route for device: {route}", destination, route.ToString());
|
||||
Debug.LogVerbose(destination, "Route for device: {route}", route.ToString());
|
||||
}
|
||||
|
||||
return (singleTypeRouteDescriptor, null);
|
||||
}
|
||||
// otherwise, audioVideo needs to be handled as two steps.
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination, source.Key, signalType);
|
||||
Debug.LogDebug(destination, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", source.Key, signalType);
|
||||
|
||||
RouteDescriptor audioRouteDescriptor;
|
||||
|
||||
if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio))
|
||||
{
|
||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio);
|
||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.SecondaryAudio);
|
||||
}
|
||||
else
|
||||
{
|
||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
|
||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Audio);
|
||||
}
|
||||
|
||||
var audioSuccess = destination.GetRouteToSource(source, null, null, signalType.HasFlag(eRoutingSignalType.SecondaryAudio) ? eRoutingSignalType.SecondaryAudio : eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort);
|
||||
|
||||
if (!audioSuccess)
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
|
||||
Debug.LogDebug(destination, "Cannot find audio route to {0}", source.Key);
|
||||
|
||||
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Video);
|
||||
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Video);
|
||||
|
||||
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort);
|
||||
|
||||
if (!videoSuccess)
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
|
||||
Debug.LogDebug(destination, "Cannot find video route to {0}", source.Key);
|
||||
|
||||
foreach (var route in audioRouteDescriptor.Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Audio route for device: {route}", destination, route.ToString());
|
||||
Debug.LogVerbose(destination, "Audio route for device: {route}", route.ToString());
|
||||
}
|
||||
|
||||
foreach (var route in videoRouteDescriptor.Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Video route for device: {route}", destination, route.ToString());
|
||||
Debug.LogVerbose(destination, "Video route for device: {route}", route.ToString());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -304,8 +306,8 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
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");
|
||||
if (destinationPort == null) Debug.LogDebug("Destination port is null");
|
||||
if (sourcePort == null) Debug.LogDebug("Source port is null");
|
||||
|
||||
var routeRequest = new RouteRequest
|
||||
{
|
||||
|
|
@ -327,7 +329,7 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
RouteRequests[destination.Key] = routeRequest;
|
||||
|
||||
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);
|
||||
Debug.LogInformation("Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", destination.Key, routeRequest.Source.Key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -339,7 +341,7 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
RouteRequests.Add(destination.Key, routeRequest);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||
Debug.LogInformation("Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", destination.Key, routeRequest.Source.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +353,7 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
RouteRequests.Remove(destination.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);
|
||||
Debug.LogInformation("Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", destination.Key, routeRequest.Source.Key);
|
||||
}
|
||||
|
||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false));
|
||||
|
|
@ -467,7 +469,8 @@ namespace PepperDash.Essentials.Core
|
|||
audioOrSingleRoute = audioCollection.Descriptors.FirstOrDefault(d =>
|
||||
d.Source.Key == request.Source.Key &&
|
||||
d.Destination.Key == request.Destination.Key &&
|
||||
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
|
||||
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) &&
|
||||
(request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key));
|
||||
}
|
||||
|
||||
if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection))
|
||||
|
|
@ -475,7 +478,8 @@ namespace PepperDash.Essentials.Core
|
|||
videoRoute = videoCollection.Descriptors.FirstOrDefault(d =>
|
||||
d.Source.Key == request.Source.Key &&
|
||||
d.Destination.Key == request.Destination.Key &&
|
||||
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
|
||||
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) &&
|
||||
(request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -490,14 +494,15 @@ namespace PepperDash.Essentials.Core
|
|||
audioOrSingleRoute = collection.Descriptors.FirstOrDefault(d =>
|
||||
d.Source.Key == request.Source.Key &&
|
||||
d.Destination.Key == request.Destination.Key &&
|
||||
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
|
||||
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) &&
|
||||
(request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key));
|
||||
}
|
||||
}
|
||||
|
||||
// If no pre-loaded route found, build it dynamically
|
||||
if (audioOrSingleRoute == null && videoRoute == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "No pre-loaded route found, building dynamically", request.Destination);
|
||||
Debug.LogDebug(request.Destination, "No pre-loaded route found, building dynamically");
|
||||
(audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
|
||||
}
|
||||
|
||||
|
|
@ -511,14 +516,15 @@ namespace PepperDash.Essentials.Core
|
|||
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute);
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
|
||||
Debug.LogVerbose(request.Destination, "Executing full route");
|
||||
|
||||
audioOrSingleRoute.ExecuteRoutes();
|
||||
videoRoute?.ExecuteRoutes();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
|
||||
Debug.LogError("Exception Running Route Request {request}: {exception}", request, ex.Message);
|
||||
Debug.LogDebug(ex, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,7 +538,7 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
Debug.LogInformation(destination, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
|
||||
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
|
||||
{
|
||||
|
|
@ -546,13 +552,14 @@ namespace PepperDash.Essentials.Core
|
|||
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey);
|
||||
if (current != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
||||
Debug.LogInformation(destination, "Releasing current route: {0}", current.Source.Key);
|
||||
current.ReleaseRoutes(clearRoute);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
Debug.LogError("Exception releasing route for '{destination}':'{inputPortKey}': {exception}", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey, ex.Message);
|
||||
Debug.LogDebug(ex, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -578,14 +585,14 @@ namespace PepperDash.Essentials.Core
|
|||
cycle++;
|
||||
|
||||
// Check if this route has already been determined to be impossible
|
||||
var routeKey = GetRouteKey(source.Key, destination.Key, signalType);
|
||||
if (_impossibleRoutes.Contains(routeKey))
|
||||
var routeKey = GetRouteKey(source.Key, destination.Key, sourcePort?.Key ?? "auto", destinationPort?.Key ?? "auto", signalType);
|
||||
if (_impossibleRoutes.ContainsKey(routeKey))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Route {0} is cached as impossible, skipping", null, routeKey);
|
||||
Debug.LogVerbose("Route {0} is cached as impossible, skipping", routeKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
Debug.LogVerbose("GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString());
|
||||
|
||||
RoutingInputPort goodInputPort = null;
|
||||
|
||||
|
|
@ -633,7 +640,7 @@ namespace PepperDash.Essentials.Core
|
|||
}
|
||||
else // no direct-connect. Walk back devices.
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {sourceKey}. Walking down tie lines", destination, source.Key);
|
||||
Debug.LogVerbose(destination, "is not directly connected to {sourceKey}. Walking down tie lines", source.Key);
|
||||
|
||||
// No direct tie? Run back out on the inputs' attached devices...
|
||||
// Only the ones that are routing devices
|
||||
|
|
@ -651,13 +658,13 @@ namespace PepperDash.Essentials.Core
|
|||
// Check if this previous device has already been walked
|
||||
if (alreadyCheckedDevices.Contains(midpointDevice))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {midpointDeviceKey} on {destinationKey}, this was already checked", destination, midpointDevice.Key, destination.Key);
|
||||
Debug.LogVerbose(destination, "Skipping input {midpointDeviceKey} on {destinationKey}, this was already checked", midpointDevice.Key, destination.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
var midpointOutputPort = tieLine.SourcePort;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {midpointDeviceKey}", destination, midpointDevice.Key);
|
||||
Debug.LogVerbose(destination, "Trying to find route on {midpointDeviceKey}", midpointDevice.Key);
|
||||
|
||||
// haven't seen this device yet. Do it. Pass the output port to the next
|
||||
// level to enable switching on success
|
||||
|
|
@ -666,9 +673,9 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
if (upstreamRoutingSuccess)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination);
|
||||
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);
|
||||
Debug.LogVerbose(destination, "Upstream device route found");
|
||||
Debug.LogVerbose(destination, "Route found on {midpointDeviceKey}", midpointDevice.Key);
|
||||
Debug.LogVerbose(destination, "TieLine: SourcePort: {SourcePort} DestinationPort: {DestinationPort}", tieLine.SourcePort, tieLine.DestinationPort);
|
||||
goodInputPort = tieLine.DestinationPort;
|
||||
break; // Stop looping the inputs in this cycle
|
||||
}
|
||||
|
|
@ -678,10 +685,10 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
if (goodInputPort == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
|
||||
Debug.LogVerbose(destination, "No route found to {0}", source.Key);
|
||||
|
||||
// Cache this as an impossible route
|
||||
_impossibleRoutes.Add(routeKey);
|
||||
_impossibleRoutes.TryAdd(routeKey, 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -698,7 +705,7 @@ namespace PepperDash.Essentials.Core
|
|||
routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort));
|
||||
}
|
||||
else // device is merely IRoutingInputOutputs
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination);
|
||||
Debug.LogVerbose(destination, "No routing. Passthrough device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,22 +20,27 @@ namespace PepperDash.Essentials.Core
|
|||
public IRoutingInputs Destination { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the InputPort
|
||||
/// The InputPort on the destination device for this route, if applicable. May be null if the route is not for a specific input port.
|
||||
/// </summary>
|
||||
public RoutingInputPort InputPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Source
|
||||
/// Gets the source device (sink or midpoint) for the route.
|
||||
/// </summary>
|
||||
public IRoutingOutputs Source { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SignalType
|
||||
/// Gets the OutputPort on the source device for this route, if applicable. May be null if the route is not for a specific output port.
|
||||
/// </summary>
|
||||
public RoutingOutputPort OutputPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the signal type for this route.
|
||||
/// </summary>
|
||||
public eRoutingSignalType SignalType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Routes
|
||||
/// Gets the collection of route switch descriptors for this route.
|
||||
/// </summary>
|
||||
public List<RouteSwitchDescriptor> Routes { get; private set; }
|
||||
|
||||
|
|
@ -56,11 +61,24 @@ namespace PepperDash.Essentials.Core
|
|||
/// <param name="destination">The destination device.</param>
|
||||
/// <param name="inputPort">The destination input port (optional).</param>
|
||||
/// <param name="signalType">The signal type for this route.</param>
|
||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
|
||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType) : this(source, destination, inputPort, null, signalType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route with specific destination input and source output ports.
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="inputPort"></param>
|
||||
/// <param name="outputPort"></param>
|
||||
/// <param name="signalType"></param>
|
||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, RoutingOutputPort outputPort, eRoutingSignalType signalType)
|
||||
{
|
||||
Destination = destination;
|
||||
InputPort = inputPort;
|
||||
Source = source;
|
||||
OutputPort = outputPort;
|
||||
SignalType = signalType;
|
||||
Routes = new List<RouteSwitchDescriptor>();
|
||||
}
|
||||
|
|
@ -72,7 +90,7 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
|
||||
Debug.LogVerbose("ExecuteRoutes: {0}", route.ToString());
|
||||
|
||||
if (route.SwitchingDevice is IRoutingSinkWithSwitching sink)
|
||||
{
|
||||
|
|
@ -86,7 +104,7 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
Debug.LogVerbose("Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +130,7 @@ namespace PepperDash.Essentials.Core
|
|||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("Error executing switch: {exception}", e.Message);
|
||||
Debug.LogDebug(e, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,11 +142,11 @@ namespace PepperDash.Essentials.Core
|
|||
if (route.OutputPort.InUseTracker != null)
|
||||
{
|
||||
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
Debug.LogVerbose("Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Error, "InUseTracker is null for OutputPort {0}", null, route.OutputPort.Key);
|
||||
Debug.LogVerbose("InUseTracker is null for OutputPort {0}", route.OutputPort.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,20 +51,25 @@ namespace PepperDash.Essentials.Core
|
|||
t.Destination == descriptor.Destination &&
|
||||
t.SignalType == descriptor.SignalType &&
|
||||
((t.InputPort == null && descriptor.InputPort == null) ||
|
||||
(t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)));
|
||||
(t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)) &&
|
||||
((t.OutputPort == null && descriptor.OutputPort == null) ||
|
||||
(t.OutputPort != null && descriptor.OutputPort != null && t.OutputPort.Key == descriptor.OutputPort.Key)));
|
||||
|
||||
if (existingRoute != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, descriptor.Destination,
|
||||
"Route from {0} to {1}:{2} ({3}) already exists in this collection",
|
||||
Debug.LogInformation(descriptor.Destination,
|
||||
"Route from {source}:{outputPort} to {destination}:{inputPort} ({signalType}) already exists in this collection",
|
||||
descriptor?.Source?.Key,
|
||||
descriptor?.OutputPort?.Key ?? "auto",
|
||||
descriptor?.Destination?.Key,
|
||||
descriptor?.InputPort?.Key ?? "auto",
|
||||
descriptor?.SignalType);
|
||||
descriptor?.SignalType
|
||||
);
|
||||
return;
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Adding route descriptor: {0} -> {1}:{2} ({3})",
|
||||
Debug.LogVerbose("Adding route descriptor: {source}:{outputPort} -> {destination}:{inputPort} ({signalType})",
|
||||
descriptor?.Source?.Key,
|
||||
descriptor?.OutputPort?.Key ?? "auto",
|
||||
descriptor?.Destination?.Key,
|
||||
descriptor?.InputPort?.Key ?? "auto",
|
||||
descriptor?.SignalType);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using System.Timers;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
|
||||
|
|
@ -21,7 +21,12 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
/// <summary>
|
||||
/// Debounce timers for each sink device to prevent rapid successive updates
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, CTimer> updateTimers = new Dictionary<string, CTimer>();
|
||||
private readonly Dictionary<string, Timer> updateTimers = new Dictionary<string, Timer>();
|
||||
|
||||
/// <summary>
|
||||
/// Lock object protecting all access to <see cref="updateTimers"/>.
|
||||
/// </summary>
|
||||
private readonly object _timerLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Debounce delay in milliseconds
|
||||
|
|
@ -50,7 +55,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
midpointToSinksMap = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
var sinks = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
var midpoints = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
|
||||
|
||||
foreach (var sink in sinks)
|
||||
{
|
||||
|
|
@ -211,9 +215,46 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a sink from every midpoint set in the map and re-adds it based on its
|
||||
/// current input port. Call this whenever a sink's selected input changes so that
|
||||
/// HandleMidpointUpdate always sees an up-to-date downstream set.
|
||||
/// </summary>
|
||||
private void RebuildMapForSink(IRoutingSinkWithSwitchingWithInputPort sink)
|
||||
{
|
||||
if (midpointToSinksMap == null)
|
||||
return;
|
||||
|
||||
// Remove this sink from all existing midpoint sets
|
||||
foreach (var set in midpointToSinksMap.Values)
|
||||
set.Remove(sink.Key);
|
||||
|
||||
// Drop any midpoint entries that are now empty
|
||||
var emptyKeys = midpointToSinksMap
|
||||
.Where(kvp => kvp.Value.Count == 0)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
foreach (var k in emptyKeys)
|
||||
midpointToSinksMap.Remove(k);
|
||||
|
||||
// Re-add the sink under every midpoint that is upstream of its new input
|
||||
if (sink.CurrentInputPort == null)
|
||||
return;
|
||||
|
||||
var upstreamMidpoints = GetUpstreamMidpoints(sink);
|
||||
foreach (var midpointKey in upstreamMidpoints)
|
||||
{
|
||||
if (!midpointToSinksMap.ContainsKey(midpointKey))
|
||||
midpointToSinksMap[midpointKey] = new HashSet<string>();
|
||||
|
||||
midpointToSinksMap[midpointKey].Add(sink.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the InputChanged event from a sink device.
|
||||
/// Triggers an update for the specific sink device.
|
||||
/// Updates the midpoint-to-sink map for the new input path, then triggers
|
||||
/// a source-info update for the sink.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sink device that reported an input change.</param>
|
||||
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
|
||||
|
|
@ -224,6 +265,10 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
{
|
||||
try
|
||||
{
|
||||
// Keep the map current so HandleMidpointUpdate can find this sink
|
||||
if (sender is IRoutingSinkWithSwitchingWithInputPort sinkWithInputPort)
|
||||
RebuildMapForSink(sinkWithInputPort);
|
||||
|
||||
UpdateDestination(sender, currentInputPort);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -255,15 +300,13 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
|
||||
var key = destination.Key;
|
||||
|
||||
// Cancel existing timer for this sink
|
||||
if (updateTimers.TryGetValue(key, out var existingTimer))
|
||||
{
|
||||
existingTimer.Stop();
|
||||
existingTimer.Dispose();
|
||||
}
|
||||
// Cancel and replace any existing timer under the lock so no callback
|
||||
// can race with us while we swap the entry.
|
||||
Timer timerToDispose = null;
|
||||
Timer newTimer = null;
|
||||
|
||||
// Start new debounced timer
|
||||
updateTimers[key] = new CTimer(_ =>
|
||||
newTimer = new Timer(DEBOUNCE_MS) { AutoReset = false };
|
||||
newTimer.Elapsed += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -281,13 +324,36 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (updateTimers.ContainsKey(key))
|
||||
// Remove the entry first so a concurrent UpdateDestination call
|
||||
// cannot re-dispose whatever timer we're about to dispose.
|
||||
Timer selfTimer = null;
|
||||
lock (_timerLock)
|
||||
{
|
||||
updateTimers[key]?.Dispose();
|
||||
if (updateTimers.TryGetValue(key, out var current) && ReferenceEquals(current, newTimer))
|
||||
{
|
||||
selfTimer = current;
|
||||
updateTimers.Remove(key);
|
||||
}
|
||||
}
|
||||
}, null, DEBOUNCE_MS);
|
||||
selfTimer?.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
lock (_timerLock)
|
||||
{
|
||||
if (updateTimers.TryGetValue(key, out var existingTimer))
|
||||
timerToDispose = existingTimer;
|
||||
|
||||
updateTimers[key] = newTimer;
|
||||
}
|
||||
|
||||
// Dispose the old timer outside the lock to avoid holding the lock during disposal.
|
||||
// Dispose implicitly stops the timer, preventing its Elapsed event from firing.
|
||||
timerToDispose?.Dispose();
|
||||
|
||||
// Start after the lock is released so the Elapsed callback cannot deadlock
|
||||
// trying to acquire _timerLock while we still hold it.
|
||||
newTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -384,7 +450,8 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
||||
Debug.LogError(this, "Error getting sourceTieLine: {message}", ex.Message);
|
||||
Debug.LogDebug(ex, "StackTrace: ");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -408,6 +475,11 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
||||
}
|
||||
|
||||
if (ConfigReader.ConfigObject.GetDestinationListForKey(r.DestinationListKey)?.FirstOrDefault(d => d.Value.SinkKey == destination.Key) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
|
@ -429,10 +501,8 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
|
||||
if (sourceList == null)
|
||||
{
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
Debug.LogDebug(this,
|
||||
"No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}",
|
||||
this,
|
||||
room.SourceListKey,
|
||||
sourceTieLine
|
||||
);
|
||||
|
|
@ -461,10 +531,8 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
|
||||
if (source == null)
|
||||
{
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
Debug.LogDebug(this,
|
||||
"No source found for device {key}. Creating transient source for {destination}",
|
||||
this,
|
||||
sourceTieLine.SourcePort.ParentDevice.Key,
|
||||
destination
|
||||
);
|
||||
|
|
@ -498,10 +566,8 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
{
|
||||
if (!(tieLine.DestinationPort.ParentDevice is IRoutingInputs sink))
|
||||
{
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
Debug.LogDebug(this,
|
||||
"TieLine destination {device} is not IRoutingInputs",
|
||||
this,
|
||||
tieLine.DestinationPort.ParentDevice.Key
|
||||
);
|
||||
return null;
|
||||
|
|
@ -540,17 +606,21 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
|
||||
if (route != null && route.Routes != null && route.Routes.Count > 0)
|
||||
{
|
||||
// Found a valid route - return the source TieLine
|
||||
// Routes[0] is the hop nearest the source: its InputPort is the
|
||||
// port on the first switching device that receives the signal from
|
||||
// the source side. The TieLine whose DestinationPort matches that
|
||||
// port is the exact tie that was traversed, giving us the precise
|
||||
// source output port via SourcePort — regardless of how many output
|
||||
// ports the source device has.
|
||||
var firstHop = route.Routes[0];
|
||||
var sourceTieLine = TieLineCollection.Default.FirstOrDefault(tl =>
|
||||
tl.SourcePort.ParentDevice.Key == source.Key &&
|
||||
tl.Type.HasFlag(signalType));
|
||||
tl.DestinationPort.Key == firstHop.InputPort.Key &&
|
||||
tl.DestinationPort.ParentDevice.Key == firstHop.InputPort.ParentDevice.Key);
|
||||
|
||||
if (sourceTieLine != null)
|
||||
{
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
Debug.LogDebug(this,
|
||||
"Found route from {source} to {sink} with {count} hops",
|
||||
this,
|
||||
source.Key,
|
||||
sink.Key,
|
||||
route.Routes.Count
|
||||
|
|
@ -561,22 +631,14 @@ namespace PepperDash.Essentials.Core.Routing
|
|||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No route found to any source from {sink}",
|
||||
this,
|
||||
sink.Key
|
||||
);
|
||||
Debug.LogDebug(this, "No route found to any source from {sink}", sink.Key);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(
|
||||
ex,
|
||||
"Error getting root tieLine: {Exception}",
|
||||
this,
|
||||
ex
|
||||
);
|
||||
Debug.LogError(this, "Error getting root tieLine: {message}", ex.Message);
|
||||
Debug.LogDebug(ex, "StackTrace: ");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ namespace PepperDash.Essentials.Core.Web
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print the available pahts
|
||||
/// Print the available paths
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// http(s)://{ipaddress}/cws/{basePath}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using Crestron.SimplSharp.WebScripting;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Web.RequestHandlers;
|
||||
using PepperDash.Essentials.Core.Web;
|
||||
using PepperDash.Essentials.WebSocketServer;
|
||||
using Serilog.Events;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.WebApiHandlers
|
||||
{
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ namespace PepperDash.Essentials
|
|||
{
|
||||
try
|
||||
{
|
||||
if (args.Contains("?"))
|
||||
if (!string.IsNullOrEmpty(args) && args.Contains("?"))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: listtielines [signaltype]\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("Signal types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n");
|
||||
|
|
@ -506,7 +506,7 @@ namespace PepperDash.Essentials
|
|||
{
|
||||
try
|
||||
{
|
||||
if (args.Contains("?"))
|
||||
if (!string.IsNullOrEmpty(args) && args.Contains("?"))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: visualizeroutes [signaltype] [-s source] [-d destination]\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n");
|
||||
|
|
@ -555,7 +555,7 @@ namespace PepperDash.Essentials
|
|||
{
|
||||
try
|
||||
{
|
||||
if (args.Contains("?"))
|
||||
if (!string.IsNullOrEmpty(args) && args.Contains("?"))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: visualizecurrentroutes [signaltype] [-s source] [-d destination]\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue