feat: add optional room combiner operation status lifecycle

This commit is contained in:
equinoy 2026-07-07 10:51:28 -05:00
parent f85343d8fd
commit e623865c2a
3 changed files with 176 additions and 7 deletions

View file

@ -17,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, IEssentialsRoomCombiner
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombinerWithOperationStatus
{
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
@ -79,6 +79,13 @@ namespace PepperDash.Essentials.Core
private Mutex _scenarioChange = new Mutex();
private readonly object _combinationOperationLock = new object();
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.
@ -256,13 +263,19 @@ namespace PepperDash.Essentials.Core
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
{
if (newScenario == _currentScenario)
{
return;
}
SetCombinationOperationStatus(
CombinationOperationState.InProgress,
newScenario != null ? newScenario.Key : null,
null,
true);
try
{
// Deactivate the old scenario first
if (_currentScenario != null)
{
@ -281,7 +294,22 @@ namespace PepperDash.Essentials.Core
RoomCombinationScenarioChanged?.Invoke(this, new EventArgs());
SetCombinationOperationStatus(
CombinationOperationState.Completed,
_currentScenario != null ? _currentScenario.Key : null,
null,
false);
}
catch (Exception ex)
{
this.LogException(ex, "Error changing room combination scenario");
SetCombinationOperationStatus(
CombinationOperationState.Failed,
newScenario != null ? newScenario.Key : null,
"Combination operation failed",
false);
}
}
#region IEssentialsRoomCombiner Members
@ -293,6 +321,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 +337,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 +502,53 @@ namespace PepperDash.Essentials.Core
}
#endregion
private void SetCombinationOperationStatus(
CombinationOperationState state,
string scenarioKey,
string message,
bool resetStartedUtc)
{
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;
}
}
CombinationOperationStatusChanged?.Invoke(this, EventArgs.Empty);
}
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>

View file

@ -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>

View file

@ -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>