From f006ed0076f7a3aa96b796a3a28e245b5e553027 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Wed, 16 Jul 2025 16:54:57 -0600 Subject: [PATCH 1/6] feat: Add camera control interfaces and messenger classes This commit introduces new interfaces in `CameraControl.cs` for various camera functionalities, including muting, panning, tilting, zooming, and auto modes. The `IHasCameras` interface is expanded to manage camera lists and selections, with the addition of `CameraSelectedEventArgs` for event handling. In `CameraBaseMessenger.cs`, a new `IHasCamerasMessenger` class is created to facilitate communication for devices implementing the `IHasCameras` interface, along with the `IHasCamerasStateMessage` class to represent the state of camera devices. These changes enhance the overall camera control capabilities and improve interaction management within the application. --- .../Cameras/CameraControl.cs | 166 +++++++++++++++++- .../Messengers/CameraBaseMessenger.cs | 8 +- .../Messengers/IHasCamerasMessenger.cs | 104 +++++++++++ 3 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs index cbf53476..3212c186 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs @@ -3,29 +3,60 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp; - +using PepperDash.Core; using PepperDash.Essentials.Core; namespace PepperDash.Essentials.Devices.Common.Cameras { + /// + /// Enum for camera control modes + /// public enum eCameraControlMode - { + { + /// + /// Manual control mode, where the camera is controlled directly by the user or system + /// Manual = 0, + /// + /// Off control mode, where the camera is turned off or disabled + /// Off, + /// + /// Auto control mode, where the camera automatically adjusts settings based on the environment or conditions + /// Auto } - public interface IHasCameras + /// + /// Interface for devices that have cameras + /// + public interface IHasCameras : IKeyName { + /// + /// Event that is raised when a camera is selected + /// event EventHandler CameraSelected; + /// + /// List of cameras on the device. This should be a list of CameraBase objects + /// List Cameras { get; } + /// + /// The currently selected camera. This should be a CameraBase object + /// CameraBase SelectedCamera { get; } + /// + /// Feedback that indicates the currently selected camera + /// StringFeedback SelectedCameraFeedback { get; } + /// + /// + /// + /// void SelectCamera(string key); } @@ -42,7 +73,14 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// public interface IHasCameraOff { + /// + /// Feedback that indicates whether the camera is off + /// BoolFeedback CameraIsOffFeedback { get; } + + /// + /// Turns the camera off, blanking the near end video + /// void CameraOff(); } @@ -51,31 +89,71 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// public interface IHasCameraMute { + /// + /// Feedback that indicates whether the camera is muted + /// BoolFeedback CameraIsMutedFeedback { get; } + + /// + /// Mutes the camera video, preventing it from being sent to the far end + /// void CameraMuteOn(); + + /// + /// Unmutes the camera video, allowing it to be sent to the far end + /// void CameraMuteOff(); + + /// + /// Toggles the camera mute state. If the camera is muted, it will be unmuted, and vice versa. + /// void CameraMuteToggle(); } + /// + /// Interface for devices that can mute and unmute their camera video, with an event for unmute requests + /// public interface IHasCameraMuteWithUnmuteReqeust : IHasCameraMute { + /// + /// Event that is raised when a video unmute is requested, typically by the far end + /// event EventHandler VideoUnmuteRequested; } + /// + /// Event arguments for the CameraSelected event + /// public class CameraSelectedEventArgs : EventArgs { + /// + /// The selected camera + /// public CameraBase SelectedCamera { get; private set; } + /// + /// Constructor for CameraSelectedEventArgs + /// + /// public CameraSelectedEventArgs(CameraBase camera) { SelectedCamera = camera; } } + /// + /// Interface for devices that have a far end camera control + /// public interface IHasFarEndCameraControl { + /// + /// Gets the far end camera, which is typically a CameraBase object that represents the camera at the far end of a call + /// CameraBase FarEndCamera { get; } + /// + /// Feedback that indicates whether the far end camera is being controlled + /// BoolFeedback ControllingFarEndCameraFeedback { get; } } @@ -88,6 +166,9 @@ namespace PepperDash.Essentials.Devices.Common.Cameras } + /// + /// Interface for devices that have camera controls + /// public interface IHasCameraControls { } @@ -108,8 +189,19 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// public interface IHasCameraPanControl : IHasCameraControls { + /// + /// Pans the camera left + /// void PanLeft(); + + /// + /// Pans the camera right + /// void PanRight(); + + /// + /// Stops the camera pan movement + /// void PanStop(); } @@ -118,8 +210,19 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// public interface IHasCameraTiltControl : IHasCameraControls { + /// + /// Tilts the camera down + /// void TiltDown(); + + /// + /// Tilts the camera up + /// void TiltUp(); + + /// + /// Stops the camera tilt movement + /// void TiltStop(); } @@ -128,8 +231,19 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// public interface IHasCameraZoomControl : IHasCameraControls { + /// + /// Zooms the camera in + /// void ZoomIn(); + + /// + /// Zooms the camera out + /// void ZoomOut(); + + /// + /// Stops the camera zoom movement + /// void ZoomStop(); } @@ -138,25 +252,71 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// public interface IHasCameraFocusControl : IHasCameraControls { + /// + /// Focuses the camera near + /// void FocusNear(); + + /// + /// Focuses the camera far + /// void FocusFar(); + + /// + /// Stops the camera focus movement + /// void FocusStop(); + /// + /// Triggers the camera's auto focus functionality, if available. + /// void TriggerAutoFocus(); } + /// + /// Interface for devices that have auto focus mode control + /// public interface IHasAutoFocusMode { + /// + /// Sets the focus mode to auto or manual, or toggles between them. + /// void SetFocusModeAuto(); + + /// + /// Sets the focus mode to manual, allowing for manual focus adjustments. + /// void SetFocusModeManual(); + + /// + /// Toggles the focus mode between auto and manual. + /// void ToggleFocusMode(); } + /// + /// Interface for devices that have camera auto mode control + /// public interface IHasCameraAutoMode : IHasCameraControls { + /// + /// Enables or disables the camera's auto mode, which may include automatic adjustments for focus, exposure, and other settings. + /// void CameraAutoModeOn(); + + /// + /// Disables the camera's auto mode, allowing for manual control of camera settings. + /// void CameraAutoModeOff(); + + /// + /// Toggles the camera's auto mode state. If the camera is in auto mode, it will switch to manual mode, and vice versa. + /// void CameraAutoModeToggle(); + + /// + /// Feedback that indicates whether the camera's auto mode is currently enabled. + /// BoolFeedback CameraAutoModeIsOnFeedback { get; } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs index 36a94781..dd67a6fe 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs @@ -6,11 +6,14 @@ using System.Collections.Generic; namespace PepperDash.Essentials.AppServer.Messengers { + /// + /// Messenger for a CameraBase device + /// public class CameraBaseMessenger : MessengerBase { /// /// Device being bridged - /// + /// public CameraBase Camera { get; set; } /// @@ -45,6 +48,9 @@ namespace PepperDash.Essentials.AppServer.Messengers ); } + /// + /// Registers the actions for this messenger. This is called by the base class + /// protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs new file mode 100644 index 00000000..6245f037 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs @@ -0,0 +1,104 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Essentials.Devices.Common.Cameras; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Messenger for devices that implement the IHasCameras interface. + /// + public class IHasCamerasMessenger : MessengerBase + { + /// + /// Device being bridged that implements IHasCameras interface. + /// + public IHasCameras CameraController { get; private set; } + + /// + /// Messenger for devices that implement IHasCameras interface. + /// + /// + /// + /// + /// + public IHasCamerasMessenger(string key, IHasCameras cameraController, string messagePath) + : base(key, messagePath, cameraController) + { + CameraController = cameraController ?? throw new ArgumentNullException("cameraController"); + CameraController.CameraSelected += CameraController_CameraSelected; + } + + private void CameraController_CameraSelected(object sender, CameraSelectedEventArgs e) + { + PostStatusMessage(new IHasCamerasStateMessage + { + SelectedCamera = e.SelectedCamera + }); + } + + /// + /// Registers the actions for this messenger. + /// + /// + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, context) => + { + SendFullStatus(); + }); + + AddAction("/selectCamera", (id, content) => + { + var cameraKey = content?.ToObject(); + + if (!string.IsNullOrEmpty(cameraKey)) + { + CameraController.SelectCamera(cameraKey); + } + else + { + throw new ArgumentException("Content must be a string representing the camera key"); + } + }); + } + + private void SendFullStatus() + { + var state = new IHasCamerasStateMessage + { + CameraList = CameraController.Cameras, + SelectedCamera = CameraController.SelectedCamera + }; + + PostStatusMessage(state); + } + + + } + + /// + /// State message for devices that implement the IHasCameras interface. + /// + public class IHasCamerasStateMessage : DeviceStateMessageBase + { + /// + /// List of cameras available in the device. + /// + [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] + public List CameraList { get; set; } + + /// + /// The currently selected camera on the device. + /// + [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] + public CameraBase SelectedCamera { get; set; } + + } +} From f8455d4110cc616373e6f43c3b57bf90faee71aa Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 17 Jul 2025 11:14:32 -0600 Subject: [PATCH 2/6] feat: Refactor IHasCamerasMessenger constructor parameters Updated the constructor of `IHasCamerasMessenger` to reorder parameters, placing `IHasCameras cameraController` last. Added logic in `MobileControlSystemController.cs` to instantiate and add `IHasCamerasMessenger` for devices implementing the `IHasCameras` interface. --- .../Messengers/IHasCamerasMessenger.cs | 2 +- .../MobileControlSystemController.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs index 6245f037..ffd1a605 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs @@ -26,7 +26,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public IHasCamerasMessenger(string key, IHasCameras cameraController, string messagePath) + public IHasCamerasMessenger(string key, string messagePath , IHasCameras cameraController) : base(key, messagePath, cameraController) { CameraController = cameraController ?? throw new ArgumentNullException("cameraController"); diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index b17e0490..9d2eaf96 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -907,6 +907,19 @@ namespace PepperDash.Essentials messengerAdded = true; } + if (device is IHasCameras cameras) + { + this.LogVerbose("Adding IHasCamerasMessenger for {deviceKey}", device.Key + ); + var messenger = new IHasCamerasMessenger( + $"{device.Key}-cameras-{Key}", + $"/device/{device.Key}", + cameras + ); + AddDefaultDeviceMessenger(messenger); + messengerAdded = true; + } + this.LogVerbose("Trying to cast to generic device for device: {key}", device.Key); if (device is EssentialsDevice) From a6cd9a0571fb15e50a4f4129934c57208dd43bf7 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Tue, 22 Jul 2025 14:56:28 -0500 Subject: [PATCH 3/6] feat: add destination and source port key properties for advanced routing --- .../Devices/DestinationListItem.cs | 7 +++++++ src/PepperDash.Essentials.Core/Devices/SourceListItem.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs b/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs index c0837401..788714b7 100644 --- a/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs @@ -113,5 +113,12 @@ namespace PepperDash.Essentials.Core /// [JsonProperty("supportsUsb")] public bool SupportsUsb { get; set; } + + /// + /// The key of the destination port associated with this source item + /// This is used to identify the specific port on the destination device that this item refers to for advanced routing + /// + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index 3d08a218..fe0bde9d 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -180,6 +180,13 @@ namespace PepperDash.Essentials.Core [JsonProperty("supportsUsb")] public bool SupportsUsb { get; set; } + /// + /// The key of the source port associated with this source item + /// This is used to identify the specific port on the source device that this item refers to for advanced routing + /// + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } + /// /// Default constructor for SourceListItem, initializes the Icon to "Blank" From 799d4c127cce5c8c416db72ee0be981844331cdc Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 22 Jul 2025 14:02:01 -0600 Subject: [PATCH 4/6] Update src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs b/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs index 788714b7..5a66c0b3 100644 --- a/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs @@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core public bool SupportsUsb { get; set; } /// - /// The key of the destination port associated with this source item + /// The key of the destination port associated with this destination item /// This is used to identify the specific port on the destination device that this item refers to for advanced routing /// [JsonProperty("destinationPortKey")] From 0069233e134f7e67d93eb210ec9b9cf472ea021b Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 24 Jul 2025 16:16:05 -0600 Subject: [PATCH 5/6] Update src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Cameras/CameraControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs index 3212c186..883d6ca4 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs @@ -54,9 +54,9 @@ namespace PepperDash.Essentials.Devices.Common.Cameras StringFeedback SelectedCameraFeedback { get; } /// - /// + /// Selects a camera from the list of available cameras based on the provided key. /// - /// + /// The unique identifier or name of the camera to select. void SelectCamera(string key); } From 86e4d2f7fb97449a2dbab9cb0d1737861c5dc482 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 24 Jul 2025 16:39:28 -0600 Subject: [PATCH 6/6] feat: Update SendFullStatus to target specific clients Modified the `SendFullStatus` method to accept a `string clientId` parameter, allowing it to send status messages to specific clients. Updated the action for `"/fullStatus"` to pass the client ID and adjusted the `PostStatusMessage` call accordingly. --- .../Messengers/IHasCamerasMessenger.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs index ffd1a605..4fa0c5b1 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs @@ -51,7 +51,7 @@ namespace PepperDash.Essentials.AppServer.Messengers AddAction("/fullStatus", (id, context) => { - SendFullStatus(); + SendFullStatus(id); }); AddAction("/selectCamera", (id, content) => @@ -69,7 +69,7 @@ namespace PepperDash.Essentials.AppServer.Messengers }); } - private void SendFullStatus() + private void SendFullStatus(string clientId) { var state = new IHasCamerasStateMessage { @@ -77,7 +77,7 @@ namespace PepperDash.Essentials.AppServer.Messengers SelectedCamera = CameraController.SelectedCamera }; - PostStatusMessage(state); + PostStatusMessage(state, clientId); }