Compare commits

..

46 Commits

Author SHA1 Message Date
Neil Dorin
f0f708294c Merge pull request #1329 from PepperDash/devlist-fix
fix: print devlist output using ConsoleCommandResponse
2025-09-10 09:58:13 -06:00
Andrew Welker
a00d186c62 fix: print devlist output using ConsoleCommandResponse 2025-09-10 10:45:55 -05:00
Neil Dorin
51da668dfd Merge pull request #1327 from PepperDash/cs-lan-mc-panel
fix: add config property for devices on CS LAN
2025-09-05 16:20:42 -06:00
Andrew Welker
d2b7400039 fix: INvxNetworkPortInformation inherits from IKeyed 2025-09-05 15:55:31 -05:00
Andrew Welker
2424838b7f chore: remove unused using
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-02 12:56:14 -05:00
Andrew Welker
9556edc064 fix: add config property for devices on CS LAN 2025-09-02 12:54:51 -05:00
Sumanth Rayancha
f9088691fd Merge pull request #1322 from PepperDash/release
Release
2025-08-26 10:16:18 -04:00
Andrew Welker
e40b6a8b4c Merge pull request #1320 from PepperDash/feature/extract-html-assets
feat: add html assets extraction from zip files in ControlSystem
2025-08-26 09:58:02 -04:00
Erik Meyer
c3b39a87da fix: enhance zip extraction to prevent directory traversal attacks 2025-08-22 15:15:02 -04:00
erikdred
06dc0e947e fix: check for null when getting directory
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-22 09:12:20 -04:00
aknous
147997f460 Merge pull request #1321 from PepperDash/IBasicVideoMuteWithFeedbackMessenger
Adds IBasicVideoMuteWithFeedbackMessenger
2025-08-21 14:26:10 -04:00
aknous
49abec5eea Update src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 13:23:43 -04:00
aknous
6830efe42a fix: fixes CameraBaseMessenger hold timer for PTZ controls, adds storePreset messenger 2025-08-19 18:34:16 -04:00
Neil Dorin
d013068a0c Update src/PepperDash.Essentials/ControlSystem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 14:54:11 -06:00
Neil Dorin
52916d29f4 Update src/PepperDash.Essentials/ControlSystem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 14:53:06 -06:00
Erik Meyer
19e8489166 feat: add html assets extraction from zip files in ControlSystem 2025-08-19 15:48:43 -04:00
Neil Dorin
fe33443b25 fix: Update IP handling in MobileControlTouchpanelController
Updates logic to handle setting the URL sent to the CH5 wrapper app to use the CS LAN IP based on the actual IpInformationChange event on the panel itself.

- Added `._PepperDash.Essentials.4Series.sln` to .gitignore.
- Introduced new using directives for Regex and Crestron libraries.
- Added `csIpAddress` and `csSubnetMask` fields to store device IP info.
- Modified constructor to retrieve and assign current IP and subnet mask.
- Updated `Panel.IpInformationChange` event handler for logging and URL setting.
- Created `GetUrlWithCorrectIp` method to determine the correct URL based on IP.
- Refactored `SetAppUrl` to utilize the new URL method.
- Commented out old IP determination logic in `MobileControlWebsocketServer.cs` as it was moved to the touchpanel controller.
2025-08-19 13:28:45 -06:00
aknous
8cf195b262 feat: adds IBasicVideoMuteWithFeedbackMessenger 2025-08-19 11:37:39 -04:00
Neil Dorin
40406b797d Merge pull request #1314 from PepperDash/streaming-device-properties 2025-08-15 11:52:20 -06:00
Andrew Welker
65bc408ebf fix: add StreamUrl to baseStreamingDeviceProperties 2025-08-15 12:41:21 -05:00
Neil Dorin
9d49fb8357 Merge pull request #1313 from PepperDash/temp-to-dev
Temp to dev
2025-08-15 09:34:41 -06:00
Neil Dorin
fb7797dac7 Merge pull request #1312 from PepperDash/comm-bridge-add 2025-08-15 09:02:23 -06:00
Andrew Welker
574f5f6dc9 chore: remove unused using directives in CommFactory.cs 2025-08-15 09:51:17 -05:00
Andrew Welker
e49c69a12f feat: add CommBridge class and enhance EssentialsBridgeableDevice with new constructors 2025-08-15 09:48:30 -05:00
Sumanth Rayancha
7889cba196 Merge pull request #1307 from PepperDash/feature/assets-folder
feat: add LoadAssets method to manage asset loading and configuration…
2025-08-13 12:08:14 -04:00
Nick Genovese
033aa1f3dd feat: enhance asset extraction and configuration file handling in ControlSystem 2025-08-12 12:17:58 -04:00
Neil Dorin
9b6c2d80ea Merge pull request #1308 from PepperDash/current-sources 2025-08-11 22:53:11 -06:00
Andrew Welker
a0fc731701 chore: apply copilot suggestions
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-11 17:55:02 -05:00
Andrew Welker
f2d0dca7b8 fix: make appdebug case-insensitive
when commands like `appdebug verbose` are used rather than `appdebug 2`, the command would fail because Verbose != verbose. The enum conversion is now case-insensitive.
2025-08-11 17:50:50 -05:00
Andrew Welker
ab4e85d081 fix: use different methods for extensions
The logging extension methods now use the appropriate methods from
the debug class. Previously some messages were not getting handled correctly, and it was causing issues with log statements.
2025-08-11 17:48:46 -05:00
Andrew Welker
47017da527 fix: use correct property names 2025-08-11 17:47:14 -05:00
Andrew Welker
4f0d464ba4 Merge pull request #1306 from PepperDash/fix/remove-unsued-method-in-cs
Fix/remove unsued method in cs
2025-08-11 15:30:04 -05:00
Nick Genovese
0107422507 refactor: remove unused assembly resolution logic from ControlSystem 2025-08-11 16:24:44 -04:00
Nick Genovese
1a366790e7 feat: add LoadAssets method to manage asset loading and configuration file cleanup 2025-08-11 16:23:31 -04:00
Andrew Welker
97448f4f0f Merge branch 'main' into current-sources 2025-08-06 09:01:40 -05:00
Andrew Welker
cf3ece4237 fix: use cr-lf for line endings 2025-08-06 09:00:45 -05:00
aknous
808e8042a7 Merge pull request #1305 from PepperDash/default-debug-levels
fix: set default debug levels if not found
2025-08-04 14:47:31 -04:00
Andrew Welker
0bc4388bfd Merge branch 'default-debug-levels' into current-sources 2025-08-04 13:31:26 -05:00
Andrew Welker
dbc132c0da fix: set default debug levels if not found 2025-08-04 13:31:17 -05:00
Andrew Welker
5bb0ab2626 fix: base config properties for use with streaming devices 2025-08-01 21:17:35 -05:00
Andrew Welker
27bf36c58c fix: modify how current sources dictionary gets updated 2025-08-01 09:22:31 -05:00
Andrew Welker
ce886aea63 chore: update local build version 2025-08-01 09:22:31 -05:00
Andrew Welker
ef920bf54c Merge branch 'main' into current-sources 2025-07-31 13:27:43 -05:00
Andrew Welker
a031424752 fix: add destination & source keys to routelist 2025-07-30 11:20:54 -05:00
Andrew Welker
fd1ba345aa fix: remove StringEnumConverter 2025-07-29 23:01:13 -05:00
Andrew Welker
e03874a7a9 fix: add messenger and event to ICurrentSources 2025-07-29 22:26:07 -05:00
27 changed files with 1125 additions and 388 deletions

1
.gitignore vendored
View File

@@ -395,3 +395,4 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
_site/
api/
*.DS_Store
/._PepperDash.Essentials.4Series.sln

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.4.0-local</Version>
<Version>2.15.1-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company>

View File

@@ -74,6 +74,10 @@ namespace PepperDash.Core
/// <summary>
/// Secure TCP/IP
/// </summary>
SecureTcpIp
SecureTcpIp,
/// <summary>
/// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction
/// </summary>
ComBridge
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
@@ -19,7 +20,6 @@ using Serilog.Templates;
namespace PepperDash.Core
{
/// <summary>
/// Contains debug commands for use in various situations
/// </summary>
public static class Debug
{
@@ -272,6 +272,9 @@ namespace PepperDash.Core
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
{
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, (int)LogEventLevel.Information);
return LogEventLevel.Information;
}
@@ -346,13 +349,13 @@ namespace PepperDash.Core
if (levelString.Trim() == "?")
{
CrestronConsole.ConsoleCommandResponse(
$@"Used to set the minimum level of debug messages to be printed to the console:
{_logLevels[0]} = 0
{_logLevels[1]} = 1
{_logLevels[2]} = 2
{_logLevels[3]} = 3
{_logLevels[4]} = 4
{_logLevels[5]} = 5");
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
$"{_logLevels[0]} = 0\r\n" +
$"{_logLevels[1]} = 1\r\n" +
$"{_logLevels[2]} = 2\r\n" +
$"{_logLevels[3]} = 3\r\n" +
$"{_logLevels[4]} = 4\r\n" +
$"{_logLevels[5]} = 5");
return;
}
@@ -373,7 +376,7 @@ namespace PepperDash.Core
return;
}
if (Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
if (Enum.TryParse<LogEventLevel>(levelString, true, out var levelEnum))
{
SetDebugLevel(levelEnum);
return;

View File

@@ -1,5 +1,5 @@
using Serilog.Events;
using System;
using System;
using Serilog.Events;
using Log = PepperDash.Core.Debug;
namespace PepperDash.Core.Logging
@@ -11,7 +11,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(ex, message, device, args);
Log.LogMessage(ex, message, device: device, args);
}
/// <summary>
@@ -19,7 +19,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Verbose, ex, message, device, args);
Log.LogVerbose(ex, device, message, args);
}
/// <summary>
@@ -27,7 +27,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogVerbose(this IKeyed device, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Verbose, device, message, args);
Log.LogVerbose(device, message, args);
}
/// <summary>
@@ -35,7 +35,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Debug, ex, message, device, args);
Log.LogDebug(ex, device, message, args);
}
/// <summary>
@@ -43,7 +43,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogDebug(this IKeyed device, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Debug, device, message, args);
Log.LogDebug(device, message, args);
}
/// <summary>
@@ -51,7 +51,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Information, ex, message, device, args);
Log.LogInformation(ex, device, message, args);
}
/// <summary>
@@ -59,7 +59,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogInformation(this IKeyed device, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Information, device, message, args);
Log.LogInformation(device, message, args);
}
/// <summary>
@@ -67,7 +67,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Warning, ex, message, device, args);
Log.LogWarning(ex, device, message, args);
}
/// <summary>
@@ -75,7 +75,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogWarning(this IKeyed device, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Warning, device, message, args);
Log.LogWarning(device, message, args);
}
/// <summary>
@@ -83,7 +83,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Error, ex, message, device, args);
Log.LogError(ex, device, message, args);
}
/// <summary>
@@ -91,7 +91,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogError(this IKeyed device, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Error, device, message, args);
Log.LogError(device, message, args);
}
/// <summary>
@@ -99,7 +99,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Fatal, ex, message, device, args);
Log.LogFatal(ex, device, message, args);
}
/// <summary>
@@ -107,7 +107,7 @@ namespace PepperDash.Core.Logging
/// </summary>
public static void LogFatal(this IKeyed device, string message, params object[] args)
{
Log.LogMessage(LogEventLevel.Fatal, device, message, args);
Log.LogFatal(device, message, args);
}
}
}

View File

@@ -7,6 +7,13 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary>
public interface IBridgeAdvanced
{
/// <summary>
/// Links the bridge to the API using the provided trilist, join start, join map key, and bridge.
/// </summary>
/// <param name="trilist">The trilist to link to.</param>
/// <param name="joinStart">The starting join number.</param>
/// <param name="joinMapKey">The key for the join map.</param>
/// <param name="bridge">The EISC API bridge.</param>
void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Text;
using Crestron.SimplSharp.CrestronSockets;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Implements IBasicCommunication and sends all communication through an EISC
/// </summary>
[Description("Generic communication wrapper class for any IBasicCommunication type")]
public class CommBridge : EssentialsBridgeableDevice, IBasicCommunication
{
private EiscApiAdvanced eisc;
private IBasicCommunicationJoinMap joinMap;
/// <summary>
/// Event triggered when text is received through the communication bridge.
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Event triggered when bytes are received through the communication bridge.
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Indicates whether the communication bridge is currently connected.
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommBridge"/> class.
/// </summary>
/// <param name="key">The unique key for the communication bridge.</param>
/// <param name="name">The display name for the communication bridge.</param>
public CommBridge(string key, string name)
: base(key, name)
{
}
/// <summary>
/// Sends a byte array through the communication bridge.
/// </summary>
/// <param name="bytes">The byte array to send.</param>
public void SendBytes(byte[] bytes)
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot send bytes.");
return;
}
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, Encoding.ASCII.GetString(bytes, 0, bytes.Length));
}
/// <summary>
/// Sends a text string through the communication bridge.
/// </summary>
/// <param name="text">The text string to send.</param>
public void SendText(string text)
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot send text.");
return;
}
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, text);
}
/// <summary>
/// Initiates a connection through the communication bridge.
/// </summary>
public void Connect()
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot connect.");
return;
}
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, true);
}
/// <summary>
/// Terminates the connection through the communication bridge.
/// </summary>
public void Disconnect()
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot disconnect.");
return;
}
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, false);
}
/// <inheritdoc />
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
joinMap = new IBasicCommunicationJoinMap(joinStart);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
if (!string.IsNullOrEmpty(joinMapSerialized))
joinMap = JsonConvert.DeserializeObject<IBasicCommunicationJoinMap>(joinMapSerialized);
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
this.LogWarning("Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
this.LogDebug("Linking to Trilist '{0}'", trilist.ID.ToString("X"));
eisc = bridge;
trilist.SetBoolSigAction(joinMap.Connected.JoinNumber, (b) => IsConnected = b);
trilist.SetStringSigAction(joinMap.TextReceived.JoinNumber, (s) =>
{
TextReceived?.Invoke(this, new GenericCommMethodReceiveTextArgs(s));
BytesReceived?.Invoke(this, new GenericCommMethodReceiveBytesArgs(Encoding.ASCII.GetBytes(s)));
});
}
}
}

View File

@@ -1,11 +1,9 @@
using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
@@ -38,9 +36,9 @@ namespace PepperDash.Essentials.Core
/// Returns a comm method of either com port, TCP, SSH, and puts this into the DeviceManager
/// </summary>
/// <param name="deviceConfig">The Device config object</param>
/// <summary>
/// CreateCommForDevice method
/// </summary>
/// <summary>
/// CreateCommForDevice method
/// </summary>
public static IBasicCommunication CreateCommForDevice(DeviceConfig deviceConfig)
{
EssentialsControlPropertiesConfig controlConfig = GetControlPropertiesConfig(deviceConfig);
@@ -56,35 +54,38 @@ namespace PepperDash.Essentials.Core
case eControlMethod.Com:
comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig);
break;
case eControlMethod.Cec:
comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig);
break;
case eControlMethod.ComBridge:
comm = new CommBridge(deviceConfig.Key + "-simpl", deviceConfig.Name + " Simpl");
break;
case eControlMethod.Cec:
comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig);
break;
case eControlMethod.IR:
break;
case eControlMethod.Ssh:
{
var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password);
ssh.AutoReconnect = c.AutoReconnect;
if(ssh.AutoReconnect)
ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = ssh;
break;
}
case eControlMethod.Tcpip:
{
var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize);
tcp.AutoReconnect = c.AutoReconnect;
if (tcp.AutoReconnect)
tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = tcp;
break;
}
case eControlMethod.Udp:
{
var udp = new GenericUdpServer(deviceConfig.Key + "-udp", c.Address, c.Port, c.BufferSize);
comm = udp;
break;
}
case eControlMethod.Ssh:
{
var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password);
ssh.AutoReconnect = c.AutoReconnect;
if (ssh.AutoReconnect)
ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = ssh;
break;
}
case eControlMethod.Tcpip:
{
var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize);
tcp.AutoReconnect = c.AutoReconnect;
if (tcp.AutoReconnect)
tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = tcp;
break;
}
case eControlMethod.Udp:
{
var udp = new GenericUdpServer(deviceConfig.Key + "-udp", c.Address, c.Port, c.BufferSize);
comm = udp;
break;
}
case eControlMethod.Telnet:
break;
case eControlMethod.SecureTcpIp:
@@ -98,7 +99,7 @@ namespace PepperDash.Essentials.Core
}
default:
break;
}
}
}
catch (Exception e)
{
@@ -107,15 +108,14 @@ namespace PepperDash.Essentials.Core
}
// put it in the device manager if it's the right flavor
var comDev = comm as Device;
if (comDev != null)
if (comm is Device comDev)
DeviceManager.AddDevice(comDev);
return comm;
}
/// <summary>
/// GetComPort method
/// </summary>
/// <summary>
/// GetComPort method
/// </summary>
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
{
var comPar = config.ComParams;
@@ -126,74 +126,74 @@ namespace PepperDash.Essentials.Core
return null;
}
/// <summary>
/// Gets an ICec port from a RoutingInput or RoutingOutput on a device
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
/// <summary>
/// GetCecPort method
/// </summary>
public static ICec GetCecPort(ControlPropertiesConfig config)
{
try
{
var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey);
/// <summary>
/// Gets an ICec port from a RoutingInput or RoutingOutput on a device
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
/// <summary>
/// GetCecPort method
/// </summary>
public static ICec GetCecPort(ControlPropertiesConfig config)
{
try
{
var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey);
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
? "is not valid, failed to get cec port"
: "found in device manager, attempting to get cec port");
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
? "is not valid, failed to get cec port"
: "found in device manager, attempting to get cec port");
if (dev == null)
return null;
if (dev == null)
return null;
if (String.IsNullOrEmpty(config.ControlPortName))
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
return null;
}
if (String.IsNullOrEmpty(config.ControlPortName))
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
return null;
}
var inputsOutputs = dev as IRoutingInputsOutputs;
if (inputsOutputs == null)
{
var inputsOutputs = dev as IRoutingInputsOutputs;
if (inputsOutputs == null)
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
return null;
}
var inputPort = inputsOutputs.InputPorts[config.ControlPortName];
if (inputPort != null && inputPort.Port is ICec)
if (inputPort != null && inputPort.Port is ICec)
return inputPort.Port as ICec;
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
if (outputPort != null && outputPort.Port is ICec)
return outputPort.Port as ICec;
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
}
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
if (outputPort != null && outputPort.Port is ICec)
return outputPort.Port as ICec;
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
}
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not have a CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
}
/// <summary>
/// Helper to grab the IComPorts device for this PortDeviceKey. Key "controlSystem" will
/// return the ControlSystem object from the Global class.
/// </summary>
/// <returns>IComPorts device or null if the device is not found or does not implement IComPorts</returns>
/// <summary>
/// GetIComPortsDeviceFromManagedDevice method
/// </summary>
/// <summary>
/// GetIComPortsDeviceFromManagedDevice method
/// </summary>
public static IComPorts GetIComPortsDeviceFromManagedDevice(string ComPortDevKey)
{
if ((ComPortDevKey.Equals("controlSystem", System.StringComparison.OrdinalIgnoreCase)
@@ -210,81 +210,81 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Represents a EssentialsControlPropertiesConfig
/// </summary>
public class EssentialsControlPropertiesConfig :
ControlPropertiesConfig
{
/// <summary>
/// Represents a EssentialsControlPropertiesConfig
/// </summary>
public class EssentialsControlPropertiesConfig :
ControlPropertiesConfig
{
[JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(ComSpecJsonConverter))]
public ComPort.ComPortSpec? ComParams { get; set; }
[JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(ComSpecJsonConverter))]
public ComPort.ComPortSpec? ComParams { get; set; }
[JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)]
public string CresnetId { get; set; }
[JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)]
public string CresnetId { get; set; }
/// <summary>
/// Attempts to provide uint conversion of string CresnetId
/// </summary>
[JsonIgnore]
public uint CresnetIdInt
{
get
{
try
{
return Convert.ToUInt32(CresnetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to convert Cresnet ID: {0} to hex. Error:\n{1}", CresnetId));
}
}
}
/// <summary>
/// Attempts to provide uint conversion of string CresnetId
/// </summary>
[JsonIgnore]
public uint CresnetIdInt
{
get
{
try
{
return Convert.ToUInt32(CresnetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to convert Cresnet ID: {0} to hex. Error:\n{1}", CresnetId));
}
}
}
[JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the InfinetId
/// </summary>
public string InfinetId { get; set; }
[JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the InfinetId
/// </summary>
public string InfinetId { get; set; }
/// <summary>
/// Attepmts to provide uiont conversion of string InifinetId
/// </summary>
[JsonIgnore]
public uint InfinetIdInt
{
get
{
try
{
return Convert.ToUInt32(InfinetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to conver Infinet ID: {0} to hex. Error:\n{1}", InfinetId));
}
}
}
}
/// <summary>
/// Attepmts to provide uiont conversion of string InifinetId
/// </summary>
[JsonIgnore]
public uint InfinetIdInt
{
get
{
try
{
return Convert.ToUInt32(InfinetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to conver Infinet ID: {0} to hex. Error:\n{1}", InfinetId));
}
}
}
}
/// <summary>
/// Represents a IrControlSpec
/// </summary>
public class IrControlSpec
{
/// <summary>
/// Gets or sets the PortDeviceKey
/// </summary>
public string PortDeviceKey { get; set; }
/// <summary>
/// Gets or sets the PortNumber
/// </summary>
public uint PortNumber { get; set; }
/// <summary>
/// Gets or sets the File
/// </summary>
public string File { get; set; }
}
/// <summary>
/// Represents a IrControlSpec
/// </summary>
public class IrControlSpec
{
/// <summary>
/// Gets or sets the PortDeviceKey
/// </summary>
public string PortDeviceKey { get; set; }
/// <summary>
/// Gets or sets the PortNumber
/// </summary>
public uint PortNumber { get; set; }
/// <summary>
/// Gets or sets the File
/// </summary>
public string File { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
using System;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Represents the base properties for a streaming device.
/// </summary>
public class BaseStreamingDeviceProperties
{
/// <summary>
/// The multicast video address for the streaming device.
/// </summary>
[JsonProperty("multicastVideoAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastVideoAddress { get; set; }
/// <summary>
/// The multicast audio address for the streaming device.
/// </summary>
[JsonProperty("multicastAudioAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastAudioAddress { get; set; }
/// <summary>
/// The URL for the streaming device's media stream.
/// </summary>
[JsonProperty("streamUrl", NullValueHandling = NullValueHandling.Ignore)]
public string StreamUrl { get; set; }
}
}

View File

@@ -1,6 +1,7 @@
using Crestron.SimplSharpPro.DM.Streaming;
using System;
using System.Collections.Generic;
using Crestron.SimplSharpPro.DM.Streaming;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
@@ -11,7 +12,7 @@ namespace PepperDash.Essentials.Core
/// subscribers when the port information is updated. Implementations of this interface should ensure that the <see
/// cref="PortInformationChanged"/> event is raised whenever the <see cref="NetworkPorts"/> collection
/// changes.</remarks>
public interface INvxNetworkPortInformation
public interface INvxNetworkPortInformation : IKeyed
{
/// <summary>
/// Occurs when the port information changes.

View File

@@ -202,14 +202,15 @@ namespace PepperDash.Essentials.Core
private static void ListDevices(string s)
{
Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count);
CrestronConsole.ConsoleCommandResponse($"{Devices.Count} Devices registered with Device Manager:");
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);
CrestronConsole.ConsoleCommandResponse($" [{d.Key}] {name}");
}
}
@@ -475,9 +476,9 @@ namespace PepperDash.Essentials.Core
if (String.IsNullOrEmpty(s) || s.Contains("?"))
{
CrestronConsole.ConsoleCommandResponse(
@"SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]
{deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use
timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes");
"SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]\r\n" +
" {deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use\r\n" +
" timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes");
return;
}

View File

@@ -3,16 +3,29 @@ using PepperDash.Essentials.Core.Bridges;
namespace PepperDash.Essentials.Core
{
public abstract class EssentialsBridgeableDevice:EssentialsDevice, IBridgeAdvanced
/// <summary>
/// Base class for devices that can be bridged to an EISC API.
/// </summary>
public abstract class EssentialsBridgeableDevice : EssentialsDevice, IBridgeAdvanced
{
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsBridgeableDevice"/> class with the specified key.
/// </summary>
/// <param name="key">The unique key for the device.</param>
protected EssentialsBridgeableDevice(string key) : base(key)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsBridgeableDevice"/> class with the specified key and name.
/// </summary>
/// <param name="key">The unique key for the device.</param>
/// <param name="name">The display name for the device.</param>
protected EssentialsBridgeableDevice(string key, string name) : base(key, name)
{
}
/// <inheritdoc />
public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
}
}

View File

@@ -244,6 +244,20 @@ namespace PepperDash.Essentials.Core
/// </summary>
[JsonProperty("type")]
public eRoutingSignalType Type { get; set; }
/// <summary>
/// Key for a destination list item. If BOTH SourceListItemKey AND DestinationListItemKey are defined,
/// then the direct route method should be used.
/// </summary>
[JsonProperty("destinationListItemKey", NullValueHandling = NullValueHandling.Ignore)]
public string DestinationListItemKey { get; set; }
/// <summary>
/// Key for a source list item. If BOTH SourceListItemKey AND DestinationListItemKey are defined,
/// then the direct route method should be used.
/// </summary>
[JsonProperty("sourceListItemKey", NullValueHandling = NullValueHandling.Ignore)]
public string SourceListItemKey { get; set; }
}
/// <summary>

View File

@@ -135,7 +135,7 @@ namespace PepperDash.Essentials.Core
{
if (FactoryMethods.ContainsKey(typeName))
{
Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName);
Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName);
return;
}
@@ -217,7 +217,8 @@ namespace PepperDash.Essentials.Core
}
catch (Exception ex)
{
Debug.LogError(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message);
Debug.LogError(ex, "Exception occurred while creating device {key}: {message}", dc.Key, ex.Message);
Debug.LogDebug(ex, "Exception details: {stackTrace}", ex.StackTrace);
return null;
}
}
@@ -249,9 +250,9 @@ namespace PepperDash.Essentials.Core
}
CrestronConsole.ConsoleCommandResponse(
@"Type: '{0}'
Type: '{1}'
Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine);
"Type: '{0}'\r\n" +
" Type: '{1}'\r\n" +
" Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine);
}
}

View File

@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core.Routing
{
@@ -25,5 +25,19 @@ namespace PepperDash.Essentials.Core.Routing
/// </summary>
Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; }
/// <summary>
/// Event raised when the current sources change.
/// </summary>
event EventHandler CurrentSourcesChanged;
/// <summary>
/// Sets the current source for a specific signal type.
/// This method updates the current source for the specified signal type and notifies any subscribers of the change.
/// </summary>
/// <param name="signalType">The signal type to update.</param>
/// <param name="sourceListKey">The key for the source list.</param>
/// <param name="sourceListItem">The source list item to set as the current source.</param>
void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem);
}
}

View File

@@ -22,38 +22,38 @@ namespace PepperDash.Essentials.Core.Config
/// The key of the source device.
/// </summary>
public string SourceKey { get; set; }
/// <summary>
/// The key of the source card (if applicable, e.g., in a modular chassis).
/// </summary>
/// <summary>
/// The key of the source card (if applicable, e.g., in a modular chassis).
/// </summary>
public string SourceCard { get; set; }
/// <summary>
/// The key of the source output port, used for routing configurations.
/// </summary>
/// <summary>
/// The key of the source output port, used for routing configurations.
/// </summary>
public string SourcePort { get; set; }
/// <summary>
/// Gets or sets the DestinationKey
/// </summary>
/// <summary>
/// Gets or sets the DestinationKey
/// </summary>
public string DestinationKey { get; set; }
/// <summary>
/// Gets or sets the DestinationCard
/// </summary>
/// <summary>
/// Gets or sets the DestinationCard
/// </summary>
public string DestinationCard { get; set; }
/// <summary>
/// Gets or sets the DestinationPort
/// </summary>
/// <summary>
/// Gets or sets the DestinationPort
/// </summary>
public string DestinationPort { get; set; }
/// <summary>
/// Optional override for the signal type of the tie line. If set, this overrides the destination port's type for routing calculations.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eRoutingSignalType? OverrideType { get; set; }
/// <summary>
/// Optional override for the signal type of the tie line. If set, this overrides the destination port's type for routing calculations.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eRoutingSignalType? OverrideType { get; set; }
/// <summary>
/// Returns the appropriate tie line for either a card-based device or
@@ -62,40 +62,39 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>null if config data does not match ports, cards or devices</returns>
public TieLine GetTieLine()
{
Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}",null, this);
Debug.LogInformation("Build TieLine: {config}", ToString());
// Get the source device
var sourceDev = DeviceManager.GetDeviceForKey(SourceKey) as IRoutingOutputs;
if (sourceDev == null)
if (!(DeviceManager.GetDeviceForKey(SourceKey) is IRoutingOutputs sourceDev))
{
LogError("Routable source not found");
return null;
}
// Get the destination device
var destDev = DeviceManager.GetDeviceForKey(DestinationKey) as IRoutingInputs;
if (destDev == null)
if (!(DeviceManager.GetDeviceForKey(DestinationKey) is IRoutingInputs destDev))
{
LogError("Routable destination not found");
return null;
}
//Get the source port
var sourceOutputPort = sourceDev.OutputPorts[SourcePort];
//Get the source port
var sourceOutputPort = sourceDev.OutputPorts[SourcePort];
if (sourceOutputPort == null)
if (sourceOutputPort == null)
{
LogError("Source does not contain port");
return null;
}
//Get the Destination port
var destinationInputPort = destDev.InputPorts[DestinationPort];
//Get the Destination port
var destinationInputPort = destDev.InputPorts[DestinationPort];
if (destinationInputPort == null)
{
LogError("Destination does not contain port");
return null;
}
if (destinationInputPort == null)
{
LogError("Destination does not contain port");
return null;
}
return new TieLine(sourceOutputPort, destinationInputPort, OverrideType);
}
@@ -104,9 +103,9 @@ namespace PepperDash.Essentials.Core.Config
/// Logs an error message related to creating this tie line configuration.
/// </summary>
/// <param name="msg">The specific error message.</param>
void LogError(string msg)
private void LogError(string msg)
{
Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this);
Debug.LogError("Cannot create tie line: {message}", msg);
}
/// <summary>
@@ -115,8 +114,7 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>A string describing the source and destination of the configured tie line.</returns>
public override string ToString()
{
return string.Format("{0}.{1}.{2} --> {3}.{4}.{5}", SourceKey, SourceCard, SourcePort,
DestinationKey, DestinationCard, DestinationPort);
return $"{SourceKey}.{SourceCard}.{SourcePort} --> {DestinationKey}.{DestinationCard}.{DestinationPort}";
}
}
}

View File

@@ -5,6 +5,7 @@ using Crestron.SimplSharp;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
@@ -52,9 +53,9 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
/// <summary>
/// Gets or sets the CurrentSourceInfoKey
/// </summary>
/// <summary>
/// Gets or sets the CurrentSourceInfoKey
/// </summary>
public string CurrentSourceInfoKey { get; set; }
/// <summary>
@@ -89,29 +90,32 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event EventHandler CurrentSourcesChanged;
/// <summary>
/// Gets feedback indicating whether the display is currently cooling down after being powered off.
/// </summary>
public BoolFeedback IsCoolingDownFeedback { get; protected set; }
/// <summary>
/// Gets or sets the IsWarmingUpFeedback
/// </summary>
/// <summary>
/// Gets or sets the IsWarmingUpFeedback
/// </summary>
public BoolFeedback IsWarmingUpFeedback { get; private set; }
/// <summary>
/// Gets or sets the UsageTracker
/// </summary>
/// <summary>
/// Gets or sets the UsageTracker
/// </summary>
public UsageTracking UsageTracker { get; set; }
/// <summary>
/// Gets or sets the WarmupTime
/// </summary>
/// <summary>
/// Gets or sets the WarmupTime
/// </summary>
public uint WarmupTime { get; set; }
/// <summary>
/// Gets or sets the CooldownTime
/// </summary>
/// <summary>
/// Gets or sets the CooldownTime
/// </summary>
public uint CooldownTime { get; set; }
/// <summary>
@@ -189,7 +193,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// <summary>
/// Gets the collection of feedback objects for this display device.
/// </summary>
/// <inheritdoc />
/// <inheritdoc />
public virtual FeedbackCollection<Feedback> Feedbacks
{
get
@@ -378,6 +382,53 @@ namespace PepperDash.Essentials.Devices.Common.Displays
volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]);
}
/// <inheritdoc />
public virtual void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, sourceListKey, sourceListItem);
}
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
}
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
if (CurrentSources.ContainsKey(signalType))
{
CurrentSources[signalType] = sourceListItem;
}
else
{
CurrentSources.Add(signalType, sourceListItem);
}
// Update the current source key for the specified signal type
if (CurrentSourceKeys.ContainsKey(signalType))
{
CurrentSourceKeys[signalType] = sourceListKey;
}
else
{
CurrentSourceKeys.Add(signalType, sourceListKey);
}
}
}
/// <summary>

View File

@@ -140,17 +140,19 @@ namespace PepperDash.Essentials.AppServer.Messengers
if (Camera is IHasCameraPresets presetsCamera)
{
for (int i = 1; i <= 6; i++)
AddAction("/recallPreset", (id, content) =>
{
var preset = i;
AddAction("/cameraPreset" + i, (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<int>>();
var msg = content.ToObject<MobileControlSimpleContent<int>>();
presetsCamera.PresetSelect(msg.Value);
});
presetsCamera.PresetSelect(msg.Value);
});
}
AddAction("/storePreset", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<int>>();
presetsCamera.PresetStore(msg.Value, string.Empty);
});
}
}
@@ -164,9 +166,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
return;
}
timerHandler(state.Value, cameraAction);
timerHandler(Camera.Key, cameraAction);
cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase));
}
/// <summary>

View File

@@ -0,0 +1,77 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Represents a IHasCurrentSourceInfoMessenger
/// </summary>
public class CurrentSourcesMessenger : MessengerBase
{
private readonly ICurrentSources sourceDevice;
/// <summary>
/// Initializes a new instance of the <see cref="CurrentSourcesMessenger"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="messagePath">The message path.</param>
/// <param name="device">The device.</param>
public CurrentSourcesMessenger(string key, string messagePath, ICurrentSources device) : base(key, messagePath, device as IKeyName)
{
sourceDevice = device;
}
/// <summary>
/// Registers the actions for the messenger.
/// </summary>
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) =>
{
var message = new CurrentSourcesStateMessage
{
CurrentSourceKeys = sourceDevice.CurrentSourceKeys,
CurrentSources = sourceDevice.CurrentSources
};
PostStatusMessage(message);
});
sourceDevice.CurrentSourcesChanged += (sender, e) =>
{
PostStatusMessage(JToken.FromObject(new
{
currentSourceKeys = sourceDevice.CurrentSourceKeys,
currentSources = sourceDevice.CurrentSources
}));
};
}
}
/// <summary>
/// Represents a CurrentSourcesStateMessage
/// </summary>
public class CurrentSourcesStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the CurrentSourceKey
/// </summary>
[JsonProperty("currentSourceKeys", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; set; }
/// <summary>
/// Gets or sets the CurrentSource
/// </summary>
[JsonProperty("currentSources")]
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; set; }
}
}

View File

@@ -0,0 +1,77 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using System.Collections.Generic;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Represents a IBasicVideoMuteWithFeedbackMessenger
/// </summary>
public class IBasicVideoMuteWithFeedbackMessenger : MessengerBase
{
private readonly IBasicVideoMuteWithFeedback device;
public IBasicVideoMuteWithFeedbackMessenger(string key, string messagePath, IBasicVideoMuteWithFeedback device)
: base(key, messagePath, device as IKeyName)
{
this.device = device;
}
/// <summary>
/// SendFullStatus method
/// </summary>
public void SendFullStatus()
{
var messageObj = new IBasicVideoMuteWithFeedbackMessage
{
VideoMuteState = device.VideoMuteIsOn.BoolValue
};
PostStatusMessage(messageObj);
}
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus());
AddAction("/videoMuteToggle", (id, content) =>
{
device.VideoMuteToggle();
});
AddAction("/videoMuteOn", (id, content) =>
{
device.VideoMuteOn();
});
AddAction("/videoMuteOff", (id, content) =>
{
device.VideoMuteOff();
});
device.VideoMuteIsOn.OutputChange += VideoMuteIsOnFeedback_OutputChange;
}
private void VideoMuteIsOnFeedback_OutputChange(object sender, FeedbackEventArgs args)
{
PostStatusMessage(JToken.FromObject(new
{
videoMuteState = args.BoolValue
})
);
}
}
/// <summary>
/// Represents a IBasicVideoMuteWithFeedbackMessage
/// </summary>
public class IBasicVideoMuteWithFeedbackMessage : DeviceStateMessageBase
{
[JsonProperty("videoMuteState")]
public bool VideoMuteState { get; set; }
}
}

View File

@@ -54,16 +54,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary>
public class CurrentSourceStateMessage : DeviceStateMessageBase
{
[JsonProperty("currentSourceKey", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the CurrentSourceKey
/// </summary>
[JsonProperty("currentSourceKey", NullValueHandling = NullValueHandling.Ignore)]
public string CurrentSourceKey { get; set; }
[JsonProperty("currentSource")]
/// <summary>
/// Gets or sets the CurrentSource
/// </summary>
[JsonProperty("currentSource")]
public SourceListItem CurrentSource { get; set; }
}
}

View File

@@ -1,6 +1,6 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Collections.Generic;
namespace PepperDash.Essentials
{
@@ -9,25 +9,34 @@ namespace PepperDash.Essentials
/// </summary>
public class MobileControlConfig
{
/// <summary>
/// Gets or sets the ServerUrl
/// </summary>
[JsonProperty("serverUrl")]
public string ServerUrl { get; set; }
/// <summary>
/// Gets or sets the ClientAppUrl
/// </summary>
[JsonProperty("clientAppUrl")]
public string ClientAppUrl { get; set; }
/// <summary>
/// Gets or sets the DirectServer
/// </summary>
[JsonProperty("directServer")]
public MobileControlDirectServerPropertiesConfig DirectServer { get; set; }
[JsonProperty("applicationConfig")]
/// <summary>
/// Gets or sets the ApplicationConfig
/// </summary>
[JsonProperty("applicationConfig")]
public MobileControlApplicationConfig ApplicationConfig { get; set; } = null;
[JsonProperty("enableApiServer")]
/// <summary>
/// Gets or sets the EnableApiServer
/// </summary>
[JsonProperty("enableApiServer")]
public bool EnableApiServer { get; set; } = true;
}
@@ -36,27 +45,42 @@ namespace PepperDash.Essentials
/// </summary>
public class MobileControlDirectServerPropertiesConfig
{
[JsonProperty("enableDirectServer")]
/// <summary>
/// Gets or sets the EnableDirectServer
/// </summary>
[JsonProperty("enableDirectServer")]
public bool EnableDirectServer { get; set; }
[JsonProperty("port")]
/// <summary>
/// Gets or sets the Port
/// </summary>
[JsonProperty("port")]
public int Port { get; set; }
[JsonProperty("logging")]
/// <summary>
/// Gets or sets the Logging
/// </summary>
[JsonProperty("logging")]
public MobileControlLoggingConfig Logging { get; set; }
/// <summary>
/// Gets or sets the AutomaticallyForwardPortToCSLAN
/// </summary>
[JsonProperty("automaticallyForwardPortToCSLAN")]
public bool? AutomaticallyForwardPortToCSLAN { get; set; }
/// <summary>
/// Gets or sets the CSLanUiDeviceKeys
/// </summary>
/// <remarks>
/// A list of device keys for the CS LAN UI. These devices will get the CS LAN IP address instead of the LAN IP Address
/// </remarks>
[JsonProperty("csLanUiDeviceKeys")]
public List<string> CSLanUiDeviceKeys { get; set; }
/// <summary>
/// Initializes a new instance of the MobileControlDirectServerPropertiesConfig class.
/// </summary>
public MobileControlDirectServerPropertiesConfig()
{
Logging = new MobileControlLoggingConfig();
@@ -68,26 +92,26 @@ namespace PepperDash.Essentials
/// </summary>
public class MobileControlLoggingConfig
{
[JsonProperty("enableRemoteLogging")]
/// <summary>
/// Gets or sets the EnableRemoteLogging
/// </summary>
[JsonProperty("enableRemoteLogging")]
public bool EnableRemoteLogging { get; set; }
[JsonProperty("host")]
/// <summary>
/// Gets or sets the Host
/// </summary>
[JsonProperty("host")]
public string Host { get; set; }
[JsonProperty("port")]
/// <summary>
/// Gets or sets the Port
/// </summary>
[JsonProperty("port")]
public int Port { get; set; }
}
/// <summary>
@@ -95,16 +119,16 @@ namespace PepperDash.Essentials
/// </summary>
public class MobileControlRoomBridgePropertiesConfig
{
[JsonProperty("key")]
/// <summary>
/// Gets or sets the Key
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("roomKey")]
/// <summary>
/// Gets or sets the RoomKey
/// </summary>
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
}
@@ -113,53 +137,71 @@ namespace PepperDash.Essentials
/// </summary>
public class MobileControlSimplRoomBridgePropertiesConfig
{
[JsonProperty("eiscId")]
/// <summary>
/// Gets or sets the EiscId
/// </summary>
[JsonProperty("eiscId")]
public string EiscId { get; set; }
}
/// <summary>
/// Represents a MobileControlApplicationConfig
/// </summary>
public class MobileControlApplicationConfig
{
/// <summary>
/// Gets or sets the ApiPath
/// </summary>
[JsonProperty("apiPath")]
public string ApiPath { get; set; }
[JsonProperty("gatewayAppPath")]
/// <summary>
/// Gets or sets the GatewayAppPath
/// </summary>
[JsonProperty("gatewayAppPath")]
public string GatewayAppPath { get; set; }
/// <summary>
/// Gets or sets the EnableDev
/// </summary>
[JsonProperty("enableDev")]
public bool? EnableDev { get; set; }
[JsonProperty("logoPath")]
/// <summary>
/// Gets or sets the LogoPath
/// </summary>
[JsonProperty("logoPath")]
public string LogoPath { get; set; }
/// <summary>
/// Gets or sets the IconSet
/// </summary>
[JsonProperty("iconSet")]
[JsonConverter(typeof(StringEnumConverter))]
public MCIconSet? IconSet { get; set; }
/// <summary>
/// Gets or sets the LoginMode
/// </summary>
[JsonProperty("loginMode")]
public string LoginMode { get; set; }
/// <summary>
/// Gets or sets the Modes
/// </summary>
[JsonProperty("modes")]
public Dictionary<string, McMode> Modes { get; set; }
[JsonProperty("enableRemoteLogging")]
/// <summary>
/// Gets or sets the Logging
/// </summary>
[JsonProperty("enableRemoteLogging")]
public bool Logging { get; set; }
[JsonProperty("partnerMetadata", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the PartnerMetadata
/// </summary>
[JsonProperty("partnerMetadata", NullValueHandling = NullValueHandling.Ignore)]
public List<MobileControlPartnerMetadata> PartnerMetadata { get; set; }
}
@@ -168,22 +210,22 @@ namespace PepperDash.Essentials
/// </summary>
public class MobileControlPartnerMetadata
{
[JsonProperty("role")]
/// <summary>
/// Gets or sets the Role
/// </summary>
[JsonProperty("role")]
public string Role { get; set; }
[JsonProperty("description")]
/// <summary>
/// Gets or sets the Description
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("logoPath")]
/// <summary>
/// Gets or sets the LogoPath
/// </summary>
[JsonProperty("logoPath")]
public string LogoPath { get; set; }
}
@@ -192,21 +234,22 @@ namespace PepperDash.Essentials
/// </summary>
public class McMode
{
[JsonProperty("listPageText")]
/// <summary>
/// Gets or sets the ListPageText
/// </summary>
[JsonProperty("listPageText")]
public string ListPageText { get; set; }
[JsonProperty("loginHelpText")]
/// <summary>
/// Gets or sets the LoginHelpText
/// </summary>
[JsonProperty("loginHelpText")]
public string LoginHelpText { get; set; }
[JsonProperty("passcodePageText")]
/// <summary>
/// Gets or sets the PasscodePageText
/// </summary>
[JsonProperty("passcodePageText")]
public string PasscodePageText { get; set; }
}
@@ -215,8 +258,19 @@ namespace PepperDash.Essentials
/// </summary>
public enum MCIconSet
{
/// <summary>
/// Google icon set
/// </summary>
GOOGLE,
/// <summary>
/// Habanero icon set
/// </summary>
HABANERO,
/// <summary>
/// Neo icon set
/// </summary>
NEO
}
}

View File

@@ -1,4 +1,10 @@
using Crestron.SimplSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.Net.Http;
using Crestron.SimplSharp.WebScripting;
@@ -30,12 +36,6 @@ using PepperDash.Essentials.RoomBridges;
using PepperDash.Essentials.Services;
using PepperDash.Essentials.WebApiHandlers;
using PepperDash.Essentials.WebSocketServer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using WebSocketSharp;
namespace PepperDash.Essentials
@@ -505,6 +505,25 @@ namespace PepperDash.Essentials
messengerAdded = true;
}
if (device is IBasicVideoMuteWithFeedback)
{
var deviceKey = device.Key;
this.LogVerbose(
"Adding IBasicVideoMuteWithFeedback for {deviceKey}",
deviceKey
);
var videoMuteControlDevice = device as IBasicVideoMuteWithFeedback;
var messenger = new IBasicVideoMuteWithFeedbackMessenger(
$"{device.Key}-videoMute-{Key}",
string.Format("/device/{0}", deviceKey),
videoMuteControlDevice
);
AddDefaultDeviceMessenger(messenger);
messengerAdded = true;
}
if (device is ILightingScenes || device is LightingBase)
{
var deviceKey = device.Key;
@@ -582,7 +601,7 @@ namespace PepperDash.Essentials
{
this.LogVerbose(
"Adding ISetTopBoxControlMessenger for {deviceKey}"
);
);
var messenger = new ISetTopBoxControlsMessenger(
$"{device.Key}-stb-{Key}",
@@ -599,7 +618,7 @@ namespace PepperDash.Essentials
{
this.LogVerbose(
"Adding IChannelMessenger for {deviceKey}", device.Key
);
);
var messenger = new IChannelMessenger(
$"{device.Key}-channel-{Key}",
@@ -614,7 +633,7 @@ namespace PepperDash.Essentials
if (device is IColor colorDevice)
{
this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key);
this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key);
var messenger = new IColorMessenger(
$"{device.Key}-color-{Key}",
@@ -629,7 +648,7 @@ namespace PepperDash.Essentials
if (device is IDPad dPadDevice)
{
this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key);
this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key);
var messenger = new IDPadMessenger(
$"{device.Key}-dPad-{Key}",
@@ -644,7 +663,7 @@ namespace PepperDash.Essentials
if (device is INumericKeypad nkDevice)
{
this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key);
this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key);
var messenger = new INumericKeypadMessenger(
$"{device.Key}-numericKeypad-{Key}",
@@ -659,7 +678,7 @@ namespace PepperDash.Essentials
if (device is IHasPowerControl pcDevice)
{
this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key);
this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key);
var messenger = new IHasPowerMessenger(
$"{device.Key}-powerControl-{Key}",
@@ -693,7 +712,7 @@ namespace PepperDash.Essentials
{
this.LogVerbose(
"Adding ITransportMessenger for {deviceKey}", device.Key
);
);
var messenger = new ITransportMessenger(
$"{device.Key}-transport-{Key}",
@@ -721,6 +740,17 @@ namespace PepperDash.Essentials
messengerAdded = true;
}
if (device is ICurrentSources currentSources)
{
this.LogVerbose("Adding CurrentSourcesMessenger for {deviceKey}", device.Key);
var messenger = new CurrentSourcesMessenger($"{device.Key}-currentSources-{Key}", $"/device/{device.Key}", currentSources);
AddDefaultDeviceMessenger(messenger);
messengerAdded = true;
}
if (device is ISwitchedOutput switchedDevice)
{
this.LogVerbose(
@@ -1625,15 +1655,14 @@ namespace PepperDash.Essentials
if (Config.EnableApiServer)
{
CrestronConsole.ConsoleCommandResponse(
@"Mobile Control Edge Server API Information:
Server address: {0}
System Name: {1}
System URL: {2}
System UUID: {3}
System User code: {4}
Connected?: {5}
Seconds Since Last Ack: {6}",
"Mobile Control Edge Server API Information:\r\n\r\n" +
"\tServer address: {0}\r\n" +
"\tSystem Name: {1}\r\n" +
"\tSystem URL: {2}\r\n" +
"\tSystem UUID: {3}\r\n" +
"\tSystem User code: {4}\r\n" +
"\tConnected?: {5}\r\n" +
"\tSeconds Since Last Ack: {6}\r\n",
url,
name,
ConfigReader.ConfigObject.SystemUrl,
@@ -1646,10 +1675,8 @@ namespace PepperDash.Essentials
else
{
CrestronConsole.ConsoleCommandResponse(
@"
Mobile Control Edge Server API Information:
Not Enabled in Config.
"
"\r\nMobile Control Edge Server API Information:\r\n" +
" Not Enabled in Config.\r\n"
);
}
@@ -1660,21 +1687,17 @@ Mobile Control Edge Server API Information:
)
{
CrestronConsole.ConsoleCommandResponse(
@"
Mobile Control Direct Server Information:
User App URL: {0}
Server port: {1}
",
"\r\nMobile Control Direct Server Information:\r\n" +
" User App URL: {0}\r\n" +
" Server port: {1}\r\n",
string.Format("{0}[insert_client_token]", _directServer.UserAppUrlPrefix),
_directServer.Port
);
CrestronConsole.ConsoleCommandResponse(
@"
UI Client Info:
Tokens Defined: {0}
Clients Connected: {1}
",
"\r\n UI Client Info:\r\n" +
" Tokens Defined: {0}\r\n" +
" Clients Connected: {1}\r\n",
_directServer.UiClients.Count,
_directServer.ConnectedUiClientsCount
);
@@ -1692,15 +1715,13 @@ Mobile Control Direct Server Information:
}
CrestronConsole.ConsoleCommandResponse(
@"
Client {0}:
Room Key: {1}
Touchpanel Key: {6}
Token: {2}
Client URL: {3}
Connected: {4}
Duration: {5}
",
"\r\nClient {0}:\r\n" +
"Room Key: {1}\r\n" +
"Touchpanel Key: {6}\r\n" +
"Token: {2}\r\n" +
"Client URL: {3}\r\n" +
"Connected: {4}\r\n" +
"Duration: {5}\r\n",
clientNo,
clientContext.Value.Token.RoomKey,
clientContext.Key,
@@ -1715,9 +1736,8 @@ Duration: {5}
else
{
CrestronConsole.ConsoleCommandResponse(
@"
Mobile Control Direct Server Infromation:
Not Enabled in Config."
"\r\nMobile Control Direct Server Information:\r\n" +
" Not Enabled in Config.\r\n"
);
}
}
@@ -2309,7 +2329,7 @@ Mobile Control Direct Server Infromation:
{
this.LogInformation("-- Warning: Incoming message has no registered handler {type}", message.Type);
break;
}
}
foreach (var handler in handlers)
{

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.UI;
@@ -106,6 +108,11 @@ namespace PepperDash.Essentials.Touchpanel
public ReadOnlyCollection<ConnectedIpInformation> ConnectedIps => Panel.ConnectedIpList;
private System.Net.IPAddress csIpAddress;
private System.Net.IPAddress csSubnetMask;
/// <summary>
/// Initializes a new instance of the MobileControlTouchpanelController class.
/// </summary>
@@ -182,6 +189,13 @@ namespace PepperDash.Essentials.Touchpanel
};
RegisterForExtenders();
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
var csSubnetMask = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, csAdapterId);
var csIpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
this.csSubnetMask = System.Net.IPAddress.Parse(csSubnetMask);
this.csIpAddress = System.Net.IPAddress.Parse(csIpAddress);
}
/// <summary>
@@ -381,19 +395,81 @@ namespace PepperDash.Essentials.Touchpanel
McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]);
UserCodeFeedback.LinkInputSig(Panel.StringInput[4]);
Panel.IpInformationChange += (sender, args) =>
{
if (args.Connected)
{
this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress);
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
var appUrl = GetUrlWithCorrectIp(_appUrl);
Panel.StringInput[1].StringValue = appUrl;
SetAppUrl(appUrl);
}
else
{
this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress);
}
};
Panel.OnlineStatusChange += (sender, args) =>
{
UpdateFeedbacks();
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
Panel.StringInput[1].StringValue = AppUrlFeedback.StringValue;
UpdateFeedbacks();
Panel.StringInput[1].StringValue = _appUrl;
Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue;
Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue;
Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue;
};
}
/// <summary>
/// Gets the URL with the correct IP address based on the connected devices and the Crestron processor's IP address.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private string GetUrlWithCorrectIp(string url)
{
var lanAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter);
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
if(csIpAddress == null || csSubnetMask == null || url == null)
{
this.LogWarning("CS IP Address Subnet Mask or url is null, cannot determine correct IP for URL");
return url;
}
this.LogVerbose("Processor IP: {processorIp}, CS IP: {csIpAddress}, CS Subnet Mask: {csSubnetMask}", processorIp, csIpAddress, csSubnetMask);
this.LogVerbose("Connected IP Count: {connectedIps}", ConnectedIps.Count);
var ip = ConnectedIps.Any(ipInfo =>
{
if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
{
return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask);
}
this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress);
return false;
}) ? csIpAddress.ToString() : processorIp;
var match = Regex.Match(url, @"^http://([^:/]+):\d+/mc/app\?token=.+$");
if (match.Success)
{
string ipa = match.Groups[1].Value;
// ip will be "192.168.1.100"
}
// replace ipa with ip but leave the rest of the string intact
var updatedUrl = Regex.Replace(url, @"^http://[^:/]+", $"http://{ip}");
this.LogVerbose("Updated URL: {updatedUrl}", updatedUrl);
return updatedUrl;
}
private void SubscribeForMobileControlUpdates()
{
foreach (var dev in DeviceManager.AllDevices)
@@ -443,7 +519,8 @@ namespace PepperDash.Essentials.Touchpanel
/// </summary>
public void SetAppUrl(string url)
{
_appUrl = url;
_appUrl = GetUrlWithCorrectIp(url);
AppUrlFeedback.FireUpdate();
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -41,8 +42,14 @@ namespace PepperDash.Essentials.WebSocketServer
private HttpServer _server;
/// <summary>
/// Gets the HttpServer instance
/// </summary>
public HttpServer Server => _server;
/// <summary>
/// Gets the collection of UI client contexts
/// </summary>
public Dictionary<string, UiClientContext> UiClients { get; private set; }
private readonly MobileControlSystemController _parent;
@@ -61,17 +68,20 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
private string lanIpAddress => CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter));
private string LanIpAddress => CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter));
private System.Net.IPAddress csIpAddress;
private readonly System.Net.IPAddress csIpAddress;
private System.Net.IPAddress csSubnetMask;
private readonly System.Net.IPAddress csSubnetMask;
/// <summary>
/// The path for the WebSocket messaging
/// </summary>
private readonly string _wsPath = "/mc/api/ui/join/";
/// <summary>
/// Gets the WebSocket path
/// </summary>
public string WsPath => _wsPath;
/// <summary>
@@ -89,6 +99,9 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public int Port { get; private set; }
/// <summary>
/// Gets the user app URL prefix
/// </summary>
public string UserAppUrlPrefix
{
get
@@ -101,6 +114,9 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
/// <summary>
/// Gets the count of connected UI clients
/// </summary>
public int ConnectedUiClientsCount
{
get
@@ -119,6 +135,9 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
/// <summary>
/// Initializes a new instance of the MobileControlWebsocketServer class.
/// </summary>
public MobileControlWebsocketServer(string key, int customPort, MobileControlSystemController parent)
: base(key)
{
@@ -327,17 +346,26 @@ namespace PepperDash.Essentials.WebSocketServer
}
string ip = processorIp;
if (touchpanel.Touchpanel is IMobileControlCrestronTouchpanelController crestronTouchpanel && csIpAddress != null)
// Moved to the MobileControlTouchpanelController class in the GetUrlWithCorrectIp method
// triggered by the Panel.IpInformationChange event so that we know we have the necessary info
// to make the determination of which IP to use.
//if (touchpanel.Touchpanel is IMobileControlCrestronTouchpanelController crestronTouchpanel && csIpAddress != null)
//{
// ip = crestronTouchpanel.ConnectedIps.Any(ipInfo =>
// {
// if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
// {
// return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask);
// }
// this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress);
// return false;
// }) ? csIpAddress.ToString() : processorIp;
//}
if (_parent.Config.DirectServer.CSLanUiDeviceKeys != null && _parent.Config.DirectServer.CSLanUiDeviceKeys.Any(k => k.Equals(touchpanel.Touchpanel.Key, StringComparison.InvariantCultureIgnoreCase)) && csIpAddress != null)
{
ip = crestronTouchpanel.ConnectedIps.Any(ipInfo =>
{
if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
{
return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask);
}
this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress);
return false;
}) ? csIpAddress.ToString() : processorIp;
ip = csIpAddress.ToString();
}
var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
@@ -621,6 +649,9 @@ namespace PepperDash.Essentials.WebSocketServer
CrestronConsole.ConsoleCommandResponse($"Token: {token}");
}
/// <summary>
/// Validates the grant code against the room key
/// </summary>
public (string, string) ValidateGrantCode(string grantCode, string roomKey)
{
var bridge = _parent.GetRoomBridge(roomKey);
@@ -634,6 +665,9 @@ namespace PepperDash.Essentials.WebSocketServer
return ValidateGrantCode(grantCode, bridge);
}
/// <summary>
/// Validates the grant code against the room key
/// </summary>
public (string, string) ValidateGrantCode(string grantCode, MobileControlBridgeBase bridge)
{
// TODO: Authenticate grant code passed in
@@ -655,6 +689,9 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
/// <summary>
/// Generates a new client token for the specified bridge
/// </summary>
public (string, string) GenerateClientToken(MobileControlBridgeBase bridge, string touchPanelKey = "")
{
var key = Guid.NewGuid().ToString();

View File

@@ -1,5 +1,5 @@
using System;
using System;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using Crestron.SimplSharp;
@@ -41,29 +41,6 @@ namespace PepperDash.Essentials
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose);
// AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
}
private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name).Name;
if (assemblyName == "PepperDash_Core")
{
return Assembly.LoadFrom("PepperDashCore.dll");
}
if (assemblyName == "PepperDash_Essentials_Core")
{
return Assembly.LoadFrom("PepperDash.Essentials.Core.dll");
}
if (assemblyName == "Essentials Devices Common")
{
return Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll");
}
return null;
}
/// <summary>
@@ -267,6 +244,8 @@ namespace PepperDash.Essentials
// _ = new ProcessorExtensionDeviceFactory();
// _ = new MobileControlFactory();
LoadAssets();
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration");
var filesReady = SetupFilesystem();
@@ -568,5 +547,142 @@ namespace PepperDash.Essentials
return false;
}
}
private static void LoadAssets()
{
var applicationDirectory = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
Debug.LogMessage(LogEventLevel.Information, "Searching: {applicationDirectory:l} for embedded assets - {Destination}", applicationDirectory.FullName, Global.FilePathPrefix);
var zipFiles = applicationDirectory.GetFiles("assets*.zip");
if (zipFiles.Length > 1)
{
throw new Exception("Multiple assets zip files found. Cannot continue.");
}
if (zipFiles.Length == 1)
{
var zipFile = zipFiles[0];
var assetsRoot = System.IO.Path.GetFullPath(Global.FilePathPrefix);
if (!assetsRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && !assetsRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
{
assetsRoot += Path.DirectorySeparatorChar;
}
Debug.LogMessage(LogEventLevel.Information, "Found assets zip file: {zipFile:l}... Unzipping...", zipFile.FullName);
using (var archive = ZipFile.OpenRead(zipFile.FullName))
{
foreach (var entry in archive.Entries)
{
var destinationPath = Path.Combine(Global.FilePathPrefix, entry.FullName);
var fullDest = System.IO.Path.GetFullPath(destinationPath);
if (!fullDest.StartsWith(assetsRoot, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException($"Entry '{entry.FullName}' is trying to extract outside of the target directory.");
if (string.IsNullOrEmpty(entry.Name))
{
Directory.CreateDirectory(destinationPath);
continue;
}
// If a directory exists where a file should go, delete it
if (Directory.Exists(destinationPath))
Directory.Delete(destinationPath, true);
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
entry.ExtractToFile(destinationPath, true);
Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath);
}
}
}
// cleaning up zip files
foreach (var file in zipFiles)
{
File.Delete(file.FullName);
}
var htmlZipFiles = applicationDirectory.GetFiles("htmlassets*.zip");
if (htmlZipFiles.Length > 1)
{
throw new Exception("Multiple htmlassets zip files found in application directory. Please ensure only one htmlassets*.zip file is present and retry.");
}
if (htmlZipFiles.Length == 1)
{
var htmlZipFile = htmlZipFiles[0];
var programDir = new DirectoryInfo(Global.FilePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
var userOrNvramDir = programDir.Parent;
var rootDir = userOrNvramDir?.Parent;
if (rootDir == null)
{
throw new Exception($"Unable to determine root directory for html extraction. Current path: {Global.FilePathPrefix}");
}
var htmlDir = Path.Combine(rootDir.FullName, "html");
var htmlRoot = System.IO.Path.GetFullPath(htmlDir);
if (!htmlRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
!htmlRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
{
htmlRoot += Path.DirectorySeparatorChar;
}
Debug.LogMessage(LogEventLevel.Information, "Found htmlassets zip file: {zipFile:l}... Unzipping...", htmlZipFile.FullName);
using (var archive = ZipFile.OpenRead(htmlZipFile.FullName))
{
foreach (var entry in archive.Entries)
{
var destinationPath = Path.Combine(htmlDir, entry.FullName);
var fullDest = System.IO.Path.GetFullPath(destinationPath);
if (!fullDest.StartsWith(htmlRoot, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException($"Entry '{entry.FullName}' is trying to extract outside of the target directory.");
if (string.IsNullOrEmpty(entry.Name))
{
Directory.CreateDirectory(destinationPath);
continue;
}
// Only delete the file if it exists and is a file, not a directory
if (File.Exists(destinationPath))
File.Delete(destinationPath);
var parentDir = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(parentDir))
Directory.CreateDirectory(parentDir);
entry.ExtractToFile(destinationPath, true);
Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath);
}
}
}
// cleaning up html zip files
foreach (var file in htmlZipFiles)
{
File.Delete(file.FullName);
}
var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json");
if (jsonFiles.Length > 1)
{
throw new Exception("Multiple configuration files found. Cannot continue.");
}
if (jsonFiles.Length == 1)
{
var jsonFile = jsonFiles[0];
var finalPath = Path.Combine(Global.FilePathPrefix, jsonFile.Name);
Debug.LogMessage(LogEventLevel.Information, "Found configuration file: {jsonFile:l}... Moving to: {Destination}", jsonFile.FullName, finalPath);
if (File.Exists(finalPath))
{
Debug.LogMessage(LogEventLevel.Information, "Removing existing configuration file: {Destination}", finalPath);
File.Delete(finalPath);
}
jsonFile.MoveTo(finalPath);
}
}
}
}

View File

@@ -20,9 +20,9 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 4.7.2|AnyCPU'">
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<DocumentationFile>bin\$(Configuration)\PepperDashEssentials.xml</DocumentationFile>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<DocumentationFile>bin\$(Configuration)\PepperDashEssentials.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Include="Example Configuration\EssentialsHuddleSpaceRoom\configurationFile-HuddleSpace-2-Source.json">
@@ -49,6 +49,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.90" />
<PackageReference Include="System.IO.Compression" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />