From 9c9a643b6a57eda1e9eb40c1e765986fcda4c5d0 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 14 Jan 2026 10:22:35 -0600 Subject: [PATCH] feat: add CWS endpoint to get routing devices & tielines together --- .../Web/EssentialsWebApi.cs | 218 +++++++++--------- .../GetRoutingDevicesAndTieLinesHandler.cs | 178 ++++++++++++++ 2 files changed, 290 insertions(+), 106 deletions(-) create mode 100644 src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs index ffbe5509..c498fedf 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs @@ -10,71 +10,71 @@ using Serilog.Events; namespace PepperDash.Essentials.Core.Web { - /// - /// Represents a EssentialsWebApi - /// - public class EssentialsWebApi : EssentialsDevice - { - private readonly WebApiServer _server; + /// + /// Represents a EssentialsWebApi + /// + public class EssentialsWebApi : EssentialsDevice + { + private readonly WebApiServer _server; - /// - /// http(s)://{ipaddress}/cws/{basePath} - /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} - /// - private readonly string _defaultBasePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance - ? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber) - : "/api"; + /// + /// http(s)://{ipaddress}/cws/{basePath} + /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} + /// + private readonly string _defaultBasePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance + ? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber) + : "/api"; - private const int DebugTrace = 0; - private const int DebugInfo = 1; - private const int DebugVerbose = 2; + private const int DebugTrace = 0; + private const int DebugInfo = 1; + private const int DebugVerbose = 2; - /// - /// Gets or sets the BasePath - /// - public string BasePath { get; private set; } + /// + /// Gets or sets the BasePath + /// + public string BasePath { get; private set; } - /// - /// Tracks if CWS is registered - /// - public bool IsRegistered - { - get { return _server.IsRegistered; } - } + /// + /// Tracks if CWS is registered + /// + public bool IsRegistered + { + get { return _server.IsRegistered; } + } - /// - /// Constructor - /// - /// - /// - public EssentialsWebApi(string key, string name) - : this(key, name, null) - { - } + /// + /// Constructor + /// + /// + /// + public EssentialsWebApi(string key, string name) + : this(key, name, null) + { + } - /// - /// Constructor - /// - /// - /// - /// - public EssentialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config) - : base(key, name) - { - Key = key; + /// + /// Constructor + /// + /// + /// + /// + public EssentialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config) + : base(key, name) + { + Key = key; - if (config == null) - BasePath = _defaultBasePath; - else - BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath; + if (config == null) + BasePath = _defaultBasePath; + else + BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath; - _server = new WebApiServer(Key, Name, BasePath); + _server = new WebApiServer(Key, Name, BasePath); - SetupRoutes(); - } + SetupRoutes(); + } - private void SetupRoutes() - { + private void SetupRoutes() + { var routes = new List { new HttpCwsRoute("versions") @@ -177,6 +177,11 @@ namespace PepperDash.Essentials.Core.Web Name = "Get Routing Ports for a device", RouteHandler = new GetRoutingPortsHandler() }, + new HttpCwsRoute("routingDevicesAndTieLines") + { + Name = "Get Routing Devices and TieLines", + RouteHandler = new GetRoutingDevicesAndTieLinesHandler() + }, }; AddRoute(routes); @@ -211,78 +216,79 @@ namespace PepperDash.Essentials.Core.Web /// /// public override void Initialize() - { - AddRoute(new HttpCwsRoute("apiPaths") { + { + AddRoute(new HttpCwsRoute("apiPaths") + { Name = "GetPaths", RouteHandler = new GetRoutesHandler(_server.GetRouteCollection(), BasePath) }); // If running on an appliance if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) - { - /* + { + /* WEBSERVER [ON | OFF | TIMEOUT | MAXSESSIONSPERUSER ] */ - var response = string.Empty; - CrestronConsole.SendControlSystemCommand("webserver", ref response); - if (response.Contains("OFF")) return; + var response = string.Empty; + CrestronConsole.SendControlSystemCommand("webserver", ref response); + if (response.Contains("OFF")) return; - var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4); - Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series"); + var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4); + Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series"); - _server.Start(); + _server.Start(); - GetPaths(); + GetPaths(); - return; - } + return; + } - // Automatically start CWS when running on a server (Linux OS, Virtual Control) - Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on Virtual Control Server"); + // Automatically start CWS when running on a server (Linux OS, Virtual Control) + Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on Virtual Control Server"); - _server.Start(); + _server.Start(); - GetPaths(); - } + GetPaths(); + } - /// - /// Print the available pahts - /// - /// - /// http(s)://{ipaddress}/cws/{basePath} - /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} - /// - /// - /// GetPaths method - /// - public void GetPaths() - { - Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); + /// + /// Print the available pahts + /// + /// + /// http(s)://{ipaddress}/cws/{basePath} + /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} + /// + /// + /// GetPaths method + /// + public void GetPaths() + { + Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); - var currentIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - - var hostname = CrestronEthernetHelper.GetEthernetParameter( + var currentIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + + var hostname = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{BasePath}" : $"https://{currentIp}/cws{BasePath}"; - - Debug.LogMessage(LogEventLevel.Information, this, "Server:{path:l}", path); - var routeCollection = _server.GetRouteCollection(); - if (routeCollection == null) - { - Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null"); - return; - } - Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:"); - foreach (var route in routeCollection) - { - Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url); - } - Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); - } - } + Debug.LogMessage(LogEventLevel.Information, this, "Server:{path:l}", path); + + var routeCollection = _server.GetRouteCollection(); + if (routeCollection == null) + { + Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null"); + return; + } + Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:"); + foreach (var route in routeCollection) + { + Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url); + } + Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); + } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs new file mode 100644 index 00000000..0d65b0e3 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + /// + /// Handles HTTP requests to retrieve routing devices and tielines information + /// + public class GetRoutingDevicesAndTieLinesHandler : WebApiBaseRequestHandler + { + public GetRoutingDevicesAndTieLinesHandler() : base(true) { } + + protected override void HandleGet(HttpCwsContext context) + { + var devices = new List(); + + // Get all devices from DeviceManager + foreach (var device in DeviceManager.AllDevices) + { + var deviceInfo = new RoutingDeviceInfo + { + Key = device.Key, + Name = (device as IKeyName)?.Name ?? device.Key + }; + + // Check if device implements IRoutingInputs + if (device is IRoutingInputs inputDevice) + { + deviceInfo.HasInputs = true; + deviceInfo.InputPorts = inputDevice.InputPorts.Select(p => new PortInfo + { + Key = p.Key, + SignalType = p.Type.ToString(), + ConnectionType = p.ConnectionType.ToString(), + IsInternal = p.IsInternal + }).ToList(); + } + + // Check if device implements IRoutingOutputs + if (device is IRoutingOutputs outputDevice) + { + deviceInfo.HasOutputs = true; + deviceInfo.OutputPorts = outputDevice.OutputPorts.Select(p => new PortInfo + { + Key = p.Key, + SignalType = p.Type.ToString(), + ConnectionType = p.ConnectionType.ToString(), + IsInternal = p.IsInternal + }).ToList(); + } + + // Check if device implements IRoutingInputsOutputs + if (device is IRoutingInputsOutputs) + { + deviceInfo.HasInputsAndOutputs = true; + } + + // Only include devices that have routing capabilities + if (deviceInfo.HasInputs || deviceInfo.HasOutputs) + { + devices.Add(deviceInfo); + } + } + + // Get all tielines + var tielines = TieLineCollection.Default.Select(tl => new TieLineInfo + { + SourceDeviceKey = tl.SourcePort.ParentDevice.Key, + SourcePortKey = tl.SourcePort.Key, + DestinationDeviceKey = tl.DestinationPort.ParentDevice.Key, + DestinationPortKey = tl.DestinationPort.Key, + SignalType = tl.Type.ToString(), + IsInternal = tl.IsInternal + }).ToList(); + + var response = new RoutingSystemInfo + { + Devices = devices, + TieLines = tielines + }; + + var jsonResponse = JsonConvert.SerializeObject(response, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(jsonResponse, false); + context.Response.End(); + } + } + + /// + /// Represents the complete routing system information including devices and tielines + /// + public class RoutingSystemInfo + { + [JsonProperty("devices")] + public List Devices { get; set; } + + [JsonProperty("tieLines")] + public List TieLines { get; set; } + } + + /// + /// Represents a routing device with its ports information + /// + public class RoutingDeviceInfo + { + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("hasInputs")] + public bool HasInputs { get; set; } + + [JsonProperty("hasOutputs")] + public bool HasOutputs { get; set; } + + [JsonProperty("hasInputsAndOutputs")] + public bool HasInputsAndOutputs { get; set; } + + [JsonProperty("inputPorts", NullValueHandling = NullValueHandling.Ignore)] + public List InputPorts { get; set; } + + [JsonProperty("outputPorts", NullValueHandling = NullValueHandling.Ignore)] + public List OutputPorts { get; set; } + } + + /// + /// Represents a routing port with its properties + /// + public class PortInfo + { + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("signalType")] + public string SignalType { get; set; } + + [JsonProperty("connectionType")] + public string ConnectionType { get; set; } + + [JsonProperty("isInternal")] + public bool IsInternal { get; set; } + } + + /// + /// Represents a tieline connection between two ports + /// + public class TieLineInfo + { + [JsonProperty("sourceDeviceKey")] + public string SourceDeviceKey { get; set; } + + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } + + [JsonProperty("destinationDeviceKey")] + public string DestinationDeviceKey { get; set; } + + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } + + [JsonProperty("signalType")] + public string SignalType { get; set; } + + [JsonProperty("isInternal")] + public bool IsInternal { get; set; } + } +}