diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index a45adb27..937fffd8 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -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. - 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 + }; + /// /// Initializes a new instance of the 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; + } - 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. public event EventHandler RoomCombinationScenarioChanged; + /// + /// Occurs when the room combination operation status changes. + /// + public event EventHandler CombinationOperationStatusChanged; + /// /// Gets the current room combination scenario. /// @@ -304,6 +337,20 @@ namespace PepperDash.Essentials.Core } } + /// + /// Gets the current room combination operation status. + /// + public CombinationOperationStatus CombinationOperation + { + get + { + lock (_combinationOperationLock) + { + return CloneCombinationOperationStatus(_combinationOperation); + } + } + } + /// /// Gets or sets the IsInAutoModeFeedback /// @@ -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 + }; + } } /// diff --git a/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs index 34d0a0a0..641993ea 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs @@ -16,12 +16,14 @@ namespace PepperDash.Essentials.Core /// event EventHandler RoomCombinationScenarioChanged; + /// /// The current room combination scenario /// [JsonProperty("currentScenario")] IRoomCombinationScenario CurrentScenario { get; } + /// /// When true, indicates the current mode is auto mode /// @@ -85,6 +87,56 @@ namespace PepperDash.Essentials.Core void SetRoomCombinationScenario(string scenarioKey); } + /// + /// Optional extension for room combiners that provide operation lifecycle status. + /// + public interface IEssentialsRoomCombinerWithOperationStatus : IEssentialsRoomCombiner + { + /// + /// Indicates that the room combination operation status has changed. + /// + event EventHandler CombinationOperationStatusChanged; + + /// + /// Gets the current room combination operation status. + /// + [JsonProperty("combinationOperation")] + CombinationOperationStatus CombinationOperation { get; } + } + + /// + /// Defines lifecycle states for a room combination operation. + /// + public enum CombinationOperationState + { + Idle, + InProgress, + Completed, + Failed, + TimedOut + } + + /// + /// Represents room combination operation status details. + /// + 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; } + } + /// /// Represents a scenario for combining rooms, including activation, deactivation, and associated state. /// diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs index 966d8d77..65a279f2 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs @@ -21,6 +21,8 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly IEssentialsRoomCombiner _roomCombiner; + private readonly IEssentialsRoomCombinerWithOperationStatus _roomCombinerWithOperationStatus; + /// /// Initializes a new instance of the class, which facilitates /// messaging for an instance. @@ -35,6 +37,7 @@ namespace PepperDash.Essentials.AppServer.Messengers : base(key, messagePath, roomCombiner as IKeyName) { _roomCombiner = roomCombiner; + _roomCombinerWithOperationStatus = roomCombiner as IEssentialsRoomCombinerWithOperationStatus; } /// @@ -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; } + /// + /// Gets or sets the room combination operation status. + /// + [JsonProperty("combinationOperation", NullValueHandling = NullValueHandling.Ignore)] + public CombinationOperationStatus CombinationOperation { get; set; } + /// /// Gets or sets the collection of rooms associated with the entity. ///