Compare commits

..

60 Commits

Author SHA1 Message Date
Andrew Welker
d7f9c74b2f fix: modify volume messenger to start with IBasicVolumeControls 2025-09-23 13:39:03 -05:00
Andrew Welker
5921b5dbb0 Merge pull request #1336 from PepperDash/hotfix/check-for-cs-lan-on-mctp
Hotfix/check for cs lan on mctp
2025-09-23 13:58:15 -04:00
Neil Dorin
ba0de5128f Update src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-23 11:56:57 -06:00
Neil Dorin
258699fbcd fix: Enhance AppUrl change logging
Updated logging to include the new AppUrl value when it changes. This provides better context in the logs, making it easier to track the specific URL that was set.
2025-09-18 18:23:12 -06:00
Neil Dorin
738504e9fc fix: Add error handling for network parameter retrieval
Introduced a try-catch block to handle exceptions when fetching the Crestron Ethernet adapter's ID, subnet mask, and IP address. Added logging for cases where the processor lacks a CS LAN. Also included a new using directive for Serilog.Events.
2025-09-18 14:48:21 -06:00
erikdred
cae1bbd6e6 Merge pull request #1332 from PepperDash/feature/add-hide-property-to-room-combine-scenario
feat: Add HideInUi property to room combiner classes
2025-09-17 18:38:21 -04:00
Neil Dorin
dea4407e3e Update src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-17 16:36:33 -06:00
Neil Dorin
ab08a546f7 feat: Add HideInUi property to room combiner classes
- Introduced `HideInUi` property in `EssentialsRoomCombinerPropertiesConfig` to control UI visibility.
- Added `HideInUi` interface property in `IEssentialsRoomCombiner`.
- Implemented `HideInUi` in `RoomCombinationScenario`, along with new `Key` and `Name` properties for improved data representation.
2025-09-17 15:25:23 -06:00
Neil Dorin
ef98d47792 Merge pull request #1331 from PepperDash/dev-list-fix 2025-09-17 08:36:04 -06:00
Andrew Welker
c05976ee60 fix: modify formatting and sorting for devfb response 2025-09-17 09:28:04 -05:00
Andrew Welker
4ca1031bef docs: fix CS1587 errors 2025-09-17 08:52:44 -05:00
Andrew Welker
6d61c4525e fix: use ConsoleCommandResponse for device feedbacks 2025-09-17 08:52:29 -05:00
Andrew Welker
3db274ace5 fix: add line endings for devlist console command 2025-09-17 08:51:45 -05:00
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
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
bb4b2f88b6 Merge pull request #1304 from PepperDash/temp-to-dev
Temp to dev
2025-08-05 16:39:38 -05:00
Andrew Welker
a41aba1904 Merge pull request #1300 from PepperDash/temp-to-dev
Temp to dev
2025-07-28 11:54:11 -05:00
Neil Dorin
b47f1d6b77 Merge pull request #1293 from PepperDash/to-dev 2025-07-23 07:53:44 -06:00
Andrew Welker
fda4a5a816 Merge pull request #1282 from PepperDash/main
Update Dev
2025-07-04 10:45:35 -05:00
Andrew Welker
9f70e3c721 Merge pull request #1276 from PepperDash/temp-to-dev
Temp to dev
2025-06-26 14:16:36 -04:00
Andrew Welker
ec6aeb17f6 Merge pull request #1271 from PepperDash/temp-to-dev
Temp to dev
2025-05-19 16:52:45 -05:00
Neil Dorin
91dc655103 Merge pull request #1269 from PepperDash/camera-preset-fix 2025-05-14 09:42:11 -06:00
Andrew Welker
bf31fb10eb fix: use correct join for preset select 2025-05-14 10:39:42 -05:00
Neil Dorin
5e21bad596 Merge pull request #1266 from PepperDash/temp-to-dev 2025-05-02 12:23:51 -06:00
Neil Dorin
2368f0c8cc Merge pull request #1262 from PepperDash/temp-to-dev 2025-04-28 11:37:01 -06:00
Neil Dorin
be58a0bc29 Merge pull request #1260 from PepperDash/temp-to-dev 2025-04-24 09:32:32 -06:00
Neil Dorin
8406f69e0d Merge pull request #1257 from PepperDash/temp-to-dev 2025-04-18 11:46:18 -06:00
Andrew Welker
116d83394a Merge pull request #1255 from PepperDash/temp-to-dev
Temp to dev
2025-04-14 11:15:27 -05:00
Neil Dorin
9b8e452eb4 Merge pull request #1251 from PepperDash/temp-to-dev 2025-04-11 14:00:02 -06:00
Neil Dorin
c9d86bd5dd Merge pull request #1248 from PepperDash/temp-to-dev 2025-04-11 11:55:35 -06:00
Neil Dorin
b2b257020f Merge pull request #1246 from PepperDash/temp-to-dev 2025-04-08 13:48:10 -06:00
Neil Dorin
6f58e18d14 Merge pull request #1244 from PepperDash/temp-to-dev
Temp to dev
2025-04-04 10:30:10 -06:00
Neil Dorin
60fc0298ec Merge pull request #1242 from PepperDash/temp-to-dev
Temp to dev
2025-04-02 11:14:45 -06:00
29 changed files with 1313 additions and 921 deletions

1
.gitignore vendored
View File

@@ -395,3 +395,4 @@ 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.12.1-local</Version> <Version>2.15.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,6 +74,10 @@ 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

@@ -2,14 +2,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp;
using System.Reflection; using System.Reflection;
using Crestron.SimplSharp;
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;
@@ -355,22 +353,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; }
@@ -379,22 +377,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; }
} }
@@ -403,22 +401,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,6 +7,13 @@ 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

@@ -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 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;
@@ -56,6 +54,9 @@ 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;
@@ -107,8 +108,7 @@ 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
var comDev = comm as Device; if (comm is Device comDev)
if (comDev != null)
DeviceManager.AddDevice(comDev); DeviceManager.AddDevice(comDev);
return comm; return comm;
} }

View File

@@ -19,5 +19,11 @@ 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,6 +1,7 @@
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
{ {
@@ -11,7 +12,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 public interface INvxNetworkPortInformation : IKeyed
{ {
/// <summary> /// <summary>
/// Occurs when the port information changes. /// Occurs when the port information changes.

View File

@@ -202,14 +202,15 @@ namespace PepperDash.Essentials.Core
private static void ListDevices(string s) 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:\r\n");
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 : "---";
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name); CrestronConsole.ConsoleCommandResponse($" [{d.Key}] {name}\r\n");
} }
} }
@@ -218,28 +219,17 @@ namespace PepperDash.Essentials.Core
var dev = GetDeviceForKey(devKey); var dev = GetDeviceForKey(devKey);
if (dev == null) if (dev == null)
{ {
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); CrestronConsole.ConsoleCommandResponse($"Device '{devKey}' not found\r\n");
return; return;
} }
if (!(dev is IHasFeedback statusDev)) if (!(dev is IHasFeedback statusDev))
{ {
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey); CrestronConsole.ConsoleCommandResponse($"Device '{devKey}' does not have visible feedbacks\r\n");
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)
{ {
@@ -249,12 +239,6 @@ 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,16 +3,29 @@ 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,47 +19,72 @@ 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)
{ {
Debug.LogMessage(LogEventLevel.Information, source, "\n\nAvailable feedbacks:"); CrestronConsole.ConsoleCommandResponse("No available feedbacks\r\n");
foreach (var f in feedbacks) return;
}
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 val = ""; string value = "";
string type = ""; string type = "";
if (getCurrentStates) if (getCurrentStates)
{ {
if (f is BoolFeedback) if (feedback is BoolFeedback)
{ {
val = f.BoolValue.ToString(); value = feedback.BoolValue.ToString();
type = "boolean"; type = "boolean";
} }
else if (f is IntFeedback) else if (feedback is IntFeedback)
{ {
val = f.IntValue.ToString(); value = feedback.IntValue.ToString();
type = "integer"; type = "integer";
} }
else if (f is StringFeedback) else if (feedback is StringFeedback)
{ {
val = f.StringValue; value = feedback.StringValue;
type = "string"; type = "string";
} }
} }
Debug.LogMessage(LogEventLevel.Information, "{0,-12} {1, -25} {2}", type, CrestronConsole.ConsoleCommandResponse($" {type,-12} {(string.IsNullOrEmpty(feedback.Key) ? "-no key-" : feedback.Key),-25} {value}\r\n");
(string.IsNullOrEmpty(f.Key) ? "-no key-" : f.Key), val); }
}
}
else
Debug.LogMessage(LogEventLevel.Information, source, "No available outputs:");
} }
} }
} }

View File

@@ -103,6 +103,12 @@ namespace PepperDash.Essentials.Core
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to hide this scenario in the UI.
/// </summary>
[JsonProperty("hideInUi", NullValueHandling = NullValueHandling.Ignore)]
public bool HideInUi { get; set; }
/// <summary> /// <summary>
/// Gets or sets the collection of partition states. /// Gets or sets the collection of partition states.
/// </summary> /// </summary>

View File

@@ -109,6 +109,12 @@ namespace PepperDash.Essentials.Core
[JsonProperty("isActive")] [JsonProperty("isActive")]
bool IsActive { get; } bool IsActive { get; }
/// <summary>
/// Gets a value indicating whether this scenario should be hidden in the UI.
/// </summary>
[JsonProperty("hideInUi")]
bool HideInUi { get; }
/// <summary> /// <summary>
/// Activates this room combination scenario /// Activates this room combination scenario
/// </summary> /// </summary>

View File

@@ -14,18 +14,40 @@ namespace PepperDash.Essentials.Core
{ {
private RoomCombinationScenarioConfig _config; private RoomCombinationScenarioConfig _config;
/// <summary>
/// Gets or sets the key associated with the object.
/// </summary>
[JsonProperty("key")] [JsonProperty("key")]
public string Key { get; set; } public string Key { get; set; }
/// <summary>
/// Gets or sets the name associated with the object.
/// </summary>
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("partitionStates")] /// <summary>
/// Gets a value indicating whether to hide this scenario in the UI.
/// </summary>
///
[JsonProperty("hideInUi")]
public bool HideInUi
{
get { return _config.HideInUi; }
}
/// <summary> /// <summary>
/// Gets or sets the PartitionStates /// Gets or sets the PartitionStates
/// </summary> /// </summary>
///
[JsonProperty("partitionStates")]
public List<PartitionState> PartitionStates { get; private set; } public List<PartitionState> PartitionStates { get; private set; }
/// <summary>
/// Determines which UI devices get mapped to which room in this scenario. The Key should be the key of the UI device and the Value should be the key of the room to map to
/// </summary>
[JsonProperty("uiMap")] [JsonProperty("uiMap")]
public Dictionary<string, string> UiMap { get; set; } public Dictionary<string, string> UiMap { get; set; }

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

@@ -1,221 +0,0 @@
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,17 +140,19 @@ namespace PepperDash.Essentials.AppServer.Messengers
if (Camera is IHasCameraPresets presetsCamera) 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; return;
} }
timerHandler(state.Value, cameraAction); timerHandler(Camera.Key, cameraAction);
cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase));
} }
/// <summary> /// <summary>

View File

@@ -1,9 +1,10 @@
using Newtonsoft.Json; using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using System;
namespace PepperDash.Essentials.AppServer.Messengers namespace PepperDash.Essentials.AppServer.Messengers
{ {
@@ -12,35 +13,46 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary> /// </summary>
public class DeviceVolumeMessenger : MessengerBase public class DeviceVolumeMessenger : MessengerBase
{ {
private readonly IBasicVolumeWithFeedback _localDevice; private readonly IBasicVolumeControls device;
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeWithFeedback device) /// <summary>
/// Initializes a new instance of the <see cref="DeviceVolumeMessenger"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="messagePath">The message path.</param>
/// <param name="device">The device.</param>
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device)
: base(key, messagePath, device as IKeyName) : base(key, messagePath, device as IKeyName)
{ {
_localDevice = device; this.device = device;
} }
private void SendStatus() private void SendStatus(string id = null)
{ {
try try
{ {
if (!(device is IBasicVolumeWithFeedback feedbackDevice))
{
return;
}
var messageObj = new VolumeStateMessage var messageObj = new VolumeStateMessage
{ {
Volume = new Volume Volume = new Volume
{ {
Level = _localDevice?.VolumeLevelFeedback.IntValue ?? -1, Level = feedbackDevice?.VolumeLevelFeedback.IntValue ?? -1,
Muted = _localDevice?.MuteFeedback.BoolValue ?? false, Muted = feedbackDevice?.MuteFeedback.BoolValue ?? false,
HasMute = true, // assume all devices have mute for now HasMute = true, // assume all devices have mute for now
} }
}; };
if (_localDevice is IBasicVolumeWithFeedbackAdvanced volumeAdvanced) if (device is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
{ {
messageObj.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString(); messageObj.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString();
messageObj.Volume.Units = volumeAdvanced.Units; messageObj.Volume.Units = volumeAdvanced.Units;
} }
PostStatusMessage(messageObj); PostStatusMessage(messageObj, id);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -53,36 +65,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
protected override void RegisterActions() protected override void RegisterActions()
{ {
AddAction("/fullStatus", (id, content) => SendStatus());
AddAction("/level", (id, content) =>
{
var volume = content.ToObject<MobileControlSimpleContent<ushort>>();
_localDevice.SetVolume(volume.Value);
});
AddAction("/muteToggle", (id, content) =>
{
_localDevice.MuteToggle();
});
AddAction("/muteOn", (id, content) =>
{
_localDevice.MuteOn();
});
AddAction("/muteOff", (id, content) =>
{
_localDevice.MuteOff();
});
AddAction("/volumeUp", (id, content) => PressAndHoldHandler.HandlePressAndHold(DeviceKey, content, (b) => AddAction("/volumeUp", (id, content) => PressAndHoldHandler.HandlePressAndHold(DeviceKey, content, (b) =>
{ {
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Calling {localDevice} volume up with {value}", DeviceKey, b); Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Calling {localDevice} volume up with {value}", DeviceKey, b);
try try
{ {
_localDevice.VolumeUp(b); device.VolumeUp(b);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -90,7 +78,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
})); }));
AddAction("/muteToggle", (id, content) =>
{
device.MuteToggle();
});
AddAction("/volumeDown", (id, content) => PressAndHoldHandler.HandlePressAndHold(DeviceKey, content, (b) => AddAction("/volumeDown", (id, content) => PressAndHoldHandler.HandlePressAndHold(DeviceKey, content, (b) =>
{ {
@@ -98,7 +89,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
try try
{ {
_localDevice.VolumeDown(b); device.VolumeDown(b);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -106,7 +97,38 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
})); }));
_localDevice.MuteFeedback.OutputChange += (sender, args) => if (!(device is IBasicVolumeWithFeedback feedback))
{
this.LogDebug("Skipping feedback methods for {deviceKey}", (device as IKeyName)?.Key);
return;
}
AddAction("/fullStatus", (id, content) => SendStatus(id));
AddAction("/volumeStatus", (id, content) => SendStatus(id));
AddAction("/level", (id, content) =>
{
var volume = content.ToObject<MobileControlSimpleContent<ushort>>();
feedback.SetVolume(volume.Value);
});
AddAction("/muteOn", (id, content) =>
{
feedback.MuteOn();
});
AddAction("/muteOff", (id, content) =>
{
feedback.MuteOff();
});
feedback.MuteFeedback.OutputChange += (sender, args) =>
{ {
PostStatusMessage(JToken.FromObject( PostStatusMessage(JToken.FromObject(
new new
@@ -119,10 +141,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
); );
}; };
_localDevice.VolumeLevelFeedback.OutputChange += (sender, args) => feedback.VolumeLevelFeedback.OutputChange += (sender, args) =>
{ {
var rawValue = ""; var rawValue = "";
if (_localDevice is IBasicVolumeWithFeedbackAdvanced volumeAdvanced) if (feedback is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
{ {
rawValue = volumeAdvanced.RawVolumeLevel.ToString(); rawValue = volumeAdvanced.RawVolumeLevel.ToString();
} }
@@ -138,8 +160,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
PostStatusMessage(JToken.FromObject(message)); PostStatusMessage(JToken.FromObject(message));
}; };
} }
#endregion #endregion

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

@@ -1,6 +1,6 @@
using Newtonsoft.Json; using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using System.Collections.Generic;
namespace PepperDash.Essentials namespace PepperDash.Essentials
{ {
@@ -9,25 +9,34 @@ 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;
} }
@@ -36,27 +45,42 @@ 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();
@@ -68,26 +92,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>
@@ -95,16 +119,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; }
} }
@@ -113,53 +137,71 @@ 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; }
} }
@@ -168,22 +210,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; }
} }
@@ -192,21 +234,22 @@ 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; }
} }
@@ -215,8 +258,19 @@ 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,7 +27,6 @@ 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;
@@ -506,6 +505,25 @@ 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;
@@ -561,21 +579,6 @@ 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

@@ -2,6 +2,8 @@
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;
@@ -14,6 +16,7 @@ using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.DeviceInfo; using PepperDash.Essentials.Core.DeviceInfo;
using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Core.UI; using PepperDash.Essentials.Core.UI;
using Serilog.Events;
using Feedback = PepperDash.Essentials.Core.Feedback; using Feedback = PepperDash.Essentials.Core.Feedback;
namespace PepperDash.Essentials.Touchpanel namespace PepperDash.Essentials.Touchpanel
@@ -106,6 +109,11 @@ 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>
@@ -182,6 +190,28 @@ namespace PepperDash.Essentials.Touchpanel
}; };
RegisterForExtenders(); RegisterForExtenders();
try
{
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);
}
catch (ArgumentException)
{
Debug.LogInformation("This processor does not have a CS LAN", this);
}
catch (InvalidOperationException)
{
Debug.LogInformation("This processor does not have a CS LAN", this);
}
catch (Exception ex)
{
Debug.LogError($"Unexpected exception when checking CS LAN: {ex}", this);
}
} }
/// <summary> /// <summary>
@@ -381,19 +411,81 @@ 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.OnlineStatusChange += (sender, args) => Panel.IpInformationChange += (sender, args) =>
{ {
UpdateFeedbacks(); if (args.Connected)
{
this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress);
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
Panel.StringInput[1].StringValue = 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) =>
{
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
UpdateFeedbacks();
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)
@@ -426,7 +518,7 @@ namespace PepperDash.Essentials.Touchpanel
_bridge.UserCodeChanged += UpdateFeedbacks; _bridge.UserCodeChanged += UpdateFeedbacks;
_bridge.AppUrlChanged += (s, a) => _bridge.AppUrlChanged += (s, a) =>
{ {
this.LogInformation("AppURL changed"); this.LogInformation("AppURL changed: {appURL}", _bridge.AppUrl);
SetAppUrl(_bridge.AppUrl); SetAppUrl(_bridge.AppUrl);
UpdateFeedbacks(s, a); UpdateFeedbacks(s, a);
}; };
@@ -443,7 +535,8 @@ namespace PepperDash.Essentials.Touchpanel
/// </summary> /// </summary>
public void SetAppUrl(string url) public void SetAppUrl(string url)
{ {
_appUrl = url; _appUrl = GetUrlWithCorrectIp(url);
AppUrlFeedback.FireUpdate(); AppUrlFeedback.FireUpdate();
} }

View File

@@ -1,5 +1,6 @@
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;
@@ -41,8 +42,14 @@ 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;
@@ -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> /// <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>
@@ -89,6 +99,9 @@ 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
@@ -101,6 +114,9 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
/// <summary>
/// Gets the count of connected UI clients
/// </summary>
public int ConnectedUiClientsCount public int ConnectedUiClientsCount
{ {
get 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) public MobileControlWebsocketServer(string key, int customPort, MobileControlSystemController parent)
: base(key) : base(key)
{ {
@@ -327,17 +346,26 @@ 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 = crestronTouchpanel.ConnectedIps.Any(ipInfo => ip = csIpAddress.ToString();
{
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}";
@@ -621,6 +649,9 @@ 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);
@@ -634,6 +665,9 @@ 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
@@ -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 = "") 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; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Crestron.SimplSharp; using Crestron.SimplSharp;
@@ -41,29 +41,6 @@ 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>
@@ -267,6 +244,8 @@ 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();
@@ -568,5 +547,142 @@ 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

@@ -49,6 +49,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.90" /> <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" />