Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
94909d2c7c Finalize CallStatusMessenger implementation - remove test files and verify clean implementation
Co-authored-by: ngenovese11 <23391587+ngenovese11@users.noreply.github.com>
2025-08-13 21:16:35 +00:00
copilot-swe-agent[bot]
9946e9a9ae Implement CallStatusMessenger for interface-based devices and update MobileControlSystemController
Co-authored-by: ngenovese11 <23391587+ngenovese11@users.noreply.github.com>
2025-08-13 21:14:29 +00:00
copilot-swe-agent[bot]
db80b7b501 Initial plan 2025-08-13 20:58:29 +00:00
30 changed files with 866 additions and 1188 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" /> <PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.157" /> <PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.90" />
<PackageReference Include="Serilog" Version="3.1.1" /> <PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Expressions" Version="4.0.0" /> <PackageReference Include="Serilog.Expressions" Version="4.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" /> <PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />

View File

@@ -2,12 +2,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using System.Reflection;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.EthernetCommunication; using Crestron.SimplSharpPro.EthernetCommunication;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using Serilog.Events; using Serilog.Events;
@@ -353,22 +355,22 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public class EiscApiPropertiesConfig public class EiscApiPropertiesConfig
{ {
[JsonProperty("control")]
/// <summary> /// <summary>
/// Gets or sets the Control /// Gets or sets the Control
/// </summary> /// </summary>
[JsonProperty("control")]
public EssentialsControlPropertiesConfig Control { get; set; } public EssentialsControlPropertiesConfig Control { get; set; }
[JsonProperty("devices")]
/// <summary> /// <summary>
/// Gets or sets the Devices /// Gets or sets the Devices
/// </summary> /// </summary>
[JsonProperty("devices")]
public List<ApiDevicePropertiesConfig> Devices { get; set; } public List<ApiDevicePropertiesConfig> Devices { get; set; }
[JsonProperty("rooms")]
/// <summary> /// <summary>
/// Gets or sets the Rooms /// Gets or sets the Rooms
/// </summary> /// </summary>
[JsonProperty("rooms")]
public List<ApiRoomPropertiesConfig> Rooms { get; set; } public List<ApiRoomPropertiesConfig> Rooms { get; set; }
@@ -377,22 +379,22 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public class ApiDevicePropertiesConfig public class ApiDevicePropertiesConfig
{ {
[JsonProperty("deviceKey")]
/// <summary> /// <summary>
/// Gets or sets the DeviceKey /// Gets or sets the DeviceKey
/// </summary> /// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; } public string DeviceKey { get; set; }
[JsonProperty("joinStart")]
/// <summary> /// <summary>
/// Gets or sets the JoinStart /// Gets or sets the JoinStart
/// </summary> /// </summary>
[JsonProperty("joinStart")]
public uint JoinStart { get; set; } public uint JoinStart { get; set; }
[JsonProperty("joinMapKey")]
/// <summary> /// <summary>
/// Gets or sets the JoinMapKey /// Gets or sets the JoinMapKey
/// </summary> /// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; } public string JoinMapKey { get; set; }
} }
@@ -401,22 +403,22 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public class ApiRoomPropertiesConfig public class ApiRoomPropertiesConfig
{ {
[JsonProperty("roomKey")]
/// <summary> /// <summary>
/// Gets or sets the RoomKey /// Gets or sets the RoomKey
/// </summary> /// </summary>
[JsonProperty("roomKey")]
public string RoomKey { get; set; } public string RoomKey { get; set; }
[JsonProperty("joinStart")]
/// <summary> /// <summary>
/// Gets or sets the JoinStart /// Gets or sets the JoinStart
/// </summary> /// </summary>
[JsonProperty("joinStart")]
public uint JoinStart { get; set; } public uint JoinStart { get; set; }
[JsonProperty("joinMapKey")]
/// <summary> /// <summary>
/// Gets or sets the JoinMapKey /// Gets or sets the JoinMapKey
/// </summary> /// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; } public string JoinMapKey { get; set; }
} }

View File

@@ -7,13 +7,6 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public interface IBridgeAdvanced 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); void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
} }
} }

View File

@@ -1,138 +0,0 @@
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,9 +1,11 @@
using System; using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM; using Crestron.SimplSharpPro.DM;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using Serilog.Events; using Serilog.Events;
@@ -54,9 +56,6 @@ namespace PepperDash.Essentials.Core
case eControlMethod.Com: case eControlMethod.Com:
comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig); comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig);
break; break;
case eControlMethod.ComBridge:
comm = new CommBridge(deviceConfig.Key + "-simpl", deviceConfig.Name + " Simpl");
break;
case eControlMethod.Cec: case eControlMethod.Cec:
comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig); comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig);
break; break;
@@ -108,7 +107,8 @@ namespace PepperDash.Essentials.Core
} }
// put it in the device manager if it's the right flavor // put it in the device manager if it's the right flavor
if (comm is Device comDev) var comDev = comm as Device;
if (comDev != null)
DeviceManager.AddDevice(comDev); DeviceManager.AddDevice(comDev);
return comm; return comm;
} }

View File

@@ -19,11 +19,5 @@ namespace PepperDash.Essentials.Core.Config
/// </summary> /// </summary>
[JsonProperty("multicastAudioAddress", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("multicastAudioAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastAudioAddress { get; set; } 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

@@ -18,41 +18,41 @@ namespace PepperDash.Essentials.Core.Config
/// </summary> /// </summary>
public class DeviceConfig public class DeviceConfig
{ {
[JsonProperty("key")]
/// <summary> /// <summary>
/// Gets or sets the Key /// Gets or sets the Key
/// </summary> /// </summary>
[JsonProperty("key")]
public string Key { get; set; } public string Key { get; set; }
[JsonProperty("uid")]
/// <summary> /// <summary>
/// Gets or sets the Uid /// Gets or sets the Uid
/// </summary> /// </summary>
[JsonProperty("uid")]
public int Uid { get; set; } public int Uid { get; set; }
[JsonProperty("name")]
/// <summary> /// <summary>
/// Gets or sets the Name /// Gets or sets the Name
/// </summary> /// </summary>
[JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("group")]
/// <summary> /// <summary>
/// Gets or sets the Group /// Gets or sets the Group
/// </summary> /// </summary>
[JsonProperty("group")]
public string Group { get; set; } public string Group { get; set; }
[JsonProperty("type")]
/// <summary> /// <summary>
/// Gets or sets the Type /// Gets or sets the Type
/// </summary> /// </summary>
[JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("properties")]
[JsonConverter(typeof(DevicePropertiesConverter))]
/// <summary> /// <summary>
/// Gets or sets the Properties /// Gets or sets the Properties
/// </summary> /// </summary>
[JsonProperty("properties")]
[JsonConverter(typeof(DevicePropertiesConverter))]
public JToken Properties { get; set; } public JToken Properties { get; set; }
public DeviceConfig(DeviceConfig dc) public DeviceConfig(DeviceConfig dc)

View File

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

View File

@@ -202,15 +202,14 @@ namespace PepperDash.Essentials.Core
private static void ListDevices(string s) private static void ListDevices(string s)
{ {
CrestronConsole.ConsoleCommandResponse($"{Devices.Count} Devices registered with Device Manager:\r\n"); Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count);
var sorted = Devices.Values.ToList(); var sorted = Devices.Values.ToList();
sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var d in sorted) foreach (var d in sorted)
{ {
var name = d is IKeyName ? (d as IKeyName).Name : "---"; var name = d is IKeyName ? (d as IKeyName).Name : "---";
CrestronConsole.ConsoleCommandResponse($" [{d.Key}] {name}\r\n"); Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
} }
} }
@@ -219,17 +218,28 @@ namespace PepperDash.Essentials.Core
var dev = GetDeviceForKey(devKey); var dev = GetDeviceForKey(devKey);
if (dev == null) if (dev == null)
{ {
CrestronConsole.ConsoleCommandResponse($"Device '{devKey}' not found\r\n"); Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
return; return;
} }
if (!(dev is IHasFeedback statusDev)) if (!(dev is IHasFeedback statusDev))
{ {
CrestronConsole.ConsoleCommandResponse($"Device '{devKey}' does not have visible feedbacks\r\n"); Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
return; return;
} }
statusDev.DumpFeedbacksToConsole(true); statusDev.DumpFeedbacksToConsole(true);
} }
//static void ListDeviceCommands(string devKey)
//{
// var dev = GetDeviceForKey(devKey);
// if (dev == null)
// {
// Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
// return;
// }
// Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey);
//}
private static void ListDeviceCommStatuses(string input) private static void ListDeviceCommStatuses(string input)
{ {
@@ -239,6 +249,12 @@ namespace PepperDash.Essentials.Core
} }
} }
//static void DoDeviceCommand(string command)
//{
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
//}
/// <summary> /// <summary>
/// AddDevice method /// AddDevice method
/// </summary> /// </summary>

View File

@@ -3,29 +3,16 @@ using PepperDash.Essentials.Core.Bridges;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
/// <summary>
/// Base class for devices that can be bridged to an EISC API.
/// </summary>
public abstract class EssentialsBridgeableDevice:EssentialsDevice, IBridgeAdvanced 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) 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) protected EssentialsBridgeableDevice(string key, string name) : base(key, name)
{ {
} }
/// <inheritdoc />
public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
} }
} }

View File

@@ -1,6 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
using Serilog.Events; using Serilog.Events;
@@ -19,72 +19,47 @@ namespace PepperDash.Essentials.Core
} }
/// <summary>
/// Extension methods for IHasFeedback
/// </summary>
public static class IHasFeedbackExtensions public static class IHasFeedbackExtensions
{ {
/// <summary>
/// Gets the feedback type name for sorting purposes
/// </summary>
/// <param name="feedback">The feedback to get the type name for</param>
/// <returns>A string representing the feedback type</returns>
private static string GetFeedbackTypeName(Feedback feedback)
{
if (feedback is BoolFeedback)
return "boolean";
else if (feedback is IntFeedback)
return "integer";
else if (feedback is StringFeedback)
return "string";
else
return feedback.GetType().Name;
}
/// <summary>
/// Dumps the feedbacks to the console
/// </summary>
/// <param name="source"></param>
/// <param name="getCurrentStates"></param>
public static void DumpFeedbacksToConsole(this IHasFeedback source, bool getCurrentStates) public static void DumpFeedbacksToConsole(this IHasFeedback source, bool getCurrentStates)
{ {
Type t = source.GetType();
// get the properties and set them into a new collection of NameType wrappers
var props = t.GetProperties().Select(p => new PropertyNameType(p, t));
var feedbacks = source.Feedbacks; var feedbacks = source.Feedbacks;
if (feedbacks != null)
if (feedbacks == null || feedbacks.Count == 0)
{ {
CrestronConsole.ConsoleCommandResponse("No available feedbacks\r\n"); Debug.LogMessage(LogEventLevel.Information, source, "\n\nAvailable feedbacks:");
return; foreach (var f in feedbacks)
}
CrestronConsole.ConsoleCommandResponse("Available feedbacks:\r\n");
// Sort feedbacks by type first, then by key
var sortedFeedbacks = feedbacks.OrderBy(f => GetFeedbackTypeName(f)).ThenBy(f => string.IsNullOrEmpty(f.Key) ? "" : f.Key);
foreach (var feedback in sortedFeedbacks)
{ {
string value = ""; string val = "";
string type = ""; string type = "";
if (getCurrentStates) if (getCurrentStates)
{ {
if (feedback is BoolFeedback) if (f is BoolFeedback)
{ {
value = feedback.BoolValue.ToString(); val = f.BoolValue.ToString();
type = "boolean"; type = "boolean";
} }
else if (feedback is IntFeedback) else if (f is IntFeedback)
{ {
value = feedback.IntValue.ToString(); val = f.IntValue.ToString();
type = "integer"; type = "integer";
} }
else if (feedback is StringFeedback) else if (f is StringFeedback)
{ {
value = feedback.StringValue; val = f.StringValue;
type = "string"; type = "string";
} }
} }
CrestronConsole.ConsoleCommandResponse($" {type,-12} {(string.IsNullOrEmpty(feedback.Key) ? "-no key-" : feedback.Key),-25} {value}\r\n"); Debug.LogMessage(LogEventLevel.Information, "{0,-12} {1, -25} {2}", type,
} (string.IsNullOrEmpty(f.Key) ? "-no key-" : f.Key), val);
}
}
else
Debug.LogMessage(LogEventLevel.Information, source, "No available outputs:");
} }
} }
} }

View File

@@ -25,7 +25,7 @@
<DocumentationFile>bin\$(Configuration)\PepperDash_Essentials_Core.xml</DocumentationFile> <DocumentationFile>bin\$(Configuration)\PepperDash_Essentials_Core.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.157" /> <PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Crestron\CrestronGenericBaseDevice.cs.orig" /> <None Include="Crestron\CrestronGenericBaseDevice.cs.orig" />

View File

@@ -114,16 +114,16 @@ namespace PepperDash.Essentials.Room.Config
[JsonProperty("emergency")] [JsonProperty("emergency")]
public EssentialsRoomEmergencyConfig Emergency { get; set; } public EssentialsRoomEmergencyConfig Emergency { get; set; }
[JsonProperty("help")]
/// <summary> /// <summary>
/// Gets or sets the Help /// Gets or sets the Help
/// </summary> /// </summary>
[JsonProperty("help")]
public EssentialsHelpPropertiesConfig Help { get; set; } public EssentialsHelpPropertiesConfig Help { get; set; }
[JsonProperty("helpMessage")]
/// <summary> /// <summary>
/// Gets or sets the HelpMessage /// Gets or sets the HelpMessage
/// </summary> /// </summary>
[JsonProperty("helpMessage")]
public string HelpMessage { get; set; } public string HelpMessage { get; set; }
/// <summary> /// <summary>
@@ -144,82 +144,82 @@ namespace PepperDash.Essentials.Room.Config
} }
} }
[JsonProperty("environment")]
/// <summary> /// <summary>
/// Gets or sets the Environment /// Gets or sets the Environment
/// </summary> /// </summary>
[JsonProperty("environment")]
public EssentialsEnvironmentPropertiesConfig Environment { get; set; } public EssentialsEnvironmentPropertiesConfig Environment { get; set; }
[JsonProperty("logo")]
/// <summary> /// <summary>
/// Gets or sets the LogoLight /// Gets or sets the LogoLight
/// </summary> /// </summary>
[JsonProperty("logo")]
public EssentialsLogoPropertiesConfig LogoLight { get; set; } public EssentialsLogoPropertiesConfig LogoLight { get; set; }
[JsonProperty("logoDark")]
/// <summary> /// <summary>
/// Gets or sets the LogoDark /// Gets or sets the LogoDark
/// </summary> /// </summary>
[JsonProperty("logoDark")]
public EssentialsLogoPropertiesConfig LogoDark { get; set; } public EssentialsLogoPropertiesConfig LogoDark { get; set; }
[JsonProperty("microphonePrivacy")]
/// <summary> /// <summary>
/// Gets or sets the MicrophonePrivacy /// Gets or sets the MicrophonePrivacy
/// </summary> /// </summary>
[JsonProperty("microphonePrivacy")]
public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; } public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; }
[JsonProperty("occupancy")]
/// <summary> /// <summary>
/// Gets or sets the Occupancy /// Gets or sets the Occupancy
/// </summary> /// </summary>
[JsonProperty("occupancy")]
public EssentialsRoomOccSensorConfig Occupancy { get; set; } public EssentialsRoomOccSensorConfig Occupancy { get; set; }
[JsonProperty("oneButtonMeeting")]
/// <summary> /// <summary>
/// Gets or sets the OneButtonMeeting /// Gets or sets the OneButtonMeeting
/// </summary> /// </summary>
[JsonProperty("oneButtonMeeting")]
public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; }
[JsonProperty("shutdownVacancySeconds")]
/// <summary> /// <summary>
/// Gets or sets the ShutdownVacancySeconds /// Gets or sets the ShutdownVacancySeconds
/// </summary> /// </summary>
[JsonProperty("shutdownVacancySeconds")]
public int ShutdownVacancySeconds { get; set; } public int ShutdownVacancySeconds { get; set; }
[JsonProperty("shutdownPromptSeconds")]
/// <summary> /// <summary>
/// Gets or sets the ShutdownPromptSeconds /// Gets or sets the ShutdownPromptSeconds
/// </summary> /// </summary>
[JsonProperty("shutdownPromptSeconds")]
public int ShutdownPromptSeconds { get; set; } public int ShutdownPromptSeconds { get; set; }
[JsonProperty("tech")]
/// <summary> /// <summary>
/// Gets or sets the Tech /// Gets or sets the Tech
/// </summary> /// </summary>
[JsonProperty("tech")]
public EssentialsRoomTechConfig Tech { get; set; } public EssentialsRoomTechConfig Tech { get; set; }
[JsonProperty("volumes")]
/// <summary> /// <summary>
/// Gets or sets the Volumes /// Gets or sets the Volumes
/// </summary> /// </summary>
[JsonProperty("volumes")]
public EssentialsRoomVolumesConfig Volumes { get; set; } public EssentialsRoomVolumesConfig Volumes { get; set; }
[JsonProperty("fusion")]
/// <summary> /// <summary>
/// Gets or sets the Fusion /// Gets or sets the Fusion
/// </summary> /// </summary>
[JsonProperty("fusion")]
public EssentialsRoomFusionConfig Fusion { get; set; } public EssentialsRoomFusionConfig Fusion { get; set; }
[JsonProperty("essentialsRoomUiBehaviorConfig", NullValueHandling=NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the UiBehavior /// Gets or sets the UiBehavior
/// </summary> /// </summary>
[JsonProperty("essentialsRoomUiBehaviorConfig", NullValueHandling = NullValueHandling.Ignore)]
public EssentialsRoomUiBehaviorConfig UiBehavior { get; set; } public EssentialsRoomUiBehaviorConfig UiBehavior { get; set; }
[JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")]
/// <summary> /// <summary>
/// Gets or sets the ZeroVolumeWhenSwtichingVolumeDevices /// Gets or sets the ZeroVolumeWhenSwtichingVolumeDevices
/// </summary> /// </summary>
[JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")]
public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; }
/// <summary> /// <summary>

View File

@@ -15,9 +15,9 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
////***************************************************************************** ////*****************************************************************************
///// <summary> /// <summary>
///// Base class for all subpage reference list controllers /// Base class for all subpage reference list controllers
///// </summary> /// </summary>
//public class SubpageReferenceListController //public class SubpageReferenceListController
//{ //{
// public SubpageReferenceList TheList { get; protected set; } // public SubpageReferenceList TheList { get; protected set; }

View File

@@ -44,9 +44,9 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public const uint ModalVisibleJoin = 3999; public const uint ModalVisibleJoin = 3999;
///// <summary> /// <summary>
///// The seconds value of the countdown timer. Ushort join 3991 /// The seconds value of the countdown timer. Ushort join 3991
///// </summary> /// </summary>
//public const uint TimerSecondsJoin = 3991; //public const uint TimerSecondsJoin = 3991;
/// <summary> /// <summary>
/// The full ushort value of the countdown timer for a gauge. Ushort join 3992 /// The full ushort value of the countdown timer for a gauge. Ushort join 3992

View File

@@ -29,6 +29,6 @@
<ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" /> <ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.157" /> <PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,221 @@
using System;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices that implement call status interfaces
/// without requiring VideoCodecBase inheritance
/// </summary>
public class CallStatusMessenger : MessengerBase
{
/// <summary>
/// Device with dialer capabilities
/// </summary>
protected IHasDialer Dialer { get; private set; }
/// <summary>
/// Device with content sharing capabilities (optional)
/// </summary>
protected IHasContentSharing ContentSharing { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="dialer"></param>
/// <param name="messagePath"></param>
public CallStatusMessenger(string key, IHasDialer dialer, string messagePath)
: base(key, messagePath, dialer as IKeyName)
{
Dialer = dialer ?? throw new ArgumentNullException(nameof(dialer));
dialer.CallStatusChange += Dialer_CallStatusChange;
// Check for optional content sharing interface
if (dialer is IHasContentSharing contentSharing)
{
ContentSharing = contentSharing;
contentSharing.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange;
contentSharing.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange;
}
}
/// <summary>
/// Handles call status changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Dialer_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e)
{
try
{
SendFullStatus();
}
catch (Exception ex)
{
this.LogError(ex, "Error handling call status change: {error}", ex.Message);
}
}
/// <summary>
/// Handles content sharing status changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostStatusMessage(JToken.FromObject(new
{
sharingContentIsOn = e.BoolValue
}));
}
/// <summary>
/// Handles sharing source changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostStatusMessage(JToken.FromObject(new
{
sharingSource = e.StringValue
}));
}
/// <summary>
/// Gets active calls from the dialer
/// </summary>
/// <returns></returns>
private object GetActiveCalls()
{
// Try to get active calls if the dialer has an ActiveCalls property
var dialerType = Dialer.GetType();
var activeCallsProperty = dialerType.GetProperty("ActiveCalls");
if (activeCallsProperty != null && activeCallsProperty.PropertyType == typeof(System.Collections.Generic.List<CodecActiveCallItem>))
{
var activeCalls = activeCallsProperty.GetValue(Dialer) as System.Collections.Generic.List<CodecActiveCallItem>;
return activeCalls ?? new System.Collections.Generic.List<CodecActiveCallItem>();
}
// Return basic call status if no ActiveCalls property
return new { isInCall = Dialer.IsInCall };
}
/// <summary>
/// Sends full status message
/// </summary>
public void SendFullStatus()
{
var status = new
{
isInCall = Dialer.IsInCall,
calls = GetActiveCalls()
};
// Add content sharing status if available
if (ContentSharing != null)
{
var statusWithSharing = new
{
isInCall = Dialer.IsInCall,
calls = GetActiveCalls(),
sharingContentIsOn = ContentSharing.SharingContentIsOnFeedback.BoolValue,
sharingSource = ContentSharing.SharingSourceFeedback.StringValue
};
PostStatusMessage(JToken.FromObject(statusWithSharing));
}
else
{
PostStatusMessage(JToken.FromObject(status));
}
}
/// <summary>
/// Registers actions for call control
/// </summary>
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus());
// Basic call control actions
AddAction("/dial", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
Dialer.Dial(msg.Value);
});
AddAction("/endAllCalls", (id, content) => Dialer.EndAllCalls());
AddAction("/dtmf", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
Dialer.SendDtmf(msg.Value);
});
// Call-specific actions (if active calls are available)
AddAction("/endCallById", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(msg.Value);
if (call != null)
Dialer.EndCall(call);
});
AddAction("/acceptById", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(msg.Value);
if (call != null)
Dialer.AcceptCall(call);
});
AddAction("/rejectById", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(msg.Value);
if (call != null)
Dialer.RejectCall(call);
});
// Content sharing actions if available
if (ContentSharing != null)
{
AddAction("/startSharing", (id, content) => ContentSharing.StartSharing());
AddAction("/stopSharing", (id, content) => ContentSharing.StopSharing());
}
}
/// <summary>
/// Finds a call by ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private CodecActiveCallItem GetCallWithId(string id)
{
// Try to get call using reflection for ActiveCalls property
var dialerType = Dialer.GetType();
var activeCallsProperty = dialerType.GetProperty("ActiveCalls");
if (activeCallsProperty != null && activeCallsProperty.PropertyType == typeof(System.Collections.Generic.List<CodecActiveCallItem>))
{
var activeCalls = activeCallsProperty.GetValue(Dialer) as System.Collections.Generic.List<CodecActiveCallItem>;
if (activeCalls != null)
{
return activeCalls.FirstOrDefault(c => c.Id.Equals(id));
}
}
return null;
}
}
}

View File

@@ -140,19 +140,17 @@ namespace PepperDash.Essentials.AppServer.Messengers
if (Camera is IHasCameraPresets presetsCamera) if (Camera is IHasCameraPresets presetsCamera)
{ {
AddAction("/recallPreset", (id, content) => for (int i = 1; i <= 6; i++)
{
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);
});
} }
} }
@@ -166,8 +164,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
return; return;
} }
timerHandler(Camera.Key, cameraAction); timerHandler(state.Value, cameraAction);
cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase));
} }
/// <summary> /// <summary>

View File

@@ -1,77 +0,0 @@
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

@@ -33,7 +33,7 @@
<Compile Remove="Messengers\SIMPLVtcMessenger.cs" /> <Compile Remove="Messengers\SIMPLVtcMessenger.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.157" /> <PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" /> <ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />

View File

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

View File

@@ -27,6 +27,7 @@ using PepperDash.Essentials.Core.Shades;
using PepperDash.Essentials.Core.Web; using PepperDash.Essentials.Core.Web;
using PepperDash.Essentials.Devices.Common.AudioCodec; using PepperDash.Essentials.Devices.Common.AudioCodec;
using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Cameras;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Devices.Common.Displays; using PepperDash.Essentials.Devices.Common.Displays;
using PepperDash.Essentials.Devices.Common.Lighting; using PepperDash.Essentials.Devices.Common.Lighting;
using PepperDash.Essentials.Devices.Common.SoftCodec; using PepperDash.Essentials.Devices.Common.SoftCodec;
@@ -505,25 +506,6 @@ namespace PepperDash.Essentials
messengerAdded = true; 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) if (device is ILightingScenes || device is LightingBase)
{ {
var deviceKey = device.Key; var deviceKey = device.Key;
@@ -579,6 +561,21 @@ namespace PepperDash.Essentials
messengerAdded = true; messengerAdded = true;
} }
else if (device is IHasDialer dialer && !messengerAdded)
{
this.LogVerbose(
"Adding CallStatusMessenger for {deviceKey}", device.Key);
var messenger = new CallStatusMessenger(
$"{device.Key}-callStatus-{Key}",
dialer,
$"/device/{device.Key}"
);
AddDefaultDeviceMessenger(messenger);
messengerAdded = true;
}
if (device is AudioCodecBase audioCodec) if (device is AudioCodecBase audioCodec)
{ {

View File

@@ -38,7 +38,7 @@
<Compile Remove="RoomBridges\SourceDeviceMapDictionary.cs" /> <Compile Remove="RoomBridges\SourceDeviceMapDictionary.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.157" /> <PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" /> <PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -2,8 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.UI; using Crestron.SimplSharpPro.UI;
@@ -108,11 +106,6 @@ namespace PepperDash.Essentials.Touchpanel
public ReadOnlyCollection<ConnectedIpInformation> ConnectedIps => Panel.ConnectedIpList; public ReadOnlyCollection<ConnectedIpInformation> ConnectedIps => Panel.ConnectedIpList;
private System.Net.IPAddress csIpAddress;
private System.Net.IPAddress csSubnetMask;
/// <summary> /// <summary>
/// Initializes a new instance of the MobileControlTouchpanelController class. /// Initializes a new instance of the MobileControlTouchpanelController class.
/// </summary> /// </summary>
@@ -189,13 +182,6 @@ namespace PepperDash.Essentials.Touchpanel
}; };
RegisterForExtenders(); 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> /// <summary>
@@ -395,81 +381,19 @@ namespace PepperDash.Essentials.Touchpanel
McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]); McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]);
UserCodeFeedback.LinkInputSig(Panel.StringInput[4]); 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) => Panel.OnlineStatusChange += (sender, args) =>
{ {
UpdateFeedbacks();
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
UpdateFeedbacks(); Panel.StringInput[1].StringValue = AppUrlFeedback.StringValue;
Panel.StringInput[1].StringValue = _appUrl;
Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue; Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue;
Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue; Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue;
Panel.StringInput[4].StringValue = UserCodeFeedback.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() private void SubscribeForMobileControlUpdates()
{ {
foreach (var dev in DeviceManager.AllDevices) foreach (var dev in DeviceManager.AllDevices)
@@ -519,8 +443,7 @@ namespace PepperDash.Essentials.Touchpanel
/// </summary> /// </summary>
public void SetAppUrl(string url) public void SetAppUrl(string url)
{ {
_appUrl = GetUrlWithCorrectIp(url); _appUrl = url;
AppUrlFeedback.FireUpdate(); AppUrlFeedback.FireUpdate();
} }

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@@ -42,14 +41,8 @@ namespace PepperDash.Essentials.WebSocketServer
private HttpServer _server; private HttpServer _server;
/// <summary>
/// Gets the HttpServer instance
/// </summary>
public HttpServer Server => _server; public HttpServer Server => _server;
/// <summary>
/// Gets the collection of UI client contexts
/// </summary>
public Dictionary<string, UiClientContext> UiClients { get; private set; } public Dictionary<string, UiClientContext> UiClients { get; private set; }
private readonly MobileControlSystemController _parent; private readonly MobileControlSystemController _parent;
@@ -68,20 +61,17 @@ 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 readonly System.Net.IPAddress csIpAddress; private System.Net.IPAddress csIpAddress;
private readonly System.Net.IPAddress csSubnetMask; private System.Net.IPAddress csSubnetMask;
/// <summary> /// <summary>
/// The path for the WebSocket messaging /// The path for the WebSocket messaging
/// </summary> /// </summary>
private readonly string _wsPath = "/mc/api/ui/join/"; private readonly string _wsPath = "/mc/api/ui/join/";
/// <summary>
/// Gets the WebSocket path
/// </summary>
public string WsPath => _wsPath; public string WsPath => _wsPath;
/// <summary> /// <summary>
@@ -99,9 +89,6 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary> /// </summary>
public int Port { get; private set; } public int Port { get; private set; }
/// <summary>
/// Gets the user app URL prefix
/// </summary>
public string UserAppUrlPrefix public string UserAppUrlPrefix
{ {
get get
@@ -114,9 +101,6 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
/// <summary>
/// Gets the count of connected UI clients
/// </summary>
public int ConnectedUiClientsCount public int ConnectedUiClientsCount
{ {
get get
@@ -135,9 +119,6 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
/// <summary>
/// Initializes a new instance of the MobileControlWebsocketServer class.
/// </summary>
public MobileControlWebsocketServer(string key, int customPort, MobileControlSystemController parent) public MobileControlWebsocketServer(string key, int customPort, MobileControlSystemController parent)
: base(key) : base(key)
{ {
@@ -346,26 +327,17 @@ namespace PepperDash.Essentials.WebSocketServer
} }
string ip = processorIp; 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 = csIpAddress.ToString(); 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;
} }
var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
@@ -649,9 +621,6 @@ namespace PepperDash.Essentials.WebSocketServer
CrestronConsole.ConsoleCommandResponse($"Token: {token}"); CrestronConsole.ConsoleCommandResponse($"Token: {token}");
} }
/// <summary>
/// Validates the grant code against the room key
/// </summary>
public (string, string) ValidateGrantCode(string grantCode, string roomKey) public (string, string) ValidateGrantCode(string grantCode, string roomKey)
{ {
var bridge = _parent.GetRoomBridge(roomKey); var bridge = _parent.GetRoomBridge(roomKey);
@@ -665,9 +634,6 @@ namespace PepperDash.Essentials.WebSocketServer
return ValidateGrantCode(grantCode, bridge); return ValidateGrantCode(grantCode, bridge);
} }
/// <summary>
/// Validates the grant code against the room key
/// </summary>
public (string, string) ValidateGrantCode(string grantCode, MobileControlBridgeBase bridge) public (string, string) ValidateGrantCode(string grantCode, MobileControlBridgeBase bridge)
{ {
// TODO: Authenticate grant code passed in // TODO: Authenticate grant code passed in
@@ -689,9 +655,6 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
/// <summary>
/// Generates a new client token for the specified bridge
/// </summary>
public (string, string) GenerateClientToken(MobileControlBridgeBase bridge, string touchPanelKey = "") public (string, string) GenerateClientToken(MobileControlBridgeBase bridge, string touchPanelKey = "")
{ {
var key = Guid.NewGuid().ToString(); var key = Guid.NewGuid().ToString();

View File

@@ -1,5 +1,5 @@
using System;
using System.IO.Compression; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Crestron.SimplSharp; using Crestron.SimplSharp;
@@ -41,6 +41,29 @@ namespace PepperDash.Essentials
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); 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> /// <summary>
@@ -244,8 +267,6 @@ namespace PepperDash.Essentials
// _ = new ProcessorExtensionDeviceFactory(); // _ = new ProcessorExtensionDeviceFactory();
// _ = new MobileControlFactory(); // _ = new MobileControlFactory();
LoadAssets();
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration");
var filesReady = SetupFilesystem(); var filesReady = SetupFilesystem();
@@ -547,142 +568,5 @@ namespace PepperDash.Essentials
return false; 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

@@ -48,8 +48,7 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.157" /> <PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.90" />
<PackageReference Include="System.IO.Compression" Version="4.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" /> <ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />