mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-08-31 19:08:29 +00:00
Compare commits
14 commits
main
...
v2.37.0-fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b499097448 | ||
|
|
8e9dbb6f44 | ||
|
|
e623865c2a | ||
|
|
f85343d8fd | ||
|
|
fb424ed5ad | ||
|
|
9eacc508c2 | ||
|
|
13a4f8101e | ||
|
|
493cd91daa | ||
|
|
8a9c8b5535 | ||
|
|
0896327dfe | ||
|
|
ee971c4ce1 | ||
|
|
7ff4f29c11 | ||
|
|
e4cd3617a1 | ||
|
|
6f2231a8d4 |
10 changed files with 608 additions and 89 deletions
|
|
@ -72,6 +72,20 @@ namespace PepperDash.Core
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether there are active WebSocket connections.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasActiveConnections
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_httpsServer == null || !_httpsServer.IsListening) return false;
|
||||||
|
var service = _httpsServer.WebSocketServices[_path];
|
||||||
|
if (service == null) return false;
|
||||||
|
return service.Sessions.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private readonly ITextFormatter _textFormatter;
|
private readonly ITextFormatter _textFormatter;
|
||||||
|
|
||||||
|
|
@ -218,6 +232,8 @@ namespace PepperDash.Core
|
||||||
Debug.LogInformation("Starting Websocket Server on port: {0}", port);
|
Debug.LogInformation("Starting Websocket Server on port: {0}", port);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Start(port, CertPath, _certificatePassword);
|
Start(port, CertPath, _certificatePassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
using System;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the contract for IMobileControlMessenger
|
/// Obsolete: messengers are subscription based by default; use IMobileControlMessenger instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("This interface is obsolete and will be removed in a future version. All messengers are now subscription based.")]
|
||||||
public interface IMobileControlMessengerWithSubscriptions : IMobileControlMessenger
|
public interface IMobileControlMessengerWithSubscriptions : IMobileControlMessenger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Serilog.Events;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
@ -17,7 +18,7 @@ namespace PepperDash.Essentials.Core
|
||||||
/// combinations based on partition states and predefined scenarios. It supports both automatic and manual modes
|
/// 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
|
/// 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>
|
/// based on partition sensor states. In manual mode, scenarios can be set explicitly by the user.</remarks>
|
||||||
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
|
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombinerWithOperationStatus
|
||||||
{
|
{
|
||||||
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
|
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
|
||||||
|
|
||||||
|
|
@ -75,10 +76,29 @@ namespace PepperDash.Essentials.Core
|
||||||
|
|
||||||
private CTimer _scenarioChangeDebounceTimer;
|
private CTimer _scenarioChangeDebounceTimer;
|
||||||
|
|
||||||
|
private CTimer _combinationOperationTimeoutTimer;
|
||||||
|
|
||||||
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
|
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
|
||||||
|
|
||||||
|
private const int DefaultCombinationOperationTimeoutSeconds = 300;
|
||||||
|
|
||||||
|
private int _combinationOperationTimeoutSeconds = DefaultCombinationOperationTimeoutSeconds;
|
||||||
|
|
||||||
private Mutex _scenarioChange = new Mutex();
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="EssentialsRoomCombiner"/> class, which manages room combination
|
/// Initializes a new instance of the <see cref="EssentialsRoomCombiner"/> class, which manages room combination
|
||||||
/// scenarios and partition states.
|
/// scenarios and partition states.
|
||||||
|
|
@ -105,6 +125,12 @@ namespace PepperDash.Essentials.Core
|
||||||
_scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds;
|
_scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_propertiesConfig.CombinationOperationTimeoutSeconds.HasValue
|
||||||
|
&& _propertiesConfig.CombinationOperationTimeoutSeconds.Value > 0)
|
||||||
|
{
|
||||||
|
_combinationOperationTimeoutSeconds = _propertiesConfig.CombinationOperationTimeoutSeconds.Value;
|
||||||
|
}
|
||||||
|
|
||||||
IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode);
|
IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode);
|
||||||
|
|
||||||
// default to auto mode
|
// default to auto mode
|
||||||
|
|
@ -133,6 +159,8 @@ namespace PepperDash.Essentials.Core
|
||||||
// connected and initialized
|
// connected and initialized
|
||||||
DeviceManager.AllDevicesInitialized += (o, a) =>
|
DeviceManager.AllDevicesInitialized += (o, a) =>
|
||||||
{
|
{
|
||||||
|
InitializeOperationStatusProviders();
|
||||||
|
|
||||||
if (IsInAutoMode)
|
if (IsInAutoMode)
|
||||||
{
|
{
|
||||||
DetermineRoomCombinationScenario();
|
DetermineRoomCombinationScenario();
|
||||||
|
|
@ -256,13 +284,19 @@ namespace PepperDash.Essentials.Core
|
||||||
|
|
||||||
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
|
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
if (newScenario == _currentScenario)
|
if (newScenario == _currentScenario)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var operationId = SetCombinationOperationStatus(
|
||||||
|
CombinationOperationState.InProgress,
|
||||||
|
newScenario != null ? newScenario.Key : null,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
// Deactivate the old scenario first
|
// Deactivate the old scenario first
|
||||||
if (_currentScenario != null)
|
if (_currentScenario != null)
|
||||||
{
|
{
|
||||||
|
|
@ -281,7 +315,20 @@ namespace PepperDash.Essentials.Core
|
||||||
|
|
||||||
RoomCombinationScenarioChanged?.Invoke(this, new EventArgs());
|
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
|
#region IEssentialsRoomCombiner Members
|
||||||
|
|
@ -293,6 +340,11 @@ namespace PepperDash.Essentials.Core
|
||||||
/// changes. Subscribers can use this event to update their logic or UI based on the new scenario.</remarks>
|
/// changes. Subscribers can use this event to update their logic or UI based on the new scenario.</remarks>
|
||||||
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the room combination operation status changes.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> CombinationOperationStatusChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current room combination scenario.
|
/// Gets the current room combination scenario.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -304,6 +356,20 @@ namespace PepperDash.Essentials.Core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current room combination operation status.
|
||||||
|
/// </summary>
|
||||||
|
public CombinationOperationStatus CombinationOperation
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_combinationOperationLock)
|
||||||
|
{
|
||||||
|
return CloneCombinationOperationStatus(_combinationOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the IsInAutoModeFeedback
|
/// Gets or sets the IsInAutoModeFeedback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -455,6 +521,299 @@ namespace PepperDash.Essentials.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,13 @@ namespace PepperDash.Essentials.Core
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
|
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
|
||||||
public int ScenarioChangeDebounceTimeSeconds { get; set; }
|
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>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,14 @@ namespace PepperDash.Essentials.Core
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current room combination scenario
|
/// The current room combination scenario
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("currentScenario")]
|
[JsonProperty("currentScenario")]
|
||||||
IRoomCombinationScenario CurrentScenario { get; }
|
IRoomCombinationScenario CurrentScenario { get; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When true, indicates the current mode is auto mode
|
/// When true, indicates the current mode is auto mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -85,6 +87,56 @@ namespace PepperDash.Essentials.Core
|
||||||
void SetRoomCombinationScenario(string scenarioKey);
|
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>
|
/// <summary>
|
||||||
/// Represents a scenario for combining rooms, including activation, deactivation, and associated state.
|
/// Represents a scenario for combining rooms, including activation, deactivation, and associated state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
|
|
@ -296,19 +297,14 @@ namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
var secretPresent = provider.TestSecret(key);
|
var secretPresent = provider.TestSecret(key);
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key);
|
provider.LogVerbose("SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key);
|
||||||
|
|
||||||
if (!secretPresent)
|
if (!secretPresent)
|
||||||
return
|
return
|
||||||
String.Format(
|
$"Unable to update secret for {provider.Key}:{key} - Please use the 'SetSecret' command to modify it";
|
||||||
"Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to modify it");
|
|
||||||
var response = provider.SetSecret(key, secret)
|
var response = provider.SetSecret(key, secret)
|
||||||
? String.Format(
|
? $"Secret successfully set for {provider.Key}:{key}"
|
||||||
"Secret successfully set for {0}:{1}",
|
: $"Unable to set secret for {provider.Key}:{key}";
|
||||||
provider.Key, key)
|
|
||||||
: String.Format(
|
|
||||||
"Unable to set secret for {0}:{1}",
|
|
||||||
provider.Key, key);
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,19 +312,14 @@ namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
var secretPresent = provider.TestSecret(key);
|
var secretPresent = provider.TestSecret(key);
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key);
|
provider.LogVerbose("SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key);
|
||||||
|
|
||||||
if (secretPresent)
|
if (secretPresent)
|
||||||
return
|
return
|
||||||
String.Format(
|
$"Unable to set secret for {provider.Key}:{key} - Please use the 'UpdateSecret' command to modify it";
|
||||||
"Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it");
|
|
||||||
var response = provider.SetSecret(key, secret)
|
var response = provider.SetSecret(key, secret)
|
||||||
? String.Format(
|
? $"Secret successfully set for {provider.Key}:{key}"
|
||||||
"Secret successfully set for {0}:{1}",
|
: $"Unable to set secret for {provider.Key}:{key}";
|
||||||
provider.Key, key)
|
|
||||||
: String.Format(
|
|
||||||
"Unable to set secret for {0}:{1}",
|
|
||||||
provider.Key, key);
|
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -377,15 +368,10 @@ namespace PepperDash.Essentials.Core
|
||||||
|
|
||||||
var key = args[1];
|
var key = args[1];
|
||||||
|
|
||||||
|
|
||||||
provider.SetSecret(key, "");
|
provider.SetSecret(key, "");
|
||||||
response = provider.SetSecret(key, "")
|
response = provider.SetSecret(key, "")
|
||||||
? String.Format(
|
? $"Secret successfully deleted for {provider.Key}:{key}"
|
||||||
"Secret successfully deleted for {0}:{1}",
|
: $"Unable to delete secret for {provider.Key}:{key}";
|
||||||
provider.Key, key)
|
|
||||||
: String.Format(
|
|
||||||
"Unable to delete secret for {0}:{1}",
|
|
||||||
provider.Key, key);
|
|
||||||
CrestronConsole.ConsoleCommandResponse(response);
|
CrestronConsole.ConsoleCommandResponse(response);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugSessionRequestHandler : WebApiBaseRequestHandler
|
public class DebugSessionRequestHandler : WebApiBaseRequestHandler
|
||||||
{
|
{
|
||||||
|
private CTimer _portForwardTimeoutTimer;
|
||||||
|
private readonly object _timerLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -48,6 +51,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
||||||
|
|
||||||
var port = 0;
|
var port = 0;
|
||||||
|
string csIp = null;
|
||||||
|
|
||||||
if (!Debug.WebsocketSink.IsRunning)
|
if (!Debug.WebsocketSink.IsRunning)
|
||||||
{
|
{
|
||||||
|
|
@ -57,15 +61,18 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
// Start the WS Server
|
// Start the WS Server
|
||||||
Debug.WebsocketSink.StartServerAndSetPort(port);
|
Debug.WebsocketSink.StartServerAndSetPort(port);
|
||||||
Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose);
|
Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
// Attempt to forward the port to the CS LAN
|
// Attempt to get the CS LAN IP and forward the port
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(
|
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(
|
||||||
EthernetAdapterType.EthernetCSAdapter);
|
EthernetAdapterType.EthernetCSAdapter);
|
||||||
var csIp = CrestronEthernetHelper.GetEthernetParameter(
|
csIp = CrestronEthernetHelper.GetEthernetParameter(
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||||
|
|
||||||
|
if (port > 0)
|
||||||
|
{
|
||||||
var result = CrestronEthernetHelper.AddPortForwarding(
|
var result = CrestronEthernetHelper.AddPortForwarding(
|
||||||
(ushort)port, (ushort)port, csIp,
|
(ushort)port, (ushort)port, csIp,
|
||||||
CrestronEthernetHelper.ePortMapTransport.TCP);
|
CrestronEthernetHelper.ePortMapTransport.TCP);
|
||||||
|
|
@ -77,6 +84,8 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for debug websocket", port);
|
Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for debug websocket", port);
|
||||||
|
StartPortForwardTimeout(port, csIp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
|
|
@ -87,16 +96,17 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message);
|
Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var url = Debug.WebsocketSink.Url;
|
var url = Debug.WebsocketSink.Url;
|
||||||
|
|
||||||
object data = new
|
var data = new
|
||||||
{
|
{
|
||||||
url = Debug.WebsocketSink.Url
|
url = Debug.WebsocketSink.Url,
|
||||||
|
fallbackUrl = csIp != null ? url.Replace(csIp, ip) : null
|
||||||
};
|
};
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url);
|
Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url);
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "Fallback Debug Session URL: {0}", data.fallbackUrl);
|
||||||
|
|
||||||
// Return the port number with the full url of the WS Server
|
// Return the port number with the full url of the WS Server
|
||||||
var res = JsonConvert.SerializeObject(data);
|
var res = JsonConvert.SerializeObject(data);
|
||||||
|
|
@ -120,6 +130,8 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
protected override void HandlePost(HttpCwsContext context)
|
protected override void HandlePost(HttpCwsContext context)
|
||||||
{
|
{
|
||||||
|
CancelPortForwardTimeout();
|
||||||
|
|
||||||
var port = Debug.WebsocketSink.Port;
|
var port = Debug.WebsocketSink.Port;
|
||||||
|
|
||||||
Debug.WebsocketSink.StopServer();
|
Debug.WebsocketSink.StopServer();
|
||||||
|
|
@ -132,6 +144,12 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
var csIp = CrestronEthernetHelper.GetEthernetParameter(
|
var csIp = CrestronEthernetHelper.GetEthernetParameter(
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||||
|
|
||||||
|
if (port <= 0)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, "Debug websocket port is not set; skipping port forwarding removal");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var result = CrestronEthernetHelper.RemovePortForwarding(
|
var result = CrestronEthernetHelper.RemovePortForwarding(
|
||||||
(ushort)port, (ushort)port, csIp,
|
(ushort)port, (ushort)port, csIp,
|
||||||
CrestronEthernetHelper.ePortMapTransport.TCP);
|
CrestronEthernetHelper.ePortMapTransport.TCP);
|
||||||
|
|
@ -145,6 +163,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port);
|
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding removal");
|
Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding removal");
|
||||||
|
|
@ -161,5 +180,55 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Websocket Debug Session Stopped");
|
Debug.LogMessage(LogEventLevel.Information, "Websocket Debug Session Stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartPortForwardTimeout(int port, string csIp)
|
||||||
|
{
|
||||||
|
lock (_timerLock)
|
||||||
|
{
|
||||||
|
_portForwardTimeoutTimer?.Dispose();
|
||||||
|
_portForwardTimeoutTimer = new CTimer(_ =>
|
||||||
|
{
|
||||||
|
if (Debug.WebsocketSink.HasActiveConnections)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "No debug websocket connection within 30 seconds; removing port forward for port {0}", port);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = CrestronEthernetHelper.RemovePortForwarding(
|
||||||
|
(ushort)port, (ushort)port, csIp,
|
||||||
|
CrestronEthernetHelper.ePortMapTransport.TCP);
|
||||||
|
|
||||||
|
if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed due to timeout", port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels the port forward timeout timer if a session is being explicitly stopped.
|
||||||
|
/// </summary>
|
||||||
|
private void CancelPortForwardTimeout()
|
||||||
|
{
|
||||||
|
lock (_timerLock)
|
||||||
|
{
|
||||||
|
_portForwardTimeoutTimer?.Dispose();
|
||||||
|
_portForwardTimeoutTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
{
|
{
|
||||||
private readonly IEssentialsRoomCombiner _roomCombiner;
|
private readonly IEssentialsRoomCombiner _roomCombiner;
|
||||||
|
|
||||||
|
private readonly IEssentialsRoomCombinerWithOperationStatus _roomCombinerWithOperationStatus;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="IEssentialsRoomCombinerMessenger"/> class, which facilitates
|
/// Initializes a new instance of the <see cref="IEssentialsRoomCombinerMessenger"/> class, which facilitates
|
||||||
/// messaging for an <see cref="IEssentialsRoomCombiner"/> instance.
|
/// messaging for an <see cref="IEssentialsRoomCombiner"/> instance.
|
||||||
|
|
@ -35,6 +37,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
: base(key, messagePath, roomCombiner as IKeyName)
|
: base(key, messagePath, roomCombiner as IKeyName)
|
||||||
{
|
{
|
||||||
_roomCombiner = roomCombiner;
|
_roomCombiner = roomCombiner;
|
||||||
|
_roomCombinerWithOperationStatus = roomCombiner as IEssentialsRoomCombinerWithOperationStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -98,6 +101,19 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
SendFullStatus();
|
SendFullStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_roomCombinerWithOperationStatus != null)
|
||||||
|
{
|
||||||
|
_roomCombinerWithOperationStatus.CombinationOperationStatusChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
var message = new
|
||||||
|
{
|
||||||
|
combinationOperation = _roomCombinerWithOperationStatus.CombinationOperation
|
||||||
|
};
|
||||||
|
|
||||||
|
PostStatusMessage(JToken.FromObject(message));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_roomCombiner.IsInAutoModeFeedback.OutputChange += (sender, args) =>
|
_roomCombiner.IsInAutoModeFeedback.OutputChange += (sender, args) =>
|
||||||
{
|
{
|
||||||
var message = new
|
var message = new
|
||||||
|
|
@ -138,6 +154,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
DisableAutoMode = _roomCombiner.DisableAutoMode,
|
DisableAutoMode = _roomCombiner.DisableAutoMode,
|
||||||
IsInAutoMode = _roomCombiner.IsInAutoMode,
|
IsInAutoMode = _roomCombiner.IsInAutoMode,
|
||||||
CurrentScenario = _roomCombiner.CurrentScenario,
|
CurrentScenario = _roomCombiner.CurrentScenario,
|
||||||
|
CombinationOperation = _roomCombinerWithOperationStatus != null ? _roomCombinerWithOperationStatus.CombinationOperation : null,
|
||||||
Rooms = rooms,
|
Rooms = rooms,
|
||||||
RoomCombinationScenarios = _roomCombiner.RoomCombinationScenarios,
|
RoomCombinationScenarios = _roomCombiner.RoomCombinationScenarios,
|
||||||
Partitions = _roomCombiner.Partitions
|
Partitions = _roomCombiner.Partitions
|
||||||
|
|
@ -194,6 +211,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
[JsonProperty("currentScenario", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("currentScenario", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public IRoomCombinationScenario CurrentScenario { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the collection of rooms associated with the entity.
|
/// Gets or sets the collection of rooms associated with the entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
|
|
||||||
message.Name = _device.Name;
|
message.Name = _device.Name;
|
||||||
|
|
||||||
|
message.MessageBasePath = MessagePath;
|
||||||
|
|
||||||
|
|
||||||
var token = JToken.FromObject(message);
|
var token = JToken.FromObject(message);
|
||||||
|
|
||||||
PostStatusMessage(token, MessagePath, clientId);
|
PostStatusMessage(token, MessagePath, clientId);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
|
|
@ -40,9 +41,10 @@ namespace PepperDash.Essentials
|
||||||
public bool EnableApiServer { get; set; } = true;
|
public bool EnableApiServer { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enable subscriptions for Messengers
|
/// Enables subscriptions for messengers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("enableMessengerSubscriptions")]
|
[JsonProperty("enableMessengerSubscriptions")]
|
||||||
|
[Obsolete("This property is obsolete and will be removed in a future version. All messengers are now subscription based.")]
|
||||||
public bool EnableMessengerSubscriptions { get; set; }
|
public bool EnableMessengerSubscriptions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue