From 9a6209f50a63a9a0b4c5436b85fdb2cef0a7dc79 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 25 Jun 2024 16:08:44 -0600 Subject: [PATCH 01/26] feat: Adds basic CamerLists config structure and CameraListItems class --- .../Config/BasicConfig.cs | 4 + .../Devices/CameraListItem.cs | 76 +++++++++++++++++++ .../Devices/SourceListItem.cs | 1 + 3 files changed, 81 insertions(+) create mode 100644 src/PepperDash.Essentials.Core/Devices/CameraListItem.cs diff --git a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs index 6df7b583..89bd7655 100644 --- a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs @@ -5,6 +5,7 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using PepperDash.Essentials.Core.Devices; namespace PepperDash.Essentials.Core.Config { @@ -28,6 +29,9 @@ namespace PepperDash.Essentials.Core.Config [JsonProperty("audioControlPointLists")] public Dictionary AudioControlPointLists { get; set; } + [JsonProperty("cameraLists")] + public Dictionary> CameraLists { get; set; } + [JsonProperty("tieLines")] public List TieLines { get; set; } diff --git a/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs b/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs new file mode 100644 index 00000000..e3fefcc8 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json; +using PepperDash.Core; + +namespace PepperDash.Essentials.Core.Devices +{ + public class CameraListItem + { + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + /// + /// Returns the source Device for this, if it exists in DeviceManager + /// + [JsonIgnore] + public Device CameraDevice + { + get + { + if (_cameraDevice == null) + _cameraDevice = DeviceManager.GetDeviceForKey(DeviceKey) as Device; + return _cameraDevice; + } + } + Device _cameraDevice; + + /// + /// Gets either the source's Name or this AlternateName property, if + /// defined. If source doesn't exist, returns "Missing source" + /// + [JsonProperty("preferredName")] + public string PreferredName + { + get + { + if (string.IsNullOrEmpty(Name)) + { + if (CameraDevice == null) + return "---"; + return CameraDevice.Name; + } + return Name; + } + } + + /// + /// A name that will override the source's name on the UI + /// + [JsonProperty("name")] + public string Name { get; set; } + + + /// + /// Specifies and icon for the source list item + /// + [JsonProperty("icon")] + public string Icon { get; set; } + + /// + /// Alternate icon + /// + [JsonProperty("altIcon", NullValueHandling = NullValueHandling.Ignore)] + public string AltIcon { get; set; } + + /// + /// Indicates if the item should be included in the user facing list + /// + [JsonProperty("includeInUserList")] + public bool IncludeInUserList { get; set; } + + /// + /// Used to specify the order of the items in the source list when displayed + /// + [JsonProperty("order")] + public int Order { get; set; } + } +} diff --git a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index b0372b51..48d43c94 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -193,6 +193,7 @@ namespace PepperDash.Essentials.Core defaultDisplay, leftDisplay, rightDisplay, + centerDisplay, programAudio, codecContent } From ddc2491664215c1af9dd6234e1ba337bc374d368 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 25 Jun 2024 17:15:45 -0600 Subject: [PATCH 02/26] feat: updates to IEssentialsRoom for CameraListKey and adds helper method to get camera list from config --- src/PepperDash.Essentials.Core/Config/BasicConfig.cs | 11 +++++++++++ .../Room/IEssentialsRoom.cs | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs index 89bd7655..5838140e 100644 --- a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs @@ -88,6 +88,17 @@ namespace PepperDash.Essentials.Core.Config return AudioControlPointLists[key]; } + /// + /// Checks CameraLists for a given list and returns it if found. Otherwise, returns null + /// + public Dictionary GetCameraListForKey(string key) + { + if (string.IsNullOrEmpty(key) || !CameraLists.ContainsKey(key)) + return null; + + return CameraLists[key]; + } + /// /// Checks Devices for an item with a Key that matches and returns it if found. Otherwise, retunes null /// diff --git a/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs b/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs index 4bf51376..d452c0b9 100644 --- a/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs +++ b/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs @@ -31,6 +31,8 @@ namespace PepperDash.Essentials.Core string AudioControlPointListKey { get; } + string CameraListKey { get; } + SecondsCountdownTimer ShutdownPromptTimer { get; } int ShutdownPromptSeconds { get; } int ShutdownVacancySeconds { get; } From 0f9bddf4ddb949126267d1ff64df2587229aea5b Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 25 Jun 2024 17:58:44 -0600 Subject: [PATCH 03/26] fix: updates EssentialsRoomBase to add CameraListKey --- .../Room/EssentialsRoomBase.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs b/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs index be86b70f..232c02ed 100644 --- a/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs +++ b/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs @@ -133,7 +133,28 @@ namespace PepperDash.Essentials.Core } } - + private string _cameraListKey; + public string CameraListKey + { + get + { + if (string.IsNullOrEmpty(_cameraListKey)) + { + return _defaultListKey; + } + else + { + return _cameraListKey; + } + } + protected set + { + if (value != _cameraListKey) + { + _cameraListKey = value; + } + } + } /// /// Timer used for informing the UIs of a shutdown From c2fb44a662261cfd5814fe9e4ad871922fb1f758 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 25 Jun 2024 21:22:49 -0600 Subject: [PATCH 04/26] feat: adds CameraListKey to EssentialsAvRoomPropertiesConfig --- .../Room/Config/EssentialsRoomConfig.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs index 32e1feda..3f457176 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs @@ -200,6 +200,9 @@ namespace PepperDash.Essentials.Room.Config public string DestinationListKey { get; set; } [JsonProperty("audioControlPointListKey")] public string AudioControlPointListKey { get; set; } + [JsonProperty("cameraListKey")] + public string CameraListKey { get; set; } + [JsonProperty("defaultSourceItem")] public string DefaultSourceItem { get; set; } From 5fb6f3e117f01f5d2b2c1f7d410a035d06259032 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 25 Jun 2024 22:20:13 -0600 Subject: [PATCH 05/26] fix: Fixes namespace for CameraListItem --- src/PepperDash.Essentials.Core/Devices/CameraListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs b/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs index e3fefcc8..d519feb4 100644 --- a/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using PepperDash.Core; -namespace PepperDash.Essentials.Core.Devices +namespace PepperDash.Essentials.Core { public class CameraListItem { From d9721a362e8e5bbe18a7120b78065baf168ada6c Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 08:55:18 -0600 Subject: [PATCH 06/26] feat: adds method to set input source type and corresponding enum to ICiscoCodecCameraConfig --- .../Codec/ICiscoCodecCameraConfig.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs b/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs new file mode 100644 index 00000000..56dc9cfe --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs @@ -0,0 +1,31 @@ +using Crestron.SimplSharpPro; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PepperDash.Essentials.Devices.Common.Codec +{ + /// + /// Describes a cisco codec device that can allow configuration of cameras + /// + public interface ICiscoCodecCameraConfig + { + void SetCameraAssignedSerialNumber(uint cameraId, string serialNumber); + + void SetCameraName(uint videoConnecterId, string name); + + void SetInputSourceType(uint videoConnectorId, eCiscoCodecInputSourceType sourceType); + } + + public enum eCiscoCodecInputSourceType + { + PC, + camera, + document_camera, + mediaplayer, + other, + whiteboard + } +} From c499d2a2eb6c1f3c51bc17aa88ac0927c72c609e Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 09:01:59 -0600 Subject: [PATCH 07/26] fix: corrects spelling mistake --- .../Codec/ICiscoCodecCameraConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs b/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs index 56dc9cfe..fefb4b09 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/ICiscoCodecCameraConfig.cs @@ -14,7 +14,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec { void SetCameraAssignedSerialNumber(uint cameraId, string serialNumber); - void SetCameraName(uint videoConnecterId, string name); + void SetCameraName(uint videoConnectorId, string name); void SetInputSourceType(uint videoConnectorId, eCiscoCodecInputSourceType sourceType); } From bec3ab8e73b43054fa910f85590d1ee4ab79f7f6 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 11:27:22 -0600 Subject: [PATCH 08/26] fix: Adds missing initializer for CameraLists --- src/PepperDash.Essentials.Core/Config/BasicConfig.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs index 5838140e..59ea9bae 100644 --- a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs @@ -45,6 +45,7 @@ namespace PepperDash.Essentials.Core.Config SourceLists = new Dictionary>(); DestinationLists = new Dictionary>(); AudioControlPointLists = new Dictionary(); + CameraLists = new Dictionary>(); TieLines = new List(); JoinMaps = new Dictionary(); } From c7f4bf1fb20cafac70456e8d59e4162f0b90e211 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 12:42:43 -0600 Subject: [PATCH 09/26] fix: fixes backing values for eLevelControlType --- .../Devices/LevelControlListItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs b/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs index d18dacc5..18da1130 100644 --- a/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs @@ -65,8 +65,8 @@ namespace PepperDash.Essentials.Core [Flags] public enum eLevelControlType { - Level = 0, - Mute = 1, + Level = 1, + Mute = 2, LevelAndMute = Level | Mute, } } From 71815eff171f580e51da14dd736a7c12dd257514 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 12:57:06 -0600 Subject: [PATCH 10/26] fix: updates mode for partition sensors when mode of room combiner changes --- .../Room/Combining/EssentialsRoomCombiner.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index dfc1066a..14cffa5f 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -132,6 +132,8 @@ namespace PepperDash.Essentials.Core void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) { + if (!IsInAutoMode) return; + StartDebounceTimer(); } @@ -232,16 +234,33 @@ namespace PepperDash.Essentials.Core public void SetAutoMode() { IsInAutoMode = true; + + foreach (var partition in Partitions) + { + partition.SetAutoMode(); + } } public void SetManualMode() { IsInAutoMode = false; + + foreach (var partition in Partitions) + { + partition.SetManualMode(); + } } public void ToggleMode() { - IsInAutoMode = !IsInAutoMode; + if(IsInAutoMode) + { + SetManualMode(); + } + else + { + SetAutoMode(); + } } public List RoomCombinationScenarios { get; private set; } From a3351812cd68678583868ab9d4711e0b00c85724 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 13:20:19 -0600 Subject: [PATCH 11/26] fix: fixes for room combination in manual/auto mode with actual crestron partition sensors --- .../PartitionSensor/EssentialsPartitionController.cs | 4 ++++ .../Room/Combining/EssentialsRoomCombiner.cs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs index 225abb76..55c49727 100644 --- a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs +++ b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs @@ -87,6 +87,8 @@ namespace PepperDash.Essentials.Core public void SetAutoMode() { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Auto Mode", this); + IsInAutoMode = true; if (PartitionPresentFeedback != null) { @@ -106,6 +108,8 @@ namespace PepperDash.Essentials.Core public void SetManualMode() { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Manual Mode", this); + IsInAutoMode = false; if (PartitionPresentFeedback != null) { diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index 14cffa5f..21cc332f 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -132,8 +132,6 @@ namespace PepperDash.Essentials.Core void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) { - if (!IsInAutoMode) return; - StartDebounceTimer(); } @@ -239,6 +237,8 @@ namespace PepperDash.Essentials.Core { partition.SetAutoMode(); } + + DetermineRoomCombinationScenario(); } public void SetManualMode() From f3b4c0aa02d5e71c19a7b45de3383ccf835c1185 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 28 Jun 2024 13:56:42 -0600 Subject: [PATCH 12/26] fix: resolves issue with incorrect partition state feedback in test mode --- .../PartitionSensor/EssentialsPartitionController.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs index 55c49727..800299b8 100644 --- a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs +++ b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs @@ -77,7 +77,14 @@ namespace PepperDash.Essentials.Core { if (IsInAutoMode) { - PartitionPresentFeedback.FireUpdate(); + if(e.BoolValue) + { + PartitionPresent = true; + } + else + { + PartitionPresent = false; + } } } @@ -132,7 +139,6 @@ namespace PepperDash.Essentials.Core if (!IsInAutoMode) { PartitionPresent = true; - PartitionPresentFeedback.FireUpdate(); } } @@ -141,7 +147,6 @@ namespace PepperDash.Essentials.Core if (!IsInAutoMode) { PartitionPresent = false; - PartitionPresentFeedback.FireUpdate(); } } From fec6b0d385f9c6de70644de64be28fdc65512208 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 8 Jul 2024 08:41:03 -0500 Subject: [PATCH 13/26] fix: change source for PartitionPresent depending on mode Added `FireUpdate` calls for feedback on mode change in order to get correct values when changing modes. --- .../EssentialsPartitionController.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs index 800299b8..56d09aee 100644 --- a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs +++ b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs @@ -1,9 +1,5 @@ -using System; +using PepperDash.Core; using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using PepperDash.Core; namespace PepperDash.Essentials.Core { @@ -26,6 +22,11 @@ namespace PepperDash.Essentials.Core { get { + if (IsInAutoMode) + { + return _partitionSensor.PartitionPresentFeedback.BoolValue; + } + return _partitionPresent; } set @@ -73,18 +74,11 @@ namespace PepperDash.Essentials.Core PartitionPresentFeedback.FireUpdate(); } - void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) + private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) { if (IsInAutoMode) { - if(e.BoolValue) - { - PartitionPresent = true; - } - else - { - PartitionPresent = false; - } + PartitionPresent = e.BoolValue; } } @@ -111,6 +105,8 @@ namespace PepperDash.Essentials.Core _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; _partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange; } + + PartitionPresentFeedback.FireUpdate(); } public void SetManualMode() @@ -131,6 +127,8 @@ namespace PepperDash.Essentials.Core { _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; } + + PartitionPresentFeedback.FireUpdate(); } From bc217a200806e1c59e599a40ec6a6ff4562b34af Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 8 Jul 2024 08:44:45 -0500 Subject: [PATCH 14/26] chore: add logging for current scenario searching --- .../Room/Combining/EssentialsRoomCombiner.cs | 39 +++++++++++-------- .../Room/Combining/RoomCombinationScenario.cs | 23 +++++------ .../Routing/Extensions.cs | 21 ++++++---- .../Routing/RouteRequest.cs | 8 +++- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index 21cc332f..df50e864 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -1,11 +1,10 @@ -using System; +using Crestron.SimplSharp; +using PepperDash.Core; +using PepperDash.Core.Logging; +using Serilog.Events; +using System; using System.Collections.Generic; using System.Linq; -using Crestron.SimplSharp; - -using PepperDash.Core; -using Serilog.Events; -using Newtonsoft.Json; namespace PepperDash.Essentials.Core { @@ -35,7 +34,7 @@ namespace PepperDash.Essentials.Core } set { - if(value == _isInAutoMode) + if (value == _isInAutoMode) { return; } @@ -93,7 +92,7 @@ namespace PepperDash.Essentials.Core }); } - void CreateScenarios() + private void CreateScenarios() { foreach (var scenarioConfig in _propertiesConfig.Scenarios) { @@ -102,21 +101,20 @@ namespace PepperDash.Essentials.Core } } - void SetRooms() + private void SetRooms() { _rooms = new List(); foreach (var roomKey in _propertiesConfig.RoomKeys) { - var room = DeviceManager.GetDeviceForKey(roomKey) as IEssentialsRoom; - if (room != null) + if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom room) { _rooms.Add(room); } } } - void SetupPartitionStateProviders() + private void SetupPartitionStateProviders() { foreach (var pConfig in _propertiesConfig.Partitions) { @@ -130,18 +128,18 @@ namespace PepperDash.Essentials.Core } } - void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) + private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) { StartDebounceTimer(); } - void StartDebounceTimer() + private void StartDebounceTimer() { // default to 500ms for manual mode var time = 500; // if in auto mode, debounce the scenario change - if(IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000; + if (IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000; if (_scenarioChangeDebounceTimer == null) { @@ -156,7 +154,7 @@ namespace PepperDash.Essentials.Core /// /// Determines the current room combination scenario based on the state of the partition sensors /// - void DetermineRoomCombinationScenario() + private void DetermineRoomCombinationScenario() { if (_scenarioChangeDebounceTimer != null) { @@ -164,14 +162,20 @@ namespace PepperDash.Essentials.Core _scenarioChangeDebounceTimer = null; } + this.LogInformation("Determining Combination Scenario"); + var currentScenario = RoomCombinationScenarios.FirstOrDefault((s) => { + this.LogDebug("Checking scenario {scenarioKey}", s.Key); // iterate the partition states foreach (var partitionState in s.PartitionStates) { + this.LogDebug("checking PartitionState {partitionStateKey}", partitionState.PartitionKey); // get the partition by key var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey)); + this.LogDebug("Expected State: {partitionPresent} Actual State: {partitionState}", partitionState.PartitionPresent, partition.PartitionPresentFeedback.BoolValue); + if (partition != null && partitionState.PartitionPresent != partition.PartitionPresentFeedback.BoolValue) { // the partition can't be found or the state doesn't match @@ -184,6 +188,7 @@ namespace PepperDash.Essentials.Core if (currentScenario != null) { + this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key); CurrentScenario = currentScenario; } } @@ -253,7 +258,7 @@ namespace PepperDash.Essentials.Core public void ToggleMode() { - if(IsInAutoMode) + if (IsInAutoMode) { SetManualMode(); } diff --git a/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs b/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs index 9bc0d8ec..84674d02 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs @@ -1,22 +1,15 @@ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - +using Newtonsoft.Json; using PepperDash.Core; - -using Newtonsoft.Json; +using PepperDash.Core.Logging; using Serilog.Events; +using System.Collections.Generic; namespace PepperDash.Essentials.Core { /// /// Represents a room combination scenario /// - public class RoomCombinationScenario: IRoomCombinationScenario, IKeyName + public class RoomCombinationScenario : IRoomCombinationScenario, IKeyName { private RoomCombinationScenarioConfig _config; @@ -40,7 +33,7 @@ namespace PepperDash.Essentials.Core get { return _isActive; } set { - if(value == _isActive) + if (value == _isActive) { return; } @@ -78,12 +71,13 @@ namespace PepperDash.Essentials.Core public void Activate() { - Debug.LogMessage(LogEventLevel.Debug, "Activating Scenario: '{0}' with {1} action(s) defined", Name, activationActions.Count); + Debug.LogMessage(LogEventLevel.Debug, "Activating Scenario: '{name}' with {activationActionCount} action(s) defined", this, Name, activationActions.Count); if (activationActions != null) { foreach (var action in activationActions) { + this.LogDebug("Running Activation action {@action}", action); DeviceJsonApi.DoDeviceAction(action); } } @@ -93,12 +87,13 @@ namespace PepperDash.Essentials.Core public void Deactivate() { - Debug.LogMessage(LogEventLevel.Debug, "Deactivating Scenario: '{0}' with {1} action(s) defined", Name, deactivationActions.Count); + Debug.LogMessage(LogEventLevel.Debug, "Deactivating Scenario: '{name}' with {deactivationActionCount} action(s) defined", this, Name, deactivationActions.Count); if (deactivationActions != null) { foreach (var action in deactivationActions) { + this.LogDebug("Running deactivation action {@action}", action); DeviceJsonApi.DoDeviceAction(action); } } diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index a0eba868..b612796a 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -1,8 +1,10 @@ -using PepperDash.Core; -using Serilog.Events; +using Serilog.Events; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; +using Debug = PepperDash.Core.Debug; namespace PepperDash.Essentials.Core @@ -21,8 +23,13 @@ namespace PepperDash.Essentials.Core /// and then attempts a new Route and if sucessful, stores that RouteDescriptor /// in RouteDescriptorCollection.DefaultCollection /// + [MethodImpl(MethodImplOptions.NoInlining)] // REMOVE ME public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "") { + // Remove this line before committing!!!!! + var frame = new StackFrame(1, true); + Debug.LogMessage(LogEventLevel.Information, "ReleaseAndMakeRoute Called from {method} with params {destinationKey}:{sourceKey}:{signalType}:{destinationPortKey}:{sourcePortKey}", frame.GetMethod().Name, destination.Key, source.Key, signalType.ToString(), destinationPortKey, sourcePortKey); + var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey); var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey); @@ -33,8 +40,8 @@ namespace PepperDash.Essentials.Core { if (destination == null) throw new ArgumentNullException(nameof(destination)); if (source == null) throw new ArgumentNullException(nameof(source)); - if (destinationPort == null) Debug.LogMessage(LogEventLevel.Verbose, "Destination port is null"); - if (sourcePort == null) Debug.LogMessage(LogEventLevel.Verbose, "Source port is null"); + if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null"); + if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null"); var routeRequest = new RouteRequest { @@ -57,7 +64,7 @@ namespace PepperDash.Essentials.Core RouteRequests[destination.Key] = routeRequest; - Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); return; } @@ -71,14 +78,14 @@ namespace PepperDash.Essentials.Core RouteRequests.Add(destination.Key, routeRequest); - Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); return; } if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) { RouteRequests.Remove(destination.Key); - Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key); + Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); } destination.ReleaseRoute(); diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs index 35169474..02b7ff3c 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -1,4 +1,7 @@ -namespace PepperDash.Essentials.Core +using PepperDash.Core; +using Serilog.Events; + +namespace PepperDash.Essentials.Core { public class RouteRequest { @@ -11,10 +14,13 @@ public void HandleCooldown(object sender, FeedbackEventArgs args) { + Debug.LogMessage(LogEventLevel.Information, "Handling cooldown route request: {destination}:{destinationPort} -> {source}:{sourcePort} {type}", null, Destination.Key, DestinationPort.Key, Source.Key, SourcePort.Key, SignalType.ToString()); + var coolingDevice = sender as IWarmingCooling; if (args.BoolValue == false) { + Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination.Key, Source.Key); Destination.ReleaseAndMakeRoute(Source, SignalType); if (sender == null) return; From b63996b9e65f1131fce0c085347a30b2155ca365 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 8 Jul 2024 13:22:18 -0500 Subject: [PATCH 15/26] fix: set partition state to match physical partitions when changing from manual to auto mode --- .../PartitionSensor/EssentialsPartitionController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs index 56d09aee..aca4f008 100644 --- a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs +++ b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs @@ -104,6 +104,7 @@ namespace PepperDash.Essentials.Core { _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; _partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange; + PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue; } PartitionPresentFeedback.FireUpdate(); @@ -126,6 +127,7 @@ namespace PepperDash.Essentials.Core if (_partitionSensor != null) { _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; + PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue; } PartitionPresentFeedback.FireUpdate(); @@ -137,6 +139,7 @@ namespace PepperDash.Essentials.Core if (!IsInAutoMode) { PartitionPresent = true; + PartitionPresentFeedback.FireUpdate(); } } @@ -145,6 +148,7 @@ namespace PepperDash.Essentials.Core if (!IsInAutoMode) { PartitionPresent = false; + PartitionPresentFeedback.FireUpdate(); } } From fb60683af6479ab9d3e4fd10dfd72f5077cb17c6 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 18 Jul 2024 13:44:49 -0500 Subject: [PATCH 16/26] feat: add async method for devjson --- .../Devices/DeviceJsonApi.cs | 616 ++++++++++-------- 1 file changed, 340 insertions(+), 276 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs index 18559b40..02a31e98 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs @@ -1,83 +1,81 @@  +using Crestron.SimplSharp; +using Newtonsoft.Json; +using PepperDash.Core; +using Serilog.Events; using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Crestron.SimplSharp; using System.Reflection; -using Newtonsoft.Json; - -using PepperDash.Core; -using Serilog.Events; +using System.Threading.Tasks; namespace PepperDash.Essentials.Core { - public class DeviceJsonApi - { - /// - /// - /// - /// - public static void DoDeviceActionWithJson(string json) - { - if (String.IsNullOrEmpty(json)) - { - CrestronConsole.ConsoleCommandResponse( - "Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted."); - return; - } - try - { - var action = JsonConvert.DeserializeObject(json); + public class DeviceJsonApi + { + /// + /// + /// + /// + public static void DoDeviceActionWithJson(string json) + { + if (String.IsNullOrEmpty(json)) + { + CrestronConsole.ConsoleCommandResponse( + "Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted."); + return; + } + try + { + var action = JsonConvert.DeserializeObject(json); - DoDeviceAction(action); - } - catch (Exception ex) - { - CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}"); - } - - } + DoDeviceAction(action); + } + catch (Exception) + { + CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}"); + } + + } - /// - /// - /// - /// - public static void DoDeviceAction(DeviceActionWrapper action) - { - var key = action.DeviceKey; - var obj = FindObjectOnPath(key); - if (obj == null) - { - CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key); - return; - } + /// + /// + /// + /// + public static void DoDeviceAction(DeviceActionWrapper action) + { + var key = action.DeviceKey; + var obj = FindObjectOnPath(key); + if (obj == null) + { + CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key); + return; + } - if (action.Params == null) - { + if (action.Params == null) + { //no params, so setting action.Params to empty array - action.Params = new object[0]; - } + action.Params = new object[0]; + } - Type t = obj.GetType(); - try - { - var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); + Type t = obj.GetType(); + try + { + var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); - var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); + var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); - if (method == null) - { - CrestronConsole.ConsoleCommandResponse( - "Unable to find method with name {0} and that matches parameters {1}", action.MethodName, - action.Params); - return; - } + if (method == null) + { + CrestronConsole.ConsoleCommandResponse( + "Unable to find method with name {0} and that matches parameters {1}", action.MethodName, + action.Params); + return; + } var mParams = method.GetParameters(); var convertedParams = mParams @@ -85,60 +83,119 @@ namespace PepperDash.Essentials.Core .ToArray(); Task.Run(() => - { - try - { - Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); - method.Invoke(obj, convertedParams); - } - catch(Exception e) - { - Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); - } - }); + { + try + { + Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); + method.Invoke(obj, convertedParams); + } + catch (Exception e) + { + Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); + } + }); - CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name, - action.DeviceKey); - } - catch (Exception ex) - { - CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName, - ex.Message);} - } + CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name, + action.DeviceKey); + } + catch (Exception ex) + { + CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName, + ex.Message); + } + } - private static object ConvertType(object value, Type conversionType) - { - if (!conversionType.IsEnum) - { - return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture); - } + public static async Task DoDeviceActionAsync(DeviceActionWrapper action) + { + var key = action.DeviceKey; + var obj = FindObjectOnPath(key); + if (obj == null) + { + Debug.LogMessage(LogEventLevel.Warning, "Unable to find object at path {deviceKey}", null, key); + return; + } - var stringValue = Convert.ToString(value); + if (action.Params == null) + { + //no params, so setting action.Params to empty array + action.Params = new object[0]; + } - if (String.IsNullOrEmpty(stringValue)) - { - throw new InvalidCastException( - String.Format("{0} cannot be converted to a string prior to conversion to enum")); - } - return Enum.Parse(conversionType, stringValue, true); - } + Type t = obj.GetType(); + try + { + var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); - /// - /// Gets the properties on a device - /// - /// - /// - public static string GetProperties(string deviceObjectPath) - { - var obj = FindObjectOnPath(deviceObjectPath); - if (obj == null) - return "{ \"error\":\"No Device\"}"; + var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); - Type t = obj.GetType(); - // get the properties and set them into a new collection of NameType wrappers - var props = t.GetProperties().Select(p => new PropertyNameType(p, obj)); - return JsonConvert.SerializeObject(props, Formatting.Indented); - } + if (method == null) + { + Debug.LogMessage(LogEventLevel.Warning, + "Unable to find method with name {methodName} and that matches parameters {@parameters}", null, action.MethodName, + action.Params); + return; + } + var mParams = method.GetParameters(); + + var convertedParams = mParams + .Select((p, i) => ConvertType(action.Params[i], p.ParameterType)) + .ToArray(); + + await Task.Run(() => + { + try + { + Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params); + method.Invoke(obj, convertedParams); + } + catch (Exception e) + { + Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); + } + }); + + Debug.LogMessage(LogEventLevel.Information, "Method {methodName} successfully called on device {deviceKey} with {@params}", null, method.Name, + action.DeviceKey, action.Params); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Unable to call method with name {methodName} with {@parameters}", null, action.MethodName, action.Params); + } + } + + private static object ConvertType(object value, Type conversionType) + { + if (!conversionType.IsEnum) + { + return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture); + } + + var stringValue = Convert.ToString(value); + + if (String.IsNullOrEmpty(stringValue)) + { + throw new InvalidCastException( + String.Format("{0} cannot be converted to a string prior to conversion to enum")); + } + return Enum.Parse(conversionType, stringValue, true); + } + + /// + /// Gets the properties on a device + /// + /// + /// + public static string GetProperties(string deviceObjectPath) + { + var obj = FindObjectOnPath(deviceObjectPath); + if (obj == null) + return "{ \"error\":\"No Device\"}"; + + Type t = obj.GetType(); + // get the properties and set them into a new collection of NameType wrappers + var props = t.GetProperties().Select(p => new PropertyNameType(p, obj)); + return JsonConvert.SerializeObject(props, Formatting.Indented); + } /// /// Gets a property from a device path by name @@ -149,9 +206,9 @@ namespace PepperDash.Essentials.Core public static object GetPropertyByName(string deviceObjectPath, string propertyName) { var dev = FindObjectOnPath(deviceObjectPath); - if(dev == null) + if (dev == null) return "{ \"error\":\"No Device\"}"; - + object prop = dev.GetType().GetType().GetProperty(propertyName).GetValue(dev, null); // var prop = t.GetProperty(propertyName); @@ -166,126 +223,126 @@ namespace PepperDash.Essentials.Core } } - /// - /// Gets the methods on a device - /// - /// - /// - public static string GetMethods(string deviceObjectPath) - { - var obj = FindObjectOnPath(deviceObjectPath); - if (obj == null) - return "{ \"error\":\"No Device\"}"; + /// + /// Gets the methods on a device + /// + /// + /// + public static string GetMethods(string deviceObjectPath) + { + var obj = FindObjectOnPath(deviceObjectPath); + if (obj == null) + return "{ \"error\":\"No Device\"}"; - // Package up method names using helper objects - Type t = obj.GetType(); - var methods = t.GetMethods() - .Where(m => !m.IsSpecialName) - .Select(p => new MethodNameParams(p)); - return JsonConvert.SerializeObject(methods, Formatting.Indented); - } + // Package up method names using helper objects + Type t = obj.GetType(); + var methods = t.GetMethods() + .Where(m => !m.IsSpecialName) + .Select(p => new MethodNameParams(p)); + return JsonConvert.SerializeObject(methods, Formatting.Indented); + } - public static string GetApiMethods(string deviceObjectPath) - { - var obj = FindObjectOnPath(deviceObjectPath); - if (obj == null) - return "{ \"error\":\"No Device\"}"; + public static string GetApiMethods(string deviceObjectPath) + { + var obj = FindObjectOnPath(deviceObjectPath); + if (obj == null) + return "{ \"error\":\"No Device\"}"; - // Package up method names using helper objects - Type t = obj.GetType(); - var methods = t.GetMethods() - .Where(m => !m.IsSpecialName) - .Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any()) - .Select(p => new MethodNameParams(p)); - return JsonConvert.SerializeObject(methods, Formatting.Indented); - } - + // Package up method names using helper objects + Type t = obj.GetType(); + var methods = t.GetMethods() + .Where(m => !m.IsSpecialName) + .Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any()) + .Select(p => new MethodNameParams(p)); + return JsonConvert.SerializeObject(methods, Formatting.Indented); + } - /// - /// Walks down a dotted object path, starting with a Device, and returns the object - /// at the end of the path - /// - public static object FindObjectOnPath(string deviceObjectPath) - { - var path = deviceObjectPath.Split('.'); - var dev = DeviceManager.GetDeviceForKey(path[0]); - if (dev == null) - { - Debug.LogMessage(LogEventLevel.Information, "Device {0} not found", path[0]); - return null; - } + /// + /// Walks down a dotted object path, starting with a Device, and returns the object + /// at the end of the path + /// + public static object FindObjectOnPath(string deviceObjectPath) + { + var path = deviceObjectPath.Split('.'); - // loop through any dotted properties - object obj = dev; - if (path.Length > 1) - { - for (int i = 1; i < path.Length; i++) - { - var objName = path[i]; - string indexStr = null; - var indexOpen = objName.IndexOf('['); - if (indexOpen != -1) - { - var indexClose = objName.IndexOf(']'); - if (indexClose == -1) - { - Debug.LogMessage(LogEventLevel.Information, dev, "ERROR Unmatched index brackets"); - return null; - } - // Get the index and strip quotes if any - indexStr = objName.Substring(indexOpen + 1, indexClose - indexOpen - 1).Replace("\"", ""); - objName = objName.Substring(0, indexOpen); - Debug.LogMessage(LogEventLevel.Information, dev, " Checking for collection '{0}', index '{1}'", objName, indexStr); - } + var dev = DeviceManager.GetDeviceForKey(path[0]); + if (dev == null) + { + Debug.LogMessage(LogEventLevel.Information, "Device {0} not found", path[0]); + return null; + } - Type oType = obj.GetType(); - var prop = oType.GetProperty(objName); - if (prop == null) - { - Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} not found on {1}", objName, path[i - 1]); - return null; - } - // if there's an index, try to get the property - if (indexStr != null) - { - if (!typeof(ICollection).IsAssignableFrom(prop.PropertyType)) - { - Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} is not collection", objName); - return null; - } - var collection = prop.GetValue(obj, null) as ICollection; - // Get the indexed items "property" - var indexedPropInfo = prop.PropertyType.GetProperty("Item"); - // These are the parameters for the indexing. Only care about one - var indexParams = indexedPropInfo.GetIndexParameters(); - if (indexParams.Length > 0) - { - Debug.LogMessage(LogEventLevel.Information, " Indexed, param type: {0}", indexParams[0].ParameterType.Name); - var properParam = Convert.ChangeType(indexStr, indexParams[0].ParameterType, - System.Globalization.CultureInfo.InvariantCulture); - try - { - obj = indexedPropInfo.GetValue(collection, new object[] { properParam }); - } - // if the index is bad, catch it here. - catch (TargetInvocationException e) - { - if (e.InnerException is ArgumentOutOfRangeException) - Debug.LogMessage(LogEventLevel.Information, " Index Out of range"); - else if (e.InnerException is KeyNotFoundException) - Debug.LogMessage(LogEventLevel.Information, " Key not found"); - return null; - } - } + // loop through any dotted properties + object obj = dev; + if (path.Length > 1) + { + for (int i = 1; i < path.Length; i++) + { + var objName = path[i]; + string indexStr = null; + var indexOpen = objName.IndexOf('['); + if (indexOpen != -1) + { + var indexClose = objName.IndexOf(']'); + if (indexClose == -1) + { + Debug.LogMessage(LogEventLevel.Information, dev, "ERROR Unmatched index brackets"); + return null; + } + // Get the index and strip quotes if any + indexStr = objName.Substring(indexOpen + 1, indexClose - indexOpen - 1).Replace("\"", ""); + objName = objName.Substring(0, indexOpen); + Debug.LogMessage(LogEventLevel.Information, dev, " Checking for collection '{0}', index '{1}'", objName, indexStr); + } - } - else - obj = prop.GetValue(obj, null); - } - } - return obj; - } + Type oType = obj.GetType(); + var prop = oType.GetProperty(objName); + if (prop == null) + { + Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} not found on {1}", objName, path[i - 1]); + return null; + } + // if there's an index, try to get the property + if (indexStr != null) + { + if (!typeof(ICollection).IsAssignableFrom(prop.PropertyType)) + { + Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} is not collection", objName); + return null; + } + var collection = prop.GetValue(obj, null) as ICollection; + // Get the indexed items "property" + var indexedPropInfo = prop.PropertyType.GetProperty("Item"); + // These are the parameters for the indexing. Only care about one + var indexParams = indexedPropInfo.GetIndexParameters(); + if (indexParams.Length > 0) + { + Debug.LogMessage(LogEventLevel.Information, " Indexed, param type: {0}", indexParams[0].ParameterType.Name); + var properParam = Convert.ChangeType(indexStr, indexParams[0].ParameterType, + System.Globalization.CultureInfo.InvariantCulture); + try + { + obj = indexedPropInfo.GetValue(collection, new object[] { properParam }); + } + // if the index is bad, catch it here. + catch (TargetInvocationException e) + { + if (e.InnerException is ArgumentOutOfRangeException) + Debug.LogMessage(LogEventLevel.Information, " Index Out of range"); + else if (e.InnerException is KeyNotFoundException) + Debug.LogMessage(LogEventLevel.Information, " Key not found"); + return null; + } + } + + } + else + obj = prop.GetValue(obj, null); + } + } + return obj; + } /// /// Sets a property on an object. @@ -308,78 +365,85 @@ namespace PepperDash.Essentials.Core //return JsonConvert.SerializeObject(props, Formatting.Indented); } - - } - public class DeviceActionWrapper - { - public string DeviceKey { get; set; } - public string MethodName { get; set; } - public object[] Params { get; set; } - } + } - public class PropertyNameType - { - object Parent; + public class DeviceActionWrapper + { + public string DeviceKey { get; set; } + public string MethodName { get; set; } + public object[] Params { get; set; } + } - [JsonIgnore] - public PropertyInfo PropInfo { get; private set; } - public string Name { get { return PropInfo.Name; } } - public string Type { get { return PropInfo.PropertyType.Name; } } - public string Value { get + public class PropertyNameType + { + private object Parent; + + [JsonIgnore] + public PropertyInfo PropInfo { get; private set; } + public string Name { get { return PropInfo.Name; } } + public string Type { get { return PropInfo.PropertyType.Name; } } + public string Value { - if (PropInfo.CanRead) + get { - try + if (PropInfo.CanRead) { - return PropInfo.GetValue(Parent, null).ToString(); + try + { + return PropInfo.GetValue(Parent, null).ToString(); + } + catch (Exception) + { + return null; + } } - catch (Exception) - { + else return null; - } } - else - return null; - } } + } public bool CanRead { get { return PropInfo.CanRead; } } public bool CanWrite { get { return PropInfo.CanWrite; } } - public PropertyNameType(PropertyInfo info, object parent) - { - PropInfo = info; + public PropertyNameType(PropertyInfo info, object parent) + { + PropInfo = info; Parent = parent; - } - } + } + } - public class MethodNameParams - { - [JsonIgnore] - public MethodInfo MethodInfo { get; private set; } + public class MethodNameParams + { + [JsonIgnore] + public MethodInfo MethodInfo { get; private set; } - public string Name { get { return MethodInfo.Name; } } - public IEnumerable Params { get { - return MethodInfo.GetParameters().Select(p => - new NameType { Name = p.Name, Type = p.ParameterType.Name }); - } } + public string Name { get { return MethodInfo.Name; } } + public IEnumerable Params + { + get + { + return MethodInfo.GetParameters().Select(p => + new NameType { Name = p.Name, Type = p.ParameterType.Name }); + } + } - public MethodNameParams(MethodInfo info) - { - MethodInfo = info; - } - } + public MethodNameParams(MethodInfo info) + { + MethodInfo = info; + } + } - public class NameType - { - public string Name { get; set; } - public string Type { get; set; } - } + public class NameType + { + public string Name { get; set; } + public string Type { get; set; } + } - [AttributeUsage(AttributeTargets.All)] - public class ApiAttribute : Attribute - { + [AttributeUsage(AttributeTargets.All)] + public class ApiAttribute : Attribute + { - } + } } \ No newline at end of file From 5a9b876d8052f0a08a07c71e65261b14dc1db738 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 18 Jul 2024 13:46:40 -0500 Subject: [PATCH 17/26] fix: remove route request once the request has been handled --- .../Routing/Extensions.cs | 15 ++++++-- .../Routing/RouteRequest.cs | 37 +++++-------------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index b612796a..66c0dac3 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; using Debug = PepperDash.Core.Debug; @@ -22,8 +21,7 @@ namespace PepperDash.Essentials.Core /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute /// and then attempts a new Route and if sucessful, stores that RouteDescriptor /// in RouteDescriptorCollection.DefaultCollection - /// - [MethodImpl(MethodImplOptions.NoInlining)] // REMOVE ME + /// public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "") { // Remove this line before committing!!!!! @@ -36,6 +34,17 @@ namespace PepperDash.Essentials.Core ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort); } + public static void RemoveRouteRequestForDestination(string destinationKey) + { + Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey); + + var result = RouteRequests.Remove(destinationKey); + + var messageTemplate = result ? "Route Request for {destination} removed" : "Route Request for {destination} not found"; + + Debug.LogMessage(LogEventLevel.Information, messageTemplate, null, destinationKey); + } + private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null) { if (destination == null) throw new ArgumentNullException(nameof(destination)); diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs index 02b7ff3c..f8679b9d 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -16,38 +16,21 @@ namespace PepperDash.Essentials.Core { Debug.LogMessage(LogEventLevel.Information, "Handling cooldown route request: {destination}:{destinationPort} -> {source}:{sourcePort} {type}", null, Destination.Key, DestinationPort.Key, Source.Key, SourcePort.Key, SignalType.ToString()); - var coolingDevice = sender as IWarmingCooling; - - if (args.BoolValue == false) + if (args.BoolValue == true) { - Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination.Key, Source.Key); - Destination.ReleaseAndMakeRoute(Source, SignalType); + return; + } - if (sender == null) return; + Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination.Key, Source.Key); + Destination.ReleaseAndMakeRoute(Source, SignalType, DestinationPort?.Key ?? string.Empty, SourcePort?.Key ?? string.Empty); + if (sender is IWarmingCooling coolingDevice) + { + Debug.LogMessage(LogEventLevel.Debug, "Unsubscribing from cooling feedback for {destination}", null, Destination.Key); coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; } + + Extensions.RemoveRouteRequestForDestination(Destination.Key); } } - - /*public class RouteRequest - { - public IRoutingSink Destination { get; set; } - public IRoutingOutputs Source { get; set; } - public eRoutingSignalType SignalType { get; set; } - - public void HandleCooldown(object sender, FeedbackEventArgs args) - { - var coolingDevice = sender as IWarmingCooling; - - if (args.BoolValue == false) - { - Destination.ReleaseAndMakeRoute(Source, SignalType); - - if (sender == null) return; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; - } - } - }*/ } \ No newline at end of file From d970d806c9ff7bb9a79e0448260508e884e2ddae Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 19 Jul 2024 13:09:47 -0500 Subject: [PATCH 18/26] fix: add mutex to prevent multiple scenarios from running at once --- .../Room/Combining/EssentialsRoomCombiner.cs | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index df50e864..6b2d8615 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -5,6 +5,7 @@ using Serilog.Events; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace PepperDash.Essentials.Core { @@ -48,6 +49,8 @@ namespace PepperDash.Essentials.Core private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s + private Mutex _scenarioChange = new Mutex(); + public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props) : base(key) { @@ -205,30 +208,39 @@ namespace PepperDash.Essentials.Core } private set { - if (value != _currentScenario) + try { - // Deactivate the old scenario first - if (_currentScenario != null) + _scenarioChange.WaitOne(); + + if (value != _currentScenario) { - _currentScenario.Deactivate(); - } - - _currentScenario = value; - - // Activate the new scenario - if (_currentScenario != null) - { - _currentScenario.Activate(); - - Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this); - } - - var handler = RoomCombinationScenarioChanged; - if (handler != null) - { - handler(this, new EventArgs()); + // Deactivate the old scenario first + if (_currentScenario != null) + { + _currentScenario.Deactivate(); + } + + _currentScenario = value; + + // Activate the new scenario + if (_currentScenario != null) + { + _currentScenario.Activate(); + + Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this); + } + + var handler = RoomCombinationScenarioChanged; + if (handler != null) + { + handler(this, new EventArgs()); + } } } + finally + { + _scenarioChange.ReleaseMutex(); + } } } From 64d6df70b01640c7862b36616823b3151e4c7d35 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 22 Jul 2024 11:19:08 -0500 Subject: [PATCH 19/26] fix: deactivate all rooms on startup As part of the Essentials startup process, ALL rooms are activated, meaning there are unnecessary rooms activated. Deactiving them all prior to determining a combination scenario helps keep unecessary activity from happening. --- .../Room/Combining/EssentialsRoomCombiner.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index 6b2d8615..9c8bfa49 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -110,11 +110,20 @@ namespace PepperDash.Essentials.Core foreach (var roomKey in _propertiesConfig.RoomKeys) { - if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom room) + var room = DeviceManager.GetDeviceForKey(roomKey); + + if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom essentialsRoom) { - _rooms.Add(room); + _rooms.Add(essentialsRoom); } } + + var rooms = DeviceManager.AllDevices.OfType().Cast(); + + foreach (var room in rooms) + { + room.Deactivate(); + } } private void SetupPartitionStateProviders() From c56841d95b2d9ed3871f2d05b6d787660feb927d Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 22 Jul 2024 11:19:34 -0500 Subject: [PATCH 20/26] fix: attempt to catch a null ref happening in the Route Descriptor --- .../Routing/RouteDescriptorCollection.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs index c9e3da37..8792a123 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; -using System.Linq; - -using PepperDash.Core; +using PepperDash.Core; using Serilog.Events; +using System.Collections.Generic; +using System.Linq; namespace PepperDash.Essentials.Core @@ -33,6 +32,11 @@ namespace PepperDash.Essentials.Core /// public void AddRouteDescriptor(RouteDescriptor descriptor) { + if (descriptor == null) + { + return; + } + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) { Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, From 97bd30e9c9f64d98c274ed23d8dff9471363aaf0 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 26 Jul 2024 06:47:13 -0500 Subject: [PATCH 21/26] fix: add async/await patterns for activation/deactivation --- .../Devices/DeviceJsonApi.cs | 3 - .../Room/Combining/EssentialsRoomCombiner.cs | 69 +++++++++---------- .../Room/Combining/IEssentialsRoomCombiner.cs | 5 +- .../Room/Combining/RoomCombinationScenario.cs | 25 ++++--- 4 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs index 02a31e98..150313e7 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs @@ -153,9 +153,6 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); } }); - - Debug.LogMessage(LogEventLevel.Information, "Method {methodName} successfully called on device {deviceKey} with {@params}", null, method.Name, - action.DeviceKey, action.Params); } catch (Exception ex) { diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index 9c8bfa49..11086821 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace PepperDash.Essentials.Core { @@ -201,10 +202,40 @@ namespace PepperDash.Essentials.Core if (currentScenario != null) { this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key); - CurrentScenario = currentScenario; + ChangeScenario(currentScenario); } } + private async Task ChangeScenario(IRoomCombinationScenario newScenario) + { + + + if (newScenario == _currentScenario) + { + return; + } + + // Deactivate the old scenario first + if (_currentScenario != null) + { + Debug.LogMessage(LogEventLevel.Information, "Deactivating scenario {currentScenario}", this, _currentScenario.Name); + await _currentScenario.Deactivate(); + } + + _currentScenario = newScenario; + + // Activate the new scenario + if (_currentScenario != null) + { + Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this); + await _currentScenario.Activate(); + } + + RoomCombinationScenarioChanged?.Invoke(this, new EventArgs()); + + + } + #region IEssentialsRoomCombiner Members public event EventHandler RoomCombinationScenarioChanged; @@ -215,42 +246,6 @@ namespace PepperDash.Essentials.Core { return _currentScenario; } - private set - { - try - { - _scenarioChange.WaitOne(); - - if (value != _currentScenario) - { - // Deactivate the old scenario first - if (_currentScenario != null) - { - _currentScenario.Deactivate(); - } - - _currentScenario = value; - - // Activate the new scenario - if (_currentScenario != null) - { - _currentScenario.Activate(); - - Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this); - } - - var handler = RoomCombinationScenarioChanged; - if (handler != null) - { - handler(this, new EventArgs()); - } - } - } - finally - { - _scenarioChange.ReleaseMutex(); - } - } } public BoolFeedback IsInAutoModeFeedback { get; private set; } diff --git a/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs index 0d4a40cc..fefdc2da 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Newtonsoft.Json; using PepperDash.Core; @@ -87,12 +88,12 @@ namespace PepperDash.Essentials.Core /// /// Activates this room combination scenario /// - void Activate(); + Task Activate(); /// /// Deactivates this room combination scenario /// - void Deactivate(); + Task Deactivate(); /// /// The state of the partitions that would activate this scenario diff --git a/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs b/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs index 84674d02..f36b807c 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/RoomCombinationScenario.cs @@ -3,6 +3,7 @@ using PepperDash.Core; using PepperDash.Core.Logging; using Serilog.Events; using System.Collections.Generic; +using System.Threading.Tasks; namespace PepperDash.Essentials.Core { @@ -69,35 +70,43 @@ namespace PepperDash.Essentials.Core IsActiveFeedback = new BoolFeedback(() => _isActive); } - public void Activate() + public async Task Activate() { - Debug.LogMessage(LogEventLevel.Debug, "Activating Scenario: '{name}' with {activationActionCount} action(s) defined", this, Name, activationActions.Count); + this.LogInformation("Activating Scenario {name} with {activationActionCount} action(s) defined", Name, activationActions.Count); + + List tasks = new List(); if (activationActions != null) { foreach (var action in activationActions) { - this.LogDebug("Running Activation action {@action}", action); - DeviceJsonApi.DoDeviceAction(action); + this.LogInformation("Running Activation action {@action}", action); + tasks.Add(DeviceJsonApi.DoDeviceActionAsync(action)); } } + await Task.WhenAll(tasks); + IsActive = true; } - public void Deactivate() + public async Task Deactivate() { - Debug.LogMessage(LogEventLevel.Debug, "Deactivating Scenario: '{name}' with {deactivationActionCount} action(s) defined", this, Name, deactivationActions.Count); + this.LogInformation("Deactivating Scenario {name} with {deactivationActionCount} action(s) defined", Name, deactivationActions.Count); + + List tasks = new List(); if (deactivationActions != null) { foreach (var action in deactivationActions) { - this.LogDebug("Running deactivation action {@action}", action); - DeviceJsonApi.DoDeviceAction(action); + this.LogInformation("Running deactivation action {actionDeviceKey}:{actionMethod}", action.DeviceKey, action.MethodName); + tasks.Add( DeviceJsonApi.DoDeviceActionAsync(action)); } } + await Task.WhenAll(tasks); + IsActive = false; } From 7a263a644a8be8d96a289f94de49058d7d5e7333 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 26 Jul 2024 06:47:48 -0500 Subject: [PATCH 22/26] fix: devcommstatus response now prints correctly --- .../Devices/DeviceManager.cs | 538 +++++++++--------- 1 file changed, 265 insertions(+), 273 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs index e92d11c5..0552faeb 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs @@ -1,50 +1,49 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; - using PepperDash.Core; using Serilog.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; namespace PepperDash.Essentials.Core { - public static class DeviceManager - { + public static class DeviceManager + { public static event EventHandler AllDevicesActivated; public static event EventHandler AllDevicesRegistered; - private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); - private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); - //public static List Devices { get { return _Devices; } } - //static List _Devices = new List(); + private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); + private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); - static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); + //public static List Devices { get { return _Devices; } } + //static List _Devices = new List(); - /// - /// Returns a copy of all the devices in a list - /// - public static List AllDevices { get { return new List(Devices.Values); } } + private static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); - public static bool AddDeviceEnabled; + /// + /// Returns a copy of all the devices in a list + /// + public static List AllDevices { get { return new List(Devices.Values); } } - public static void Initialize(CrestronControlSystem cs) - { - AddDeviceEnabled = true; - CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator); + public static bool AddDeviceEnabled; + + public static void Initialize(CrestronControlSystem cs) + { + AddDeviceEnabled = true; + CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive", "Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator); @@ -52,77 +51,77 @@ namespace PepperDash.Essentials.Core CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator); } - /// - /// Calls activate steps on all Device class items - /// - public static void ActivateAll() - { - try - { + /// + /// Calls activate steps on all Device class items + /// + public static void ActivateAll() + { + try + { OnAllDevicesRegistered(); - DeviceCriticalSection.Enter(); + DeviceCriticalSection.Enter(); AddDeviceEnabled = false; - // PreActivate all devices - Debug.LogMessage(LogEventLevel.Information,"****PreActivation starting...****"); - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).PreActivate(); - } - catch (Exception e) - { + // PreActivate all devices + Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****"); + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).PreActivate(); + } + catch (Exception e) + { Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key); Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } + } + } Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****"); - Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****"); + Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****"); - // Activate all devices - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).Activate(); - } - catch (Exception e) - { + // Activate all devices + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).Activate(); + } + catch (Exception e) + { Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key); Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } + } + } Debug.LogMessage(LogEventLevel.Information, "****Activation complete****"); Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****"); - // PostActivate all devices - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).PostActivate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } + // PostActivate all devices + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).PostActivate(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); + } + } Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****"); OnAllDevicesActivated(); - } - finally - { + } + finally + { DeviceCriticalSection.Leave(); - } - } + } + } private static void OnAllDevicesActivated() { @@ -142,77 +141,76 @@ namespace PepperDash.Essentials.Core } } - /// - /// Calls activate on all Device class items - /// - public static void DeactivateAll() - { - try - { - DeviceCriticalSection.Enter(); - foreach (var d in Devices.Values.OfType()) - { - d.Deactivate(); - } - } - finally - { - DeviceCriticalSection.Leave(); - } - } + /// + /// Calls activate on all Device class items + /// + public static void DeactivateAll() + { + try + { + DeviceCriticalSection.Enter(); + foreach (var d in Devices.Values.OfType()) + { + d.Deactivate(); + } + } + finally + { + DeviceCriticalSection.Leave(); + } + } - //static void ListMethods(string devKey) - //{ - // var dev = GetDeviceForKey(devKey); - // if(dev != null) - // { - // var type = dev.GetType().GetType(); - // var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance); - // var sb = new StringBuilder(); - // sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length)); - // foreach (var m in methods) - // { - // sb.Append(string.Format("{0}(", m.Name)); - // var pars = m.GetParameters(); - // foreach (var p in pars) - // sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name)); - // sb.AppendLine(")"); - // } - // CrestronConsole.ConsoleCommandResponse(sb.ToString()); - // } - //} + //static void ListMethods(string devKey) + //{ + // var dev = GetDeviceForKey(devKey); + // if(dev != null) + // { + // var type = dev.GetType().GetType(); + // var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance); + // var sb = new StringBuilder(); + // sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length)); + // foreach (var m in methods) + // { + // sb.Append(string.Format("{0}(", m.Name)); + // var pars = m.GetParameters(); + // foreach (var p in pars) + // sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name)); + // sb.AppendLine(")"); + // } + // CrestronConsole.ConsoleCommandResponse(sb.ToString()); + // } + //} - private static void ListDevices(string s) - { - Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count); - var sorted = Devices.Values.ToList(); - sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); + private static void ListDevices(string s) + { + Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count); + var sorted = Devices.Values.ToList(); + sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); - foreach (var d in sorted) - { - var name = d is IKeyName ? (d as IKeyName).Name : "---"; - Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name); - } - } + foreach (var d in sorted) + { + var name = d is IKeyName ? (d as IKeyName).Name : "---"; + Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name); + } + } - private static void ListDeviceFeedbacks(string devKey) - { - var dev = GetDeviceForKey(devKey); - if (dev == null) - { - Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); - return; - } - var statusDev = dev as IHasFeedback; - if (statusDev == null) - { - Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey); - return; - } - statusDev.DumpFeedbacksToConsole(true); - } + private static void ListDeviceFeedbacks(string devKey) + { + var dev = GetDeviceForKey(devKey); + if (dev == null) + { + Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); + return; + } + if (!(dev is IHasFeedback statusDev)) + { + Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey); + return; + } + statusDev.DumpFeedbacksToConsole(true); + } - //static void ListDeviceCommands(string devKey) + //static void ListDeviceCommands(string devKey) //{ // var dev = GetDeviceForKey(devKey); // if (dev == null) @@ -223,134 +221,132 @@ namespace PepperDash.Essentials.Core // Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey); //} - private static void ListDeviceCommStatuses(string input) - { - var sb = new StringBuilder(); - foreach (var dev in Devices.Values.OfType()) - { - sb.Append(string.Format("{0}: {1}\r", dev, - dev.CommunicationMonitor.Status)); - } - CrestronConsole.ConsoleCommandResponse(sb.ToString()); - } + private static void ListDeviceCommStatuses(string input) + { + + foreach (var dev in Devices.Values.OfType()) + { + CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}{Environment.NewLine}"); + } + } - //static void DoDeviceCommand(string command) + //static void DoDeviceCommand(string command) //{ // Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned"); //} - public static void AddDevice(IKeyed newDev) - { - try - { - if (!DeviceCriticalSection.TryEnter()) - { - Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again"); - return; - } - // Check for device with same key - //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); - ////// If it exists, remove or warn?? - //if (existingDevice != null) + public static void AddDevice(IKeyed newDev) + { + try + { + if (!DeviceCriticalSection.TryEnter()) + { + Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again"); + return; + } + // Check for device with same key + //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); + ////// If it exists, remove or warn?? + //if (existingDevice != null) - if (!AddDeviceEnabled) - { - Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed."); - return; - } + if (!AddDeviceEnabled) + { + Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed."); + return; + } - if (Devices.ContainsKey(newDev.Key)) - { - Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager"); - return; - } - Devices.Add(newDev.Key, newDev); - //if (!(_Devices.Contains(newDev))) - // _Devices.Add(newDev); - } - finally - { - DeviceCriticalSection.Leave(); - } - } + if (Devices.ContainsKey(newDev.Key)) + { + Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager"); + return; + } + Devices.Add(newDev.Key, newDev); + //if (!(_Devices.Contains(newDev))) + // _Devices.Add(newDev); + } + finally + { + DeviceCriticalSection.Leave(); + } + } - public static void AddDevice(IEnumerable devicesToAdd) - { - try - { - if (!DeviceCriticalSection.TryEnter()) - { - Debug.LogMessage(LogEventLevel.Information, - "Currently unable to add devices to Device Manager. Please try again"); - return; - } - if (!AddDeviceEnabled) - { - Debug.LogMessage(LogEventLevel.Information, - "All devices have been activated. Adding new devices is not allowed."); - return; - } + public static void AddDevice(IEnumerable devicesToAdd) + { + try + { + if (!DeviceCriticalSection.TryEnter()) + { + Debug.LogMessage(LogEventLevel.Information, + "Currently unable to add devices to Device Manager. Please try again"); + return; + } + if (!AddDeviceEnabled) + { + Debug.LogMessage(LogEventLevel.Information, + "All devices have been activated. Adding new devices is not allowed."); + return; + } - foreach (var dev in devicesToAdd) - { - try - { - Devices.Add(dev.Key, dev); - } - catch (ArgumentException ex) - { - Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}", - dev.Key, ex.Message, ex.StackTrace); - } - } - } - finally - { - DeviceCriticalSection.Leave(); - } - } + foreach (var dev in devicesToAdd) + { + try + { + Devices.Add(dev.Key, dev); + } + catch (ArgumentException ex) + { + Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}", + dev.Key, ex.Message, ex.StackTrace); + } + } + } + finally + { + DeviceCriticalSection.Leave(); + } + } - public static void RemoveDevice(IKeyed newDev) - { - try - { + public static void RemoveDevice(IKeyed newDev) + { + try + { DeviceCriticalSection.Enter(); - if (newDev == null) - return; - if (Devices.ContainsKey(newDev.Key)) - Devices.Remove(newDev.Key); - //if (_Devices.Contains(newDev)) - // _Devices.Remove(newDev); - else - Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); - } - finally - { - DeviceCriticalSection.Leave(); - } - } + if (newDev == null) + return; + if (Devices.ContainsKey(newDev.Key)) + Devices.Remove(newDev.Key); + //if (_Devices.Contains(newDev)) + // _Devices.Remove(newDev); + else + Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); + } + finally + { + DeviceCriticalSection.Leave(); + } + } - public static IEnumerable GetDeviceKeys() - { - //return _Devices.Select(d => d.Key).ToList(); - return Devices.Keys; - } + public static IEnumerable GetDeviceKeys() + { + //return _Devices.Select(d => d.Key).ToList(); + return Devices.Keys; + } - public static IEnumerable GetDevices() - { - //return _Devices.Select(d => d.Key).ToList(); - return Devices.Values; - } + public static IEnumerable GetDevices() + { + //return _Devices.Select(d => d.Key).ToList(); + return Devices.Values; + } - public static IKeyed GetDeviceForKey(string key) - { - //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); - if (key != null && Devices.ContainsKey(key)) - return Devices[key]; + public static IKeyed GetDeviceForKey(string key) + { + //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key != null && Devices.ContainsKey(key)) + return Devices[key]; - return null; - } + return null; + } /// /// Console handler that simulates com port data receive @@ -367,8 +363,7 @@ namespace PepperDash.Essentials.Core } //Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value); - var com = GetDeviceForKey(match.Groups[1].Value) as ComPortController; - if (com == null) + if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com)) { CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value); return; @@ -423,16 +418,15 @@ namespace PepperDash.Essentials.Core var deviceKey = args[0]; var setting = args[1]; - var timeout= String.Empty; + var timeout = String.Empty; if (args.Length >= 3) { timeout = args[2]; } - var device = GetDeviceForKey(deviceKey) as IStreamDebugging; - if (device == null) + if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device)) { CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey); return; @@ -479,13 +473,11 @@ namespace PepperDash.Essentials.Core { foreach (var device in AllDevices) { - var streamDevice = device as IStreamDebugging; - - if (streamDevice != null) + if (device is IStreamDebugging streamDevice) { streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off); } } } - } + } } \ No newline at end of file From e374f7b50f38b7e8cd20ac9eb6cead074739ba3f Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 26 Jul 2024 06:48:23 -0500 Subject: [PATCH 23/26] fix: add some options to destination type enum --- .../Devices/SourceListItem.cs | 169 +++++++++--------- 1 file changed, 83 insertions(+), 86 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index 48d43c94..6de2f35d 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -1,128 +1,121 @@ - - -using System; -using System.Collections.Generic; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; - -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; using PepperDash.Core; +using System.Collections.Generic; namespace PepperDash.Essentials.Core { - /// - /// - /// - public enum eSourceListItemType - { - Route, Off, Other, SomethingAwesomerThanThese - } + /// + /// + /// + public enum eSourceListItemType + { + Route, Off, Other, SomethingAwesomerThanThese + } - /// - /// Represents an item in a source list - can be deserialized into. - /// - public class SourceListItem - { - [JsonProperty("sourceKey")] - public string SourceKey { get; set; } + /// + /// Represents an item in a source list - can be deserialized into. + /// + public class SourceListItem + { + [JsonProperty("sourceKey")] + public string SourceKey { get; set; } - /// - /// Returns the source Device for this, if it exists in DeviceManager - /// - [JsonIgnore] - public Device SourceDevice - { - get - { - if (_SourceDevice == null) - _SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device; - return _SourceDevice; - } - } - Device _SourceDevice; + /// + /// Returns the source Device for this, if it exists in DeviceManager + /// + [JsonIgnore] + public Device SourceDevice + { + get + { + if (_SourceDevice == null) + _SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device; + return _SourceDevice; + } + } - /// - /// Gets either the source's Name or this AlternateName property, if - /// defined. If source doesn't exist, returns "Missing source" - /// - [JsonProperty("preferredName")] - public string PreferredName - { - get - { - if (string.IsNullOrEmpty(Name)) - { - if (SourceDevice == null) - return "---"; - return SourceDevice.Name; - } - return Name; - } - } + private Device _SourceDevice; - /// - /// A name that will override the source's name on the UI - /// - [JsonProperty("name")] - public string Name { get; set; } + /// + /// Gets either the source's Name or this AlternateName property, if + /// defined. If source doesn't exist, returns "Missing source" + /// + [JsonProperty("preferredName")] + public string PreferredName + { + get + { + if (string.IsNullOrEmpty(Name)) + { + if (SourceDevice == null) + return "---"; + return SourceDevice.Name; + } + return Name; + } + } + + /// + /// A name that will override the source's name on the UI + /// + [JsonProperty("name")] + public string Name { get; set; } /// /// Specifies and icon for the source list item /// [JsonProperty("icon")] - public string Icon { get; set; } + public string Icon { get; set; } /// /// Alternate icon /// [JsonProperty("altIcon")] - public string AltIcon { get; set; } + public string AltIcon { get; set; } /// /// Indicates if the item should be included in the source list /// [JsonProperty("includeInSourceList")] - public bool IncludeInSourceList { get; set; } + public bool IncludeInSourceList { get; set; } /// /// Used to specify the order of the items in the source list when displayed /// [JsonProperty("order")] - public int Order { get; set; } + public int Order { get; set; } /// /// The key of the device for volume control /// [JsonProperty("volumeControlKey")] - public string VolumeControlKey { get; set; } + public string VolumeControlKey { get; set; } /// /// The type of source list item /// [JsonProperty("type")] - [JsonConverter(typeof(StringEnumConverter))] - public eSourceListItemType Type { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public eSourceListItemType Type { get; set; } /// /// The list of routes to execute for this source list item /// [JsonProperty("routeList")] - public List RouteList { get; set; } + public List RouteList { get; set; } /// /// Indicates if this source should be disabled for sharing to the far end call participants via codec content /// [JsonProperty("disableCodecSharing")] - public bool DisableCodecSharing { get; set; } + public bool DisableCodecSharing { get; set; } /// /// Indicates if this source should be disabled for routing to a shared output /// [JsonProperty("disableRoutedSharing")] - public bool DisableRoutedSharing { get; set; } + public bool DisableRoutedSharing { get; set; } [JsonProperty("destinations")] public List Destinations { get; set; } @@ -156,10 +149,10 @@ namespace PepperDash.Essentials.Core [JsonProperty("disableSimpleRouting")] public bool DisableSimpleRouting { get; set; } - public SourceListItem() - { - Icon = "Blank"; - } + public SourceListItem() + { + Icon = "Blank"; + } public override string ToString() { @@ -167,23 +160,23 @@ namespace PepperDash.Essentials.Core } } - public class SourceRouteListItem - { - [JsonProperty("sourceKey")] - public string SourceKey { get; set; } + public class SourceRouteListItem + { + [JsonProperty("sourceKey")] + public string SourceKey { get; set; } [JsonProperty("sourcePortKey")] public string SourcePortKey { get; set; } - [JsonProperty("destinationKey")] - public string DestinationKey { get; set; } + [JsonProperty("destinationKey")] + public string DestinationKey { get; set; } [JsonProperty("destinationPortKey")] public string DestinationPortKey { get; set; } - [JsonProperty("type")] - public eRoutingSignalType Type { get; set; } - } + [JsonProperty("type")] + public eRoutingSignalType Type { get; set; } + } /// /// Defines the valid destination types for SourceListItems in a room @@ -193,8 +186,12 @@ namespace PepperDash.Essentials.Core defaultDisplay, leftDisplay, rightDisplay, - centerDisplay, + centerDisplay, programAudio, - codecContent + codecContent, + frontLeftDisplay, + frontRightDisplay, + rearLeftDisplay, + rearRightDisplay, } } \ No newline at end of file From 1fb1947158ee80bc04877c6a4d35165d9c5d9ba5 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 26 Jul 2024 06:51:45 -0500 Subject: [PATCH 24/26] fix: ReleaseRoute callse `ExecuteSwitch` with null for input selector Most devices that implement IRouting will now need to handle the possiblity of a null as the input selector, with the idea being that a null input selector should clear the route to whatever device is selected as the input selector. This may change to a typed value in the future. --- src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs index ebacc809..3abafae4 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs @@ -60,10 +60,12 @@ namespace PepperDash.Essentials.Core /// public void ReleaseRoutes() { - foreach (var route in Routes) + foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting)) { - if (route.SwitchingDevice is IRouting) + if (route.SwitchingDevice is IRouting switchingDevice) { + switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType); + // Pull the route from the port. Whatever is watching the output's in use tracker is // responsible for responding appropriately. route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); From ab73bbf97982b91843482bb318c5335f1260081f Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 26 Jul 2024 06:53:18 -0500 Subject: [PATCH 25/26] fix: remove call to remove routerequest The routerequest is being removed if the route is successfully made after the display has cooled down. This was an extraneous removal --- src/PepperDash.Essentials.Core/Routing/RouteRequest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs index f8679b9d..0f51d174 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -29,8 +29,6 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Debug, "Unsubscribing from cooling feedback for {destination}", null, Destination.Key); coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; } - - Extensions.RemoveRouteRequestForDestination(Destination.Key); } } } \ No newline at end of file From f7c5e18af859ce57c1cc3648c86a0b79968bb723 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 26 Jul 2024 11:19:43 -0500 Subject: [PATCH 26/26] fix: add input port matching to route descriptors --- .../Routing/Extensions.cs | 21 ++++++++++++------ .../Routing/RouteDescriptor.cs | 18 ++++++++++----- .../Routing/RouteDescriptorCollection.cs | 22 ++++++++++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 66c0dac3..6d3a98fd 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -97,7 +97,7 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); } - destination.ReleaseRoute(); + destination.ReleaseRoute(destinationPort?.Key ?? string.Empty); RunRouteRequest(routeRequest); } @@ -125,14 +125,21 @@ namespace PepperDash.Essentials.Core videoRoute?.ExecuteRoutes(); } + public static void ReleaseRoute(this IRoutingInputs destination) + { + ReleaseRoute(destination, string.Empty); + } + /// /// Will release the existing route on the destination, if it is found in /// RouteDescriptorCollection.DefaultCollection /// - /// - public static void ReleaseRoute(this IRoutingInputs destination) + /// + public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey) { + Debug.LogMessage(LogEventLevel.Information, "Release route for {inputPortKey}", destination, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) { var coolingDevice = destination as IWarmingCooling; @@ -142,7 +149,7 @@ namespace PepperDash.Essentials.Core RouteRequests.Remove(destination.Key); - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey); if (current != null) { Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); @@ -162,7 +169,7 @@ namespace PepperDash.Essentials.Core // if it's a single signal type, find the route if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) { - var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, signalType); + var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType); Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType); if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort)) @@ -179,14 +186,14 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key); - var audioRouteDescriptor = new RouteDescriptor(source, destination, eRoutingSignalType.Audio); + var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio); var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort); if (!audioSuccess) Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); - var videoRouteDescriptor = new RouteDescriptor(source, destination, eRoutingSignalType.Video); + var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Video); var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort); diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs index 3abafae4..fa486c07 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs @@ -14,19 +14,27 @@ namespace PepperDash.Essentials.Core public class RouteDescriptor { public IRoutingInputs Destination { get; private set; } + + public RoutingInputPort InputPort { get; private set; } + public IRoutingOutputs Source { get; private set; } public eRoutingSignalType SignalType { get; private set; } public List Routes { get; private set; } - public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType):this(source,destination, null, signalType) { - Destination = destination; - Source = source; - SignalType = signalType; - Routes = new List(); } + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType) + { + Destination = destination; + Source = source; + SignalType = signalType; + InputPort = inputPort; + Routes = new List(); + } + /// /// Executes all routes described in this collection. Typically called via /// extension method IRoutingInputs.ReleaseAndMakeRoute() diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs index 8792a123..6c4a5df5 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs @@ -37,7 +37,8 @@ namespace PepperDash.Essentials.Core return; } - if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination) + && RouteDescriptors.Any(t => t.Destination == descriptor.Destination && t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)) { Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, "Route to [{0}] already exists in global routes table", descriptor.Source.Key); @@ -52,18 +53,33 @@ namespace PepperDash.Essentials.Core /// null if no RouteDescriptor for a destination exists public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) { + Debug.LogMessage(LogEventLevel.Debug, "Getting route descriptor", destination); + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); } + public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey) + { + Debug.LogMessage(LogEventLevel.Debug, "Getting route descriptor for {inputPortKey}", destination, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination && rd.InputPort != null && rd.InputPort.Key == inputPortKey); + } + /// /// Returns the RouteDescriptor for a given destination AND removes it from collection. /// Returns null if no route with the provided destination exists. /// - public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string inputPortKey = "") { - var descr = GetRouteDescriptorForDestination(destination); + Debug.LogMessage(LogEventLevel.Debug, "Removing route descriptor for {inputPortKey}", destination, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + + var descr = string.IsNullOrEmpty(inputPortKey) + ? GetRouteDescriptorForDestination(destination) + : GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey); if (descr != null) RouteDescriptors.Remove(descr); + + Debug.LogMessage(LogEventLevel.Debug, "Found route descriptor {routeDescriptor}", destination, descr); + return descr; } }