mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-08-31 19:08:29 +00:00
Compare commits
16 commits
v2.40.0-fe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f05654638 | ||
|
|
4ca59e3186 | ||
|
|
a55e4811b3 | ||
|
|
5cac0bb6c8 | ||
|
|
44e5ff4c3e | ||
|
|
ec9753b19e | ||
|
|
12e5ff752d | ||
|
|
85a4b635f6 | ||
|
|
aff4c1e897 | ||
|
|
3afc73a446 | ||
|
|
3c9f5e95e0 | ||
|
|
f70c78bcc5 | ||
|
|
5b5619764f | ||
|
|
d21cef0e60 | ||
|
|
1719de195f | ||
|
|
6949253b1a |
19 changed files with 146 additions and 564 deletions
32
README.md
32
README.md
|
|
@ -54,38 +54,6 @@ Utilization of Essentials Framework falls into the following categories:
|
|||
|
||||
For detailed documentation, see the [Wiki](https://github.com/PepperDash/EssentialsFramework/wiki).
|
||||
|
||||
## Room Combiner Operation Lifecycle
|
||||
|
||||
Essentials room combiner now exposes an operation lifecycle that clients can use to show progress and terminal status during scenario changes.
|
||||
|
||||
Lifecycle states:
|
||||
- Idle
|
||||
- InProgress
|
||||
- Completed
|
||||
- Failed
|
||||
- TimedOut
|
||||
|
||||
The lifecycle is exposed on room combiners that implement the operation-status extension and is included in app server combiner messages under the combinationOperation payload.
|
||||
|
||||
Configuration:
|
||||
- combinationOperationTimeoutSeconds (optional): timeout in seconds for an in-progress operation.
|
||||
- Default when omitted: 300 seconds.
|
||||
- Values less than or equal to 0 are treated as invalid and fall back to the default.
|
||||
|
||||
Example room combiner properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"defaultToManualMode": false,
|
||||
"defaultScenarioKey": "divided",
|
||||
"combinationOperationTimeoutSeconds": 30,
|
||||
"roomKeys": ["roomA", "roomB"]
|
||||
}
|
||||
```
|
||||
|
||||
Operational note:
|
||||
- When an operation reaches a terminal state (Completed, Failed, TimedOut), the operation timeout timer is stopped.
|
||||
|
||||
## Support
|
||||
|
||||
* Check out our [Discord Server](https://discord.gg/rWyeRH3K)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>2.36.6-local</Version>
|
||||
<Version>2.42.1-local</Version>
|
||||
<InformationalVersion>$(Version)</InformationalVersion>
|
||||
<Authors>PepperDash Technology</Authors>
|
||||
<Company>PepperDash Technology</Company>
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ namespace PepperDash.Core
|
|||
// Thread-safety lock for state changes
|
||||
private readonly object _stateLock = new object();
|
||||
|
||||
private volatile bool _isProgramStopping;
|
||||
|
||||
private bool disconnectLogged = false;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -207,13 +209,11 @@ namespace PepperDash.Core
|
|||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
_isProgramStopping = true;
|
||||
this.LogDebug("Program stopping. Closing connection");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect method
|
||||
|
|
@ -228,6 +228,12 @@ namespace PepperDash.Core
|
|||
return;
|
||||
}
|
||||
|
||||
if (_isProgramStopping)
|
||||
{
|
||||
this.LogDebug("Skipping connect because program is stopping");
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectEnabled = true;
|
||||
|
||||
try
|
||||
|
|
@ -287,13 +293,7 @@ namespace PepperDash.Core
|
|||
}
|
||||
catch (SshConnectionException e)
|
||||
{
|
||||
var ie = e.InnerException; // The details are inside!!
|
||||
|
||||
if (ie is SocketException)
|
||||
{
|
||||
this.LogError("CONNECTION failure: Cannot reach host");
|
||||
this.LogVerbose(ie, "Exception details: ");
|
||||
}
|
||||
var ie = e.InnerException; // The details are inside, when present - remote can close the connection with no inner exception at all
|
||||
|
||||
if (ie is System.Net.Sockets.SocketException socketException)
|
||||
{
|
||||
|
|
@ -301,20 +301,20 @@ namespace PepperDash.Core
|
|||
Hostname, Port);
|
||||
this.LogVerbose(socketException, "SocketException details: ");
|
||||
}
|
||||
if (ie is SshAuthenticationException)
|
||||
else if (ie is SshAuthenticationException)
|
||||
{
|
||||
this.LogError("Authentication failure for username {userName}", Username);
|
||||
this.LogVerbose(ie, "AuthenticationException details: ");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogError("Error on connect: {error}", ie.Message);
|
||||
this.LogVerbose(ie, "Exception details: ");
|
||||
this.LogError("Error on connect: {error}", ie?.Message ?? e.Message);
|
||||
this.LogVerbose(ie ?? e, "Exception details: ");
|
||||
}
|
||||
|
||||
disconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
|
|
@ -326,7 +326,7 @@ namespace PepperDash.Core
|
|||
|
||||
disconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
|
|
@ -338,7 +338,7 @@ namespace PepperDash.Core
|
|||
this.LogVerbose(e, "Exception details: ");
|
||||
disconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
|
|
@ -473,7 +473,7 @@ namespace PepperDash.Core
|
|||
{
|
||||
connectLock.Release();
|
||||
}
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
|
|
@ -516,8 +516,11 @@ namespace PepperDash.Core
|
|||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
|
||||
{
|
||||
StartReconnectTimer();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending text: '{message}'", text);
|
||||
|
|
@ -549,8 +552,11 @@ namespace PepperDash.Core
|
|||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
|
||||
{
|
||||
StartReconnectTimer();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.90" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.274" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
return _partitionSensor?.PartitionPresentFeedback?.BoolValue ?? _partitionPresent;
|
||||
return _partitionSensor.PartitionPresentFeedback.BoolValue;
|
||||
}
|
||||
|
||||
return _partitionPresent;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<DocumentationFile>bin\$(Configuration)\PepperDash_Essentials_Core.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.274" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Crestron\CrestronGenericBaseDevice.cs.orig" />
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Serilog.Events;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -18,7 +17,7 @@ namespace PepperDash.Essentials.Core
|
|||
/// combinations based on partition states and predefined scenarios. It supports both automatic and manual modes
|
||||
/// for managing room combinations. In automatic mode, the device determines the current room combination scenario
|
||||
/// based on partition sensor states. In manual mode, scenarios can be set explicitly by the user.</remarks>
|
||||
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombinerWithOperationStatus
|
||||
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
|
||||
{
|
||||
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
|
||||
|
||||
|
|
@ -76,29 +75,10 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
private CTimer _scenarioChangeDebounceTimer;
|
||||
|
||||
private CTimer _combinationOperationTimeoutTimer;
|
||||
|
||||
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
|
||||
|
||||
private const int DefaultCombinationOperationTimeoutSeconds = 300;
|
||||
|
||||
private int _combinationOperationTimeoutSeconds = DefaultCombinationOperationTimeoutSeconds;
|
||||
|
||||
private Mutex _scenarioChange = new Mutex();
|
||||
|
||||
private readonly object _combinationOperationLock = new object();
|
||||
|
||||
private readonly List<IKeyed> _operationStatusProviderDevices = new List<IKeyed>();
|
||||
|
||||
private string _pendingCompletionOperationId;
|
||||
|
||||
private string _pendingCompletionScenarioKey;
|
||||
|
||||
private CombinationOperationStatus _combinationOperation = new CombinationOperationStatus
|
||||
{
|
||||
State = CombinationOperationState.Idle
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EssentialsRoomCombiner"/> class, which manages room combination
|
||||
/// scenarios and partition states.
|
||||
|
|
@ -125,12 +105,6 @@ namespace PepperDash.Essentials.Core
|
|||
_scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds;
|
||||
}
|
||||
|
||||
if (_propertiesConfig.CombinationOperationTimeoutSeconds.HasValue
|
||||
&& _propertiesConfig.CombinationOperationTimeoutSeconds.Value > 0)
|
||||
{
|
||||
_combinationOperationTimeoutSeconds = _propertiesConfig.CombinationOperationTimeoutSeconds.Value;
|
||||
}
|
||||
|
||||
IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode);
|
||||
|
||||
// default to auto mode
|
||||
|
|
@ -159,8 +133,6 @@ namespace PepperDash.Essentials.Core
|
|||
// connected and initialized
|
||||
DeviceManager.AllDevicesInitialized += (o, a) =>
|
||||
{
|
||||
InitializeOperationStatusProviders();
|
||||
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
DetermineRoomCombinationScenario();
|
||||
|
|
@ -284,19 +256,13 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
|
||||
{
|
||||
|
||||
|
||||
if (newScenario == _currentScenario)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var operationId = SetCombinationOperationStatus(
|
||||
CombinationOperationState.InProgress,
|
||||
newScenario != null ? newScenario.Key : null,
|
||||
null,
|
||||
true);
|
||||
|
||||
try
|
||||
{
|
||||
// Deactivate the old scenario first
|
||||
if (_currentScenario != null)
|
||||
{
|
||||
|
|
@ -315,20 +281,7 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
RoomCombinationScenarioChanged?.Invoke(this, new EventArgs());
|
||||
|
||||
TryCompleteCombinationOperationIfReady(
|
||||
operationId,
|
||||
_currentScenario != null ? _currentScenario.Key : null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error changing room combination scenario");
|
||||
|
||||
TrySetCombinationOperationTerminalStatus(
|
||||
operationId,
|
||||
CombinationOperationState.Failed,
|
||||
newScenario != null ? newScenario.Key : null,
|
||||
"Combination operation failed");
|
||||
}
|
||||
}
|
||||
|
||||
#region IEssentialsRoomCombiner Members
|
||||
|
|
@ -340,11 +293,6 @@ namespace PepperDash.Essentials.Core
|
|||
/// changes. Subscribers can use this event to update their logic or UI based on the new scenario.</remarks>
|
||||
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the room combination operation status changes.
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> CombinationOperationStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current room combination scenario.
|
||||
/// </summary>
|
||||
|
|
@ -356,20 +304,6 @@ namespace PepperDash.Essentials.Core
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current room combination operation status.
|
||||
/// </summary>
|
||||
public CombinationOperationStatus CombinationOperation
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_combinationOperationLock)
|
||||
{
|
||||
return CloneCombinationOperationStatus(_combinationOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the IsInAutoModeFeedback
|
||||
/// </summary>
|
||||
|
|
@ -521,299 +455,6 @@ namespace PepperDash.Essentials.Core
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string SetCombinationOperationStatus(
|
||||
CombinationOperationState state,
|
||||
string scenarioKey,
|
||||
string message,
|
||||
bool resetStartedUtc)
|
||||
{
|
||||
string operationId;
|
||||
|
||||
lock (_combinationOperationLock)
|
||||
{
|
||||
if (resetStartedUtc)
|
||||
{
|
||||
_combinationOperation = new CombinationOperationStatus
|
||||
{
|
||||
OperationId = Guid.NewGuid().ToString(),
|
||||
ScenarioKey = scenarioKey,
|
||||
StartedUtc = DateTime.UtcNow.ToString("o"),
|
||||
State = state,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_combinationOperation.ScenarioKey = scenarioKey ?? _combinationOperation.ScenarioKey;
|
||||
_combinationOperation.State = state;
|
||||
_combinationOperation.Message = message;
|
||||
}
|
||||
|
||||
if (state == CombinationOperationState.InProgress)
|
||||
{
|
||||
_pendingCompletionOperationId = null;
|
||||
_pendingCompletionScenarioKey = null;
|
||||
}
|
||||
|
||||
operationId = _combinationOperation.OperationId;
|
||||
}
|
||||
|
||||
if (state == CombinationOperationState.InProgress)
|
||||
{
|
||||
StartCombinationOperationTimeout(operationId);
|
||||
}
|
||||
else if (state == CombinationOperationState.Completed
|
||||
|| state == CombinationOperationState.Failed
|
||||
|| state == CombinationOperationState.TimedOut
|
||||
|| state == CombinationOperationState.Idle)
|
||||
{
|
||||
StopCombinationOperationTimeout();
|
||||
}
|
||||
|
||||
CombinationOperationStatusChanged?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return operationId;
|
||||
}
|
||||
|
||||
private void TrySetCombinationOperationTerminalStatus(
|
||||
string operationId,
|
||||
CombinationOperationState state,
|
||||
string scenarioKey,
|
||||
string message)
|
||||
{
|
||||
var statusUpdated = false;
|
||||
|
||||
lock (_combinationOperationLock)
|
||||
{
|
||||
if (_combinationOperation == null
|
||||
|| !string.Equals(_combinationOperation.OperationId, operationId, StringComparison.Ordinal)
|
||||
|| _combinationOperation.State != CombinationOperationState.InProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_combinationOperation.ScenarioKey = scenarioKey ?? _combinationOperation.ScenarioKey;
|
||||
_combinationOperation.State = state;
|
||||
_combinationOperation.Message = message;
|
||||
_pendingCompletionOperationId = null;
|
||||
_pendingCompletionScenarioKey = null;
|
||||
statusUpdated = true;
|
||||
}
|
||||
|
||||
if (!statusUpdated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopCombinationOperationTimeout();
|
||||
CombinationOperationStatusChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void StartCombinationOperationTimeout(string operationId)
|
||||
{
|
||||
StopCombinationOperationTimeout();
|
||||
|
||||
if (_combinationOperationTimeoutSeconds <= 0 || string.IsNullOrEmpty(operationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_combinationOperationTimeoutTimer = new CTimer(
|
||||
_ => HandleCombinationOperationTimeout(operationId),
|
||||
_combinationOperationTimeoutSeconds * 1000);
|
||||
}
|
||||
|
||||
private void StopCombinationOperationTimeout()
|
||||
{
|
||||
if (_combinationOperationTimeoutTimer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_combinationOperationTimeoutTimer.Dispose();
|
||||
_combinationOperationTimeoutTimer = null;
|
||||
}
|
||||
|
||||
private void HandleCombinationOperationTimeout(string operationId)
|
||||
{
|
||||
TrySetCombinationOperationTerminalStatus(
|
||||
operationId,
|
||||
CombinationOperationState.TimedOut,
|
||||
null,
|
||||
"Combination operation timed out");
|
||||
}
|
||||
|
||||
private void InitializeOperationStatusProviders()
|
||||
{
|
||||
_operationStatusProviderDevices.Clear();
|
||||
|
||||
foreach (var device in DeviceManager.AllDevices)
|
||||
{
|
||||
if (IsOperationStatusProviderDevice(device))
|
||||
{
|
||||
_operationStatusProviderDevices.Add(device);
|
||||
SubscribeToOperationStatusProviderChanged(device);
|
||||
}
|
||||
}
|
||||
|
||||
this.LogDebug("Room combiner {combinerKey} found {providerCount} post-combination status provider(s)", Key, _operationStatusProviderDevices.Count);
|
||||
}
|
||||
|
||||
private static bool IsOperationStatusProviderDevice(object device)
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var type = device.GetType();
|
||||
|
||||
var roomCombinerKeyProperty = type.GetProperty("RoomCombinerKey", BindingFlags.Instance | BindingFlags.Public);
|
||||
var scenarioReconciledProperty = type.GetProperty("ScenarioReconciled", BindingFlags.Instance | BindingFlags.Public);
|
||||
var scenarioReconciledScenarioKeyProperty = type.GetProperty("ScenarioReconciledScenarioKey", BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
return roomCombinerKeyProperty != null
|
||||
&& roomCombinerKeyProperty.PropertyType == typeof(string)
|
||||
&& roomCombinerKeyProperty.CanRead
|
||||
&& scenarioReconciledProperty != null
|
||||
&& scenarioReconciledProperty.PropertyType == typeof(bool)
|
||||
&& scenarioReconciledProperty.CanRead
|
||||
&& scenarioReconciledScenarioKeyProperty != null
|
||||
&& scenarioReconciledScenarioKeyProperty.PropertyType == typeof(string)
|
||||
&& scenarioReconciledScenarioKeyProperty.CanRead;
|
||||
}
|
||||
|
||||
private void SubscribeToOperationStatusProviderChanged(object device)
|
||||
{
|
||||
var eventInfo = device.GetType().GetEvent("ScenarioReconciledChanged", BindingFlags.Instance | BindingFlags.Public);
|
||||
if (eventInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventInfo.EventHandlerType != typeof(EventHandler<EventArgs>))
|
||||
{
|
||||
this.LogDebug("Room combiner {combinerKey} skipping provider event subscription for {providerType}: unsupported event type {eventType}", Key, device.GetType().Name, eventInfo.EventHandlerType);
|
||||
return;
|
||||
}
|
||||
|
||||
eventInfo.AddEventHandler(device, new EventHandler<EventArgs>(OperationStatusProvider_ScenarioReconciledChanged));
|
||||
}
|
||||
|
||||
private void OperationStatusProvider_ScenarioReconciledChanged(object sender, EventArgs e)
|
||||
{
|
||||
TryCompletePendingCombinationOperation();
|
||||
}
|
||||
|
||||
private void TryCompletePendingCombinationOperation()
|
||||
{
|
||||
string operationId;
|
||||
string scenarioKey;
|
||||
|
||||
lock (_combinationOperationLock)
|
||||
{
|
||||
operationId = _pendingCompletionOperationId;
|
||||
scenarioKey = _pendingCompletionScenarioKey;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(operationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryCompleteCombinationOperationIfReady(operationId, scenarioKey);
|
||||
}
|
||||
|
||||
private void TryCompleteCombinationOperationIfReady(string operationId, string scenarioKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(operationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AreOperationStatusProvidersSatisfied(scenarioKey))
|
||||
{
|
||||
lock (_combinationOperationLock)
|
||||
{
|
||||
if (_combinationOperation != null
|
||||
&& string.Equals(_combinationOperation.OperationId, operationId, StringComparison.Ordinal)
|
||||
&& _combinationOperation.State == CombinationOperationState.InProgress)
|
||||
{
|
||||
_pendingCompletionOperationId = operationId;
|
||||
_pendingCompletionScenarioKey = scenarioKey;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TrySetCombinationOperationTerminalStatus(
|
||||
operationId,
|
||||
CombinationOperationState.Completed,
|
||||
scenarioKey,
|
||||
null);
|
||||
}
|
||||
|
||||
private bool AreOperationStatusProvidersSatisfied(string scenarioKey)
|
||||
{
|
||||
var matchingProviders = _operationStatusProviderDevices
|
||||
.Where(d => string.Equals(GetStringPropertyValue(d, "RoomCombinerKey"), Key, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
if (!matchingProviders.Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var provider in matchingProviders)
|
||||
{
|
||||
var providerScenarioReconciled = GetBoolPropertyValue(provider, "ScenarioReconciled");
|
||||
var providerScenarioKey = GetStringPropertyValue(provider, "ScenarioReconciledScenarioKey");
|
||||
|
||||
if (!providerScenarioReconciled
|
||||
|| !string.Equals(providerScenarioKey, scenarioKey, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetStringPropertyValue(object target, string propertyName)
|
||||
{
|
||||
var propertyInfo = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
|
||||
return propertyInfo != null ? propertyInfo.GetValue(target, null) as string : null;
|
||||
}
|
||||
|
||||
private static bool GetBoolPropertyValue(object target, string propertyName)
|
||||
{
|
||||
var propertyInfo = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
|
||||
if (propertyInfo == null || propertyInfo.PropertyType != typeof(bool))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool)propertyInfo.GetValue(target, null);
|
||||
}
|
||||
|
||||
private static CombinationOperationStatus CloneCombinationOperationStatus(CombinationOperationStatus status)
|
||||
{
|
||||
if (status == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CombinationOperationStatus
|
||||
{
|
||||
OperationId = status.OperationId,
|
||||
ScenarioKey = status.ScenarioKey,
|
||||
StartedUtc = status.StartedUtc,
|
||||
State = status.State,
|
||||
Message = status.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace PepperDash.Essentials.Core
|
|||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the system operates in automatic mode.
|
||||
/// <remarks>Some systems don't have partitions sensors, and shouldn't allow auto mode to be turned on. When this is true in the configuration,
|
||||
/// <remarks>Some systems don't have partitions sensors, and show shouldn't allow auto mode to be turned on. When this is true in the configuration,
|
||||
/// auto mode won't be allowed to be turned on.</remarks>
|
||||
/// </summary>
|
||||
[JsonProperty("disableAutoMode")]
|
||||
|
|
@ -54,13 +54,6 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
|
||||
public int ScenarioChangeDebounceTimeSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout, in seconds, for room combination operations.
|
||||
/// When null or less than or equal to zero, the default timeout is used.
|
||||
/// </summary>
|
||||
[JsonProperty("combinationOperationTimeoutSeconds", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int? CombinationOperationTimeoutSeconds { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -16,14 +16,12 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The current room combination scenario
|
||||
/// </summary>
|
||||
[JsonProperty("currentScenario")]
|
||||
IRoomCombinationScenario CurrentScenario { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When true, indicates the current mode is auto mode
|
||||
/// </summary>
|
||||
|
|
@ -87,56 +85,6 @@ namespace PepperDash.Essentials.Core
|
|||
void SetRoomCombinationScenario(string scenarioKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional extension for room combiners that provide operation lifecycle status.
|
||||
/// </summary>
|
||||
public interface IEssentialsRoomCombinerWithOperationStatus : IEssentialsRoomCombiner
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the room combination operation status has changed.
|
||||
/// </summary>
|
||||
event EventHandler<EventArgs> CombinationOperationStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current room combination operation status.
|
||||
/// </summary>
|
||||
[JsonProperty("combinationOperation")]
|
||||
CombinationOperationStatus CombinationOperation { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines lifecycle states for a room combination operation.
|
||||
/// </summary>
|
||||
public enum CombinationOperationState
|
||||
{
|
||||
Idle,
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed,
|
||||
TimedOut
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents room combination operation status details.
|
||||
/// </summary>
|
||||
public class CombinationOperationStatus
|
||||
{
|
||||
[JsonProperty("operationId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string OperationId { get; set; }
|
||||
|
||||
[JsonProperty("scenarioKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ScenarioKey { get; set; }
|
||||
|
||||
[JsonProperty("startedUtc", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string StartedUtc { get; set; }
|
||||
|
||||
[JsonProperty("state")]
|
||||
public CombinationOperationState State { get; set; }
|
||||
|
||||
[JsonProperty("message", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a scenario for combining rooms, including activation, deactivation, and associated state.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -368,6 +368,7 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
var key = args[1];
|
||||
|
||||
provider.SetSecret(key, "");
|
||||
response = provider.SetSecret(key, "")
|
||||
? $"Secret successfully deleted for {provider.Key}:{key}"
|
||||
: $"Unable to delete secret for {provider.Key}:{key}";
|
||||
|
|
|
|||
|
|
@ -190,7 +190,6 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
|||
if (Debug.WebsocketSink.HasActiveConnections)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward");
|
||||
StartPortForwardTimeout(port, csIp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,6 @@
|
|||
<ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.274" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -21,8 +21,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
{
|
||||
private readonly IEssentialsRoomCombiner _roomCombiner;
|
||||
|
||||
private readonly IEssentialsRoomCombinerWithOperationStatus _roomCombinerWithOperationStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IEssentialsRoomCombinerMessenger"/> class, which facilitates
|
||||
/// messaging for an <see cref="IEssentialsRoomCombiner"/> instance.
|
||||
|
|
@ -37,7 +35,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
: base(key, messagePath, roomCombiner as IKeyName)
|
||||
{
|
||||
_roomCombiner = roomCombiner;
|
||||
_roomCombinerWithOperationStatus = roomCombiner as IEssentialsRoomCombinerWithOperationStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -101,19 +98,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
SendFullStatus();
|
||||
};
|
||||
|
||||
if (_roomCombinerWithOperationStatus != null)
|
||||
{
|
||||
_roomCombinerWithOperationStatus.CombinationOperationStatusChanged += (sender, args) =>
|
||||
{
|
||||
var message = new
|
||||
{
|
||||
combinationOperation = _roomCombinerWithOperationStatus.CombinationOperation
|
||||
};
|
||||
|
||||
PostStatusMessage(JToken.FromObject(message));
|
||||
};
|
||||
}
|
||||
|
||||
_roomCombiner.IsInAutoModeFeedback.OutputChange += (sender, args) =>
|
||||
{
|
||||
var message = new
|
||||
|
|
@ -154,7 +138,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
DisableAutoMode = _roomCombiner.DisableAutoMode,
|
||||
IsInAutoMode = _roomCombiner.IsInAutoMode,
|
||||
CurrentScenario = _roomCombiner.CurrentScenario,
|
||||
CombinationOperation = _roomCombinerWithOperationStatus != null ? _roomCombinerWithOperationStatus.CombinationOperation : null,
|
||||
Rooms = rooms,
|
||||
RoomCombinationScenarios = _roomCombiner.RoomCombinationScenarios,
|
||||
Partitions = _roomCombiner.Partitions
|
||||
|
|
@ -211,12 +194,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
[JsonProperty("currentScenario", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public IRoomCombinationScenario CurrentScenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the room combination operation status.
|
||||
/// </summary>
|
||||
[JsonProperty("combinationOperation", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public CombinationOperationStatus CombinationOperation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of rooms associated with the entity.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
<Compile Remove="Messengers\SIMPLVtcMessenger.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.274" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<Compile Remove="RoomBridges\SourceDeviceMapDictionary.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.274" />
|
||||
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -25,11 +25,28 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
/// Mobile Control touchpanel controller that provides app control, Zoom integration,
|
||||
/// and mobile control functionality for Crestron touchpanels.
|
||||
/// </summary>
|
||||
public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme
|
||||
public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme, ICommunicationMonitor
|
||||
{
|
||||
private readonly MobileControlTouchpanelProperties localConfig;
|
||||
private IMobileControlRoomMessenger _bridge;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CommunicationMonitor tracking the panel's online/offline state
|
||||
/// </summary>
|
||||
public StatusMonitorBase CommunicationMonitor { get; private set; }
|
||||
|
||||
private sealed class NullCommunicationMonitor : StatusMonitorBase
|
||||
{
|
||||
public NullCommunicationMonitor(IKeyed parent) : base(parent, 120000, 300000)
|
||||
{
|
||||
Status = MonitorStatus.InError;
|
||||
Message = "Panel is not initialized";
|
||||
}
|
||||
|
||||
public override void Start() { }
|
||||
public override void Stop() { }
|
||||
}
|
||||
|
||||
private string _appUrl;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -128,6 +145,11 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
{
|
||||
localConfig = config;
|
||||
|
||||
if (panel != null)
|
||||
{
|
||||
CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, panel, 120000, 300000);
|
||||
}
|
||||
|
||||
AddPostActivationAction(SubscribeForMobileControlUpdates);
|
||||
|
||||
ThemeFeedback = new StringFeedback($"{Key}-theme", () => Theme);
|
||||
|
|
@ -366,6 +388,8 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
/// </summary>
|
||||
public override bool CustomActivate()
|
||||
{
|
||||
CommunicationMonitor?.Start();
|
||||
|
||||
var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this);
|
||||
|
||||
var zoomMessenger = new ITswZoomControlMessenger($"zoomControlMessenger-{Key}", $"/device/{Key}", this);
|
||||
|
|
@ -393,6 +417,17 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
return base.CustomActivate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the CommunicationMonitor on deactivation.
|
||||
/// </summary>
|
||||
/// <returns>True if deactivation was successful; otherwise, false.</returns>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
CommunicationMonitor?.Stop();
|
||||
|
||||
return base.Deactivate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles device extender signal changes for system reserved signals.
|
||||
/// </summary>
|
||||
|
|
@ -519,15 +554,8 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
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}");
|
||||
// replace the host but preserve whatever scheme (http/https) is already present in the URL
|
||||
var updatedUrl = Regex.Replace(url, @"^(https?)://[^:/]+", $"$1://{ip}");
|
||||
|
||||
this.LogVerbose("Updated URL: {updatedUrl}", updatedUrl);
|
||||
|
||||
|
|
@ -740,7 +768,7 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
/// </summary>
|
||||
public MobileControlTouchpanelControllerFactory()
|
||||
{
|
||||
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" };
|
||||
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mctsw1080", "mcts1080", "mcxpanel", "mcdge1000" };
|
||||
MinimumEssentialsFrameworkVersion = "2.0.0";
|
||||
|
||||
factories = new Dictionary<string, Func<uint, CrestronControlSystem, string, BasicTriListWithSmartObject>>
|
||||
|
|
@ -765,6 +793,8 @@ namespace PepperDash.Essentials.Touchpanel
|
|||
{"ts770", (id, controlSystem, projectName) => new Ts770(id, controlSystem)},
|
||||
{"tsw1070", (id, controlSystem, projectName) => new Tsw1070(id, controlSystem)},
|
||||
{"ts1070", (id, controlSystem, projectName) => new Ts1070(id, controlSystem)},
|
||||
{"tsw1080", (id, controlSystem, projectName) => new Tsw1080(id, controlSystem)},
|
||||
{"ts1080", (id, controlSystem, projectName) => new Ts1080(id, controlSystem)},
|
||||
{"dge1000", (id, controlSystem, projectName) => new Dge1000(id, controlSystem)}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,16 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP scheme to use for generated URLs, based on whether the direct server is configured as secure
|
||||
/// </summary>
|
||||
private string HttpScheme => _parent.Config.DirectServer.Secure ? "https" : "http";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the WebSocket scheme to use for generated URLs, based on whether the direct server is configured as secure
|
||||
/// </summary>
|
||||
private string WsScheme => _parent.Config.DirectServer.Secure ? "wss" : "ws";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user app URL prefix
|
||||
/// </summary>
|
||||
|
|
@ -134,7 +144,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
{
|
||||
get
|
||||
{
|
||||
return string.Format("http://{0}:{1}{2}?token=",
|
||||
return string.Format("{0}://{1}:{2}{3}?token=",
|
||||
HttpScheme,
|
||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||
Port,
|
||||
_userAppBaseHref);
|
||||
|
|
@ -273,7 +284,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
{
|
||||
base.Initialize();
|
||||
|
||||
_server = new HttpServer(Port, false);
|
||||
_server = new HttpServer(Port, _parent.Config.DirectServer.Secure);
|
||||
|
||||
_server.OnGet += Server_OnGet;
|
||||
|
||||
|
|
@ -291,7 +302,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
{
|
||||
ClientCertificateRequired = false,
|
||||
CheckCertificateRevocation = false,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11
|
||||
EnabledSslProtocols = SslProtocols.Tls12
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -403,11 +414,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
ip = csIpAddress.ToString();
|
||||
}
|
||||
|
||||
var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
|
||||
var appUrl = $"{HttpScheme}://{ip}:{Port}/mc/app?token={touchpanel.Key}";
|
||||
|
||||
this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key);
|
||||
|
||||
touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
|
||||
touchpanel.Touchpanel.SetAppUrl(appUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -487,7 +498,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
{
|
||||
var config = new MobileControlApplicationConfig
|
||||
{
|
||||
ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port),
|
||||
ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, Port),
|
||||
GatewayAppPath = "",
|
||||
LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png",
|
||||
EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false,
|
||||
|
|
@ -1098,6 +1109,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
res.StatusCode = 200;
|
||||
res.Close();
|
||||
|
||||
// remote log collector has no dedicated secure flag; keep it on http regardless of DirectServer.Secure
|
||||
var logRequest = new HttpRequestMessage(HttpMethod.Post, $"http://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs")
|
||||
{
|
||||
Content = new StringContent(body, Encoding.UTF8, "application/json"),
|
||||
|
|
@ -1150,6 +1162,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
var qp = req.QueryString;
|
||||
var token = qp["token"];
|
||||
|
||||
// Each join mints a single-use clientId; the panel webview must never replay a cached
|
||||
// response, or it reconnects forever with an already-consumed id (1008 loop).
|
||||
res.Headers.Add("Cache-Control", "no-store");
|
||||
res.Headers.Add("Pragma", "no-cache");
|
||||
|
||||
this.LogVerbose("Join Room Request with token: {token}", token);
|
||||
|
||||
byte[] body;
|
||||
|
|
@ -1213,8 +1230,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
this.LogVerbose("Assigning ClientId: {clientId} for token: {token} at {timestamp}", clientId, token, now);
|
||||
|
||||
// Construct WebSocket URL with clientId query parameter
|
||||
var wsProtocol = "ws";
|
||||
var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
|
||||
var wsUrl = $"{WsScheme}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
|
||||
|
||||
// Construct the response object
|
||||
JoinResponse jRes = new JoinResponse
|
||||
|
|
@ -1226,7 +1242,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
Config = _parent.GetConfigWithPluginVersion(),
|
||||
CodeExpires = new DateTime().AddYears(1),
|
||||
UserCode = bridge.UserCode,
|
||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||
UserAppUrl = string.Format("{0}://{1}:{2}/mc/app",
|
||||
HttpScheme,
|
||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||
Port),
|
||||
WebSocketUrl = wsUrl,
|
||||
|
|
@ -1251,6 +1268,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "application/json";
|
||||
res.Headers.Add("Cache-Control", "no-store");
|
||||
res.Headers.Add("Pragma", "no-cache");
|
||||
var version = new Version() { ServerVersion = _parent.GetConfigWithPluginVersion().RuntimeInfo.PluginVersion };
|
||||
var message = JsonConvert.SerializeObject(version);
|
||||
this.LogVerbose("{message}", message);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.90" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.274" />
|
||||
<PackageReference Include="System.IO.Compression" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue