mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-08-31 19:08:29 +00:00
Merge 690db6beb8 into e23c987516
This commit is contained in:
commit
484dca52b3
9 changed files with 516 additions and 43 deletions
32
README.md
32
README.md
|
|
@ -54,6 +54,38 @@ 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,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;
|
||||
return _partitionSensor?.PartitionPresentFeedback?.BoolValue ?? _partitionPresent;
|
||||
}
|
||||
|
||||
return _partitionPresent;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Serilog.Events;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
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
|
||||
/// 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, IEssentialsRoomCombiner
|
||||
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombinerWithOperationStatus
|
||||
{
|
||||
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
|
||||
|
||||
|
|
@ -75,10 +76,29 @@ 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.
|
||||
|
|
@ -105,6 +125,12 @@ 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
|
||||
|
|
@ -133,6 +159,8 @@ namespace PepperDash.Essentials.Core
|
|||
// connected and initialized
|
||||
DeviceManager.AllDevicesInitialized += (o, a) =>
|
||||
{
|
||||
InitializeOperationStatusProviders();
|
||||
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
DetermineRoomCombinationScenario();
|
||||
|
|
@ -256,13 +284,19 @@ namespace PepperDash.Essentials.Core
|
|||
|
||||
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
|
||||
{
|
||||
|
||||
if (newScenario == _currentScenario)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -281,7 +315,20 @@ 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
|
||||
|
|
@ -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>
|
||||
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>
|
||||
|
|
@ -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>
|
||||
/// Gets or sets the IsInAutoModeFeedback
|
||||
/// </summary>
|
||||
|
|
@ -455,6 +521,299 @@ 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>
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
public class EssentialsRoomCombinerPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the system operates in automatic mode.
|
||||
/// <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>
|
||||
/// 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,
|
||||
/// auto mode won't be allowed to be turned on.</remarks>
|
||||
/// </summary>
|
||||
[JsonProperty("disableAutoMode")]
|
||||
public bool DisableAutoMode { get; set; }
|
||||
|
|
@ -49,11 +49,18 @@ namespace PepperDash.Essentials.Core
|
|||
[JsonProperty("defaultScenarioKey")]
|
||||
public string defaultScenarioKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the debounce time, in seconds, for scenario changes.
|
||||
/// <summary>
|
||||
/// Gets or sets the debounce time, in seconds, for scenario changes.
|
||||
/// </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>
|
||||
|
|
@ -61,14 +68,14 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
public class PartitionConfig : IKeyName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique key associated with the object.
|
||||
/// <summary>
|
||||
/// Gets or sets the unique key associated with the object.
|
||||
/// </summary>
|
||||
[JsonProperty("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name associated with the object.
|
||||
/// <summary>
|
||||
/// Gets or sets the name associated with the object.
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
|
@ -91,26 +98,26 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
public class RoomCombinationScenarioConfig : IKeyName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the key associated with the object.
|
||||
/// <summary>
|
||||
/// Gets or sets the key associated with the object.
|
||||
/// </summary>
|
||||
[JsonProperty("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name associated with the object.
|
||||
/// <summary>
|
||||
/// Gets or sets the name associated with the object.
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to hide this scenario in the UI.
|
||||
/// <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>
|
||||
/// Gets or sets the collection of partition states.
|
||||
public bool HideInUi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of partition states.
|
||||
/// </summary>
|
||||
[JsonProperty("partitionStates")]
|
||||
public List<PartitionState> PartitionStates { get; set; }
|
||||
|
|
@ -121,14 +128,14 @@ namespace PepperDash.Essentials.Core
|
|||
[JsonProperty("uiMap")]
|
||||
public Dictionary<string, string> UiMap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of actions to be performed during device activation.
|
||||
/// <summary>
|
||||
/// Gets or sets the list of actions to be performed during device activation.
|
||||
/// </summary>
|
||||
[JsonProperty("activationActions")]
|
||||
public List<DeviceActionWrapper> ActivationActions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of actions to be performed when a device is deactivated.
|
||||
/// <summary>
|
||||
/// Gets or sets the list of actions to be performed when a device is deactivated.
|
||||
/// </summary>
|
||||
[JsonProperty("deactivationActions")]
|
||||
public List<DeviceActionWrapper> DeactivationActions { get; set; }
|
||||
|
|
@ -139,14 +146,14 @@ namespace PepperDash.Essentials.Core
|
|||
/// </summary>
|
||||
public class PartitionState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the partition key used to group and organize data within a storage system.
|
||||
/// <summary>
|
||||
/// Gets or sets the partition key used to group and organize data within a storage system.
|
||||
/// </summary>
|
||||
[JsonProperty("partitionKey")]
|
||||
public string PartitionKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a partition is currently present.
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a partition is currently present.
|
||||
/// </summary>
|
||||
[JsonProperty("partitionSensedState")]
|
||||
public bool PartitionPresent { get; set; }
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ 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>
|
||||
|
|
@ -85,6 +87,56 @@ 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,7 +368,6 @@ 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}";
|
||||
|
|
|
|||
|
|
@ -187,11 +187,12 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
|||
_portForwardTimeoutTimer?.Dispose();
|
||||
_portForwardTimeoutTimer = new CTimer(_ =>
|
||||
{
|
||||
if (Debug.WebsocketSink.HasActiveConnections)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward");
|
||||
return;
|
||||
}
|
||||
if (Debug.WebsocketSink.HasActiveConnections)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward");
|
||||
StartPortForwardTimeout(port, csIp);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "No debug websocket connection within 30 seconds; removing port forward for port {0}", port);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ 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.
|
||||
|
|
@ -35,6 +37,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
: base(key, messagePath, roomCombiner as IKeyName)
|
||||
{
|
||||
_roomCombiner = roomCombiner;
|
||||
_roomCombinerWithOperationStatus = roomCombiner as IEssentialsRoomCombinerWithOperationStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -98,6 +101,19 @@ 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
|
||||
|
|
@ -138,6 +154,7 @@ 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
|
||||
|
|
@ -194,6 +211,12 @@ 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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue