feat: Add messengers for codec functionalities and directory management

- Implemented IHasCodecCamerasMessenger to handle camera selection and control actions.
- Created IHasCodecLayoutsMessenger for managing codec layouts.
- Developed IHasCodecSelfViewMessenger to manage self-view functionality.
- Added IHasDirectoryMessenger for directory operations including fetching and searching.
- Introduced IHasFarEndContentStatusMessenger to report far-end content status.
- Implemented IPasswordPromptMessenger for handling password prompts.
- Updated VideoCodecBaseMessenger to remove redundant directory and call history handling.
- Registered new messenger types in MessengerFactoryRegistry.
- Added necessary using directives in ControlSystem.cs for UC functionalities.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Neil Dorin 2026-05-07 20:51:14 -06:00
parent e19e69d5c0
commit ab4a243ffb
12 changed files with 845 additions and 643 deletions

View file

@ -1,11 +1,8 @@

using System;
using System;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Config;

View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IHasCallHistory"/>
/// </summary>
public class IHasCallHistoryMessenger : MessengerBase
{
private readonly IHasCallHistory _callHistory;
/// <summary>
/// Initializes a new instance of the <see cref="IHasCallHistoryMessenger"/> class.
/// </summary>
public IHasCallHistoryMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_callHistory = device as IHasCallHistory ?? throw new ArgumentException("device must implement IHasCallHistory", nameof(device));
_callHistory.CallHistory.RecentCallsListHasChanged += CallHistory_RecentCallsListHasChanged;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => PostCallHistory());
AddAction("/getCallHistory", (id, content) => PostCallHistory());
}
private void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e)
{
try
{
if (!(sender is CodecCallHistory codecCallHistory)) return;
var recents = codecCallHistory.RecentCalls;
if (recents != null)
{
PostStatusMessage(new IHasCallHistoryStateMessage
{
RecentCalls = recents
});
}
}
catch (Exception ex)
{
this.LogError(ex, "Error posting call history");
}
}
private void PostCallHistory()
{
try
{
var recents = _callHistory.CallHistory.RecentCalls;
if (recents != null)
{
PostStatusMessage(new IHasCallHistoryStateMessage
{
RecentCalls = recents
});
}
}
catch (Exception ex)
{
this.LogError(ex, "Error posting call history");
}
}
}
public class IHasCallHistoryStateMessage : DeviceStateMessageBase
{
[JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecCallHistory.CallHistoryEntry> RecentCalls { get; set; }
}
}

View file

@ -12,7 +12,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <summary>
/// Messenger for devices that implement the IHasCameras interface.
/// </summary>
public class IHasCamerasWithControlMessenger : MessengerBase
public class IHasCamerasWithControlsMessenger : MessengerBase
{
/// <summary>
/// Device being bridged that implements IHasCameras interface.
@ -26,7 +26,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <param name="cameraController"></param>
/// <param name="messagePath"></param>
/// <exception cref="ArgumentNullException"></exception>
public IHasCamerasWithControlMessenger(string key, string messagePath, IHasCamerasWithControls cameraController)
public IHasCamerasWithControlsMessenger(string key, string messagePath, IHasCamerasWithControls cameraController)
: base(key, messagePath, cameraController)
{
CameraController = cameraController ?? throw new ArgumentNullException("cameraController");

View file

@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Cameras;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IHasCodecCameras"/>, including
/// sub-interface support for <see cref="IHasCodecRoomPresets"/>,
/// <see cref="IHasCameraAutoMode"/>, and <see cref="IHasCameraOff"/>.
/// </summary>
public class IHasCodecCamerasMessenger : MessengerBase
{
private readonly VideoCodecBase _codec;
private readonly IHasCodecCameras _cameraCodec;
/// <summary>
/// Initializes a new instance of the <see cref="IHasCodecCamerasMessenger"/> class.
/// </summary>
public IHasCodecCamerasMessenger(string key, string messagePath, VideoCodecBase codec)
: base(key, messagePath, codec)
{
_codec = codec ?? throw new ArgumentNullException(nameof(codec));
_cameraCodec = codec as IHasCodecCameras ?? throw new ArgumentException("codec must implement IHasCodecCameras", nameof(codec));
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
this.LogVerbose("Adding IHasCodecCameras Actions");
_cameraCodec.CameraSelected += CameraCodec_CameraSelected;
AddAction("/fullStatus", (id, content) => PostSelectedCamera());
AddAction("/cameraSelect", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
_cameraCodec.SelectCamera(msg.Value);
});
MapCameraActions();
if (_codec is IHasCodecRoomPresets presetsCodec)
{
this.LogVerbose("Adding IHasCodecRoomPresets Actions");
presetsCodec.CodecRoomPresetsListHasChanged += PresetsCodec_CameraPresetsListHasChanged;
AddAction("/cameraPreset", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<int>>();
presetsCodec.CodecRoomPresetSelect(msg.Value);
});
AddAction("/cameraPresetStore", (id, content) =>
{
var msg = content.ToObject<CodecRoomPreset>();
presetsCodec.CodecRoomPresetStore(msg.ID, msg.Description);
});
}
if (_codec is IHasCameraAutoMode speakerTrackCodec)
{
this.LogVerbose("Adding IHasCameraAutoMode Actions");
speakerTrackCodec.CameraAutoModeIsOnFeedback.OutputChange += CameraAutoModeIsOnFeedback_OutputChange;
AddAction("/cameraModeAuto", (id, content) => speakerTrackCodec.CameraAutoModeOn());
AddAction("/cameraModeManual", (id, content) => speakerTrackCodec.CameraAutoModeOff());
}
if (_codec is IHasCameraOff cameraOffCodec)
{
this.LogVerbose("Adding IHasCameraOff Actions");
cameraOffCodec.CameraIsOffFeedback.OutputChange += CameraIsOffFeedback_OutputChange;
AddAction("/cameraModeOff", (id, content) => cameraOffCodec.CameraOff());
}
}
private void CameraCodec_CameraSelected(object sender, CameraSelectedEventArgs<IHasCameraControls> e)
{
try
{
MapCameraActions();
PostSelectedCamera();
}
catch (Exception ex)
{
this.LogError(ex, "Exception handling camera selected event");
}
}
private void MapCameraActions()
{
if (_cameraCodec.SelectedCamera == null) return;
RemoveAction("/cameraUp");
RemoveAction("/cameraDown");
RemoveAction("/cameraLeft");
RemoveAction("/cameraRight");
RemoveAction("/cameraZoomIn");
RemoveAction("/cameraZoomOut");
RemoveAction("/cameraHome");
if (_cameraCodec.SelectedCamera is IHasCameraPtzControl camera)
{
AddAction("/cameraUp", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) camera.TiltUp(); else camera.TiltStop();
}));
AddAction("/cameraDown", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) camera.TiltDown(); else camera.TiltStop();
}));
AddAction("/cameraLeft", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) camera.PanLeft(); else camera.PanStop();
}));
AddAction("/cameraRight", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) camera.PanRight(); else camera.PanStop();
}));
AddAction("/cameraZoomIn", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) camera.ZoomIn(); else camera.ZoomStop();
}));
AddAction("/cameraZoomOut", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) camera.ZoomOut(); else camera.ZoomStop();
}));
AddAction("/cameraHome", (id, content) => camera.PositionHome());
RemoveAction("/cameraAutoFocus");
RemoveAction("/cameraFocusNear");
RemoveAction("/cameraFocusFar");
if (_cameraCodec.SelectedCamera is IHasCameraFocusControl focusCamera)
{
AddAction("/cameraAutoFocus", (id, content) => focusCamera.TriggerAutoFocus());
AddAction("/cameraFocusNear", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) focusCamera.FocusNear(); else focusCamera.FocusStop();
}));
AddAction("/cameraFocusFar", (id, content) => HandleCameraPressAndHold(content, b =>
{
if (b) focusCamera.FocusFar(); else focusCamera.FocusStop();
}));
}
}
}
private void HandleCameraPressAndHold(JToken content, Action<bool> cameraAction)
{
var state = content.ToObject<MobileControlSimpleContent<string>>();
var timerHandler = PressAndHoldHandler.GetPressAndHoldHandler(state.Value);
if (timerHandler == null) return;
timerHandler(state.Value, cameraAction);
cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase));
}
private void CameraAutoModeIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostCameraMode();
}
private void CameraIsOffFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostCameraMode();
}
private void PresetsCodec_CameraPresetsListHasChanged(object sender, EventArgs e)
{
PostCameraPresets();
}
private string GetCameraMode()
{
string m = "";
if (_codec is IHasCameraAutoMode speakerTrackCodec)
{
m = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue
? eCameraControlMode.Auto.ToString().ToLower()
: eCameraControlMode.Manual.ToString().ToLower();
}
if (_codec is IHasCameraOff cameraOffCodec && cameraOffCodec.CameraIsOffFeedback.BoolValue)
{
m = eCameraControlMode.Off.ToString().ToLower();
}
return m;
}
private Camera GetSelectedCamera()
{
var camera = new Camera();
if (_cameraCodec.SelectedCameraFeedback != null)
camera.Key = _cameraCodec.SelectedCameraFeedback.StringValue;
if (_cameraCodec.SelectedCamera != null)
{
camera.Name = _cameraCodec.SelectedCamera.Name;
if (_cameraCodec.SelectedCamera is IHasCameraPtzControl ptz)
{
camera.Capabilities = new CameraCapabilities
{
CanPan = ptz is IHasCameraPanControl,
CanTilt = ptz is IHasCameraTiltControl,
CanZoom = ptz is IHasCameraZoomControl,
CanFocus = ptz is IHasCameraFocusControl,
};
}
}
if (_cameraCodec.ControllingFarEndCameraFeedback != null)
camera.IsFarEnd = _cameraCodec.ControllingFarEndCameraFeedback.BoolValue;
return camera;
}
private List<CodecRoomPreset> GetCurrentPresets()
{
if (!(_codec is IHasCodecRoomPresets presetsCodec)) return null;
if (_codec is IHasFarEndCameraControl farEnd && farEnd.ControllingFarEndCameraFeedback.BoolValue)
return presetsCodec.FarEndRoomPresets;
return presetsCodec.NearEndPresets;
}
private void PostCameraMode()
{
try
{
PostStatusMessage(new IHasCodecCamerasStateMessage
{
CameraMode = GetCameraMode()
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting camera mode");
}
}
private void PostSelectedCamera()
{
try
{
PostStatusMessage(new IHasCodecCamerasStateMessage
{
Cameras = new CameraStatus { SelectedCamera = GetSelectedCamera() },
Presets = GetCurrentPresets()
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting selected camera");
}
}
private void PostCameraPresets()
{
try
{
PostStatusMessage(new IHasCodecCamerasStateMessage
{
Presets = GetCurrentPresets()
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting camera presets");
}
}
}
public class IHasCodecCamerasStateMessage : DeviceStateMessageBase
{
[JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)]
public string CameraMode { get; set; }
[JsonProperty("cameras", NullValueHandling = NullValueHandling.Ignore)]
public CameraStatus Cameras { get; set; }
[JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecRoomPreset> Presets { get; set; }
}
}

View file

@ -0,0 +1,64 @@
using System;
using Newtonsoft.Json;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IHasCodecLayouts"/>
/// </summary>
public class IHasCodecLayoutsMessenger : MessengerBase
{
private readonly IHasCodecLayouts _layouts;
/// <summary>
/// Initializes a new instance of the <see cref="IHasCodecLayoutsMessenger"/> class.
/// </summary>
public IHasCodecLayoutsMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_layouts = device as IHasCodecLayouts ?? throw new ArgumentException("device must implement IHasCodecLayouts", nameof(device));
_layouts.LocalLayoutFeedback.OutputChange += LocalLayoutFeedback_OutputChange;
}
private void LocalLayoutFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
SendFullStatus();
}
private void SendFullStatus()
{
PostStatusMessage(new IHasCodecLayoutsStateMessage
{
CurrentLayout = _layouts.LocalLayoutFeedback.StringValue
});
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus());
AddAction("/layoutStatus", (id, content) => SendFullStatus());
AddAction("/cameraRemoteView", (id, content) => _layouts.LocalLayoutToggle());
AddAction("/cameraLayout", (id, content) => _layouts.LocalLayoutToggle());
}
}
/// <summary>
/// State message for <see cref="IHasCodecLayoutsMessenger"/>
/// </summary>
public class IHasCodecLayoutsStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the current layout of the codec
/// </summary>
[JsonProperty("currentLayout", NullValueHandling = NullValueHandling.Ignore)]
public string CurrentLayout { get; set; }
}
}

View file

@ -0,0 +1,62 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IHasCodecSelfView"/>
/// </summary>
public class IHasCodecSelfViewMessenger : MessengerBase
{
private readonly IHasCodecSelfView _selfView;
/// <summary>
/// Initializes a new instance of the <see cref="IHasCodecSelfViewMessenger"/> class.
/// </summary>
public IHasCodecSelfViewMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_selfView = device as IHasCodecSelfView ?? throw new ArgumentException("device must implement IHasCodecSelfView", nameof(device));
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/cameraSelfView", (id, content) => _selfView.SelfViewModeToggle());
_selfView.SelfviewIsOnFeedback.OutputChange += SelfviewIsOnFeedback_OutputChange;
}
private void SelfviewIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostCameraSelfView();
}
private void PostCameraSelfView()
{
try
{
PostStatusMessage(new IHasCodecSelfViewStateMessage
{
CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting camera self view");
}
}
}
public class IHasCodecSelfViewStateMessage : DeviceStateMessageBase
{
[JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSelfViewIsOn { get; set; }
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IHasDirectory"/>
/// </summary>
public class IHasDirectoryMessenger : MessengerBase
{
private readonly IHasDirectory _directory;
/// <summary>
/// Initializes a new instance of the <see cref="IHasDirectoryMessenger"/> class.
/// </summary>
public IHasDirectoryMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_directory = device as IHasDirectory ?? throw new ArgumentException("device must implement IHasDirectory", nameof(device));
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/getDirectory", (id, content) => GetDirectoryRoot());
AddAction("/directoryById", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
GetDirectory(msg.Value);
});
AddAction("/directorySearch", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
GetDirectory(msg.Value);
});
AddAction("/directoryBack", (id, content) => GetPreviousDirectory());
_directory.DirectoryResultReturned += DirectoryResultReturned;
_directory.PhonebookSyncState.InitialSyncCompleted += PhonebookSyncState_InitialSyncCompleted;
}
private void DirectoryResultReturned(object sender, DirectoryEventArgs e)
{
SendDirectory(e.Directory);
}
private void SendDirectory(CodecDirectory directory)
{
try
{
this.LogVerbose("Sending Directory. Directory Item Count: {directoryItemCount}", directory.CurrentDirectoryResults.Count);
Task.Run(() => PostStatusMessage(new IHasDirectoryStateMessage
{
CurrentDirectory = directory
}));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending directory");
}
}
private void PhonebookSyncState_InitialSyncCompleted(object sender, EventArgs e)
{
try
{
PostStatusMessage(new IHasDirectoryStateMessage
{
InitialPhonebookSyncComplete = true
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting phonebook sync state");
}
}
private void GetDirectory(string id)
{
_directory.GetDirectoryFolderContents(id);
}
private void GetDirectoryRoot()
{
try
{
if (!_directory.PhonebookSyncState.InitialSyncComplete)
{
PostStatusMessage(new IHasDirectoryStateMessage
{
InitialPhonebookSyncComplete = false
});
return;
}
_directory.SetCurrentDirectoryToRoot();
}
catch (Exception ex)
{
this.LogError(ex, "Error getting directory root");
}
}
private void GetPreviousDirectory()
{
_directory.GetDirectoryParentFolderContents();
}
}
public class IHasDirectoryStateMessage : DeviceStateMessageBase
{
[JsonProperty("currentDirectory", NullValueHandling = NullValueHandling.Ignore)]
public CodecDirectory CurrentDirectory { get; set; }
[JsonProperty("initialPhonebookSyncComplete", NullValueHandling = NullValueHandling.Ignore)]
public bool? InitialPhonebookSyncComplete { get; set; }
}
}

View file

@ -0,0 +1,55 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IHasFarEndContentStatus"/>
/// </summary>
public class IHasFarEndContentStatusMessenger : MessengerBase
{
private readonly IHasFarEndContentStatus _device;
/// <summary>
/// Initializes a new instance of the <see cref="IHasFarEndContentStatusMessenger"/> class.
/// </summary>
public IHasFarEndContentStatusMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_device = device as IHasFarEndContentStatus ?? throw new ArgumentException("device must implement IHasFarEndContentStatus", nameof(device));
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
_device.ReceivingContent.OutputChange += (sender, args) => PostReceivingContent(args.BoolValue);
}
private void PostReceivingContent(bool receivingContent)
{
try
{
PostStatusMessage(new IHasFarEndContentStatusStateMessage
{
ReceivingContent = receivingContent
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting receiving content");
}
}
}
public class IHasFarEndContentStatusStateMessage : DeviceStateMessageBase
{
[JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)]
public bool? ReceivingContent { get; set; }
}
}

View file

@ -0,0 +1,85 @@
using System;
using Newtonsoft.Json;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices implementing <see cref="IPasswordPrompt"/>
/// </summary>
public class IPasswordPromptMessenger : MessengerBase
{
private readonly IPasswordPrompt _device;
/// <summary>
/// Initializes a new instance of the <see cref="IPasswordPromptMessenger"/> class.
/// </summary>
public IPasswordPromptMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_device = device as IPasswordPrompt ?? throw new ArgumentException("device must implement IPasswordPrompt", nameof(device));
_device.PasswordRequired += OnPasswordRequired;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/password", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
_device.SubmitPassword(msg.Value);
});
}
private void OnPasswordRequired(object sender, PasswordPromptEventArgs args)
{
PostEventMessage(new PasswordPromptEventMessage
{
Message = args.Message,
LastAttemptWasIncorrect = args.LastAttemptWasIncorrect,
LoginAttemptFailed = args.LoginAttemptFailed,
LoginAttemptCancelled = args.LoginAttemptCancelled,
EventType = "passwordPrompt"
});
}
}
/// <summary>
/// Base event message for video codec events
/// </summary>
public class VideoCodecBaseEventMessage : DeviceEventMessageBase
{
}
/// <summary>
/// Event message sent when a password is required
/// </summary>
public class PasswordPromptEventMessage : VideoCodecBaseEventMessage
{
/// <summary>
/// Gets or sets the Message
/// </summary>
[JsonProperty("message", NullValueHandling = NullValueHandling.Ignore)]
public string Message { get; set; }
/// <summary>
/// Gets or sets the LastAttemptWasIncorrect
/// </summary>
[JsonProperty("lastAttemptWasIncorrect", NullValueHandling = NullValueHandling.Ignore)]
public bool LastAttemptWasIncorrect { get; set; }
/// <summary>
/// Gets or sets the LoginAttemptFailed
/// </summary>
[JsonProperty("loginAttemptFailed", NullValueHandling = NullValueHandling.Ignore)]
public bool LoginAttemptFailed { get; set; }
/// <summary>
/// Gets or sets the LoginAttemptCancelled
/// </summary>
[JsonProperty("loginAttemptCancelled", NullValueHandling = NullValueHandling.Ignore)]
public bool LoginAttemptCancelled { get; set; }
}
}

View file

@ -38,99 +38,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
Codec = codec ?? throw new ArgumentNullException("codec");
codec.CallStatusChange += Codec_CallStatusChange;
codec.IsReadyChange += Codec_IsReadyChange;
if (codec is IHasDirectory dirCodec)
{
dirCodec.DirectoryResultReturned += DirCodec_DirectoryResultReturned;
}
if (codec is IHasCallHistory recCodec)
{
recCodec.CallHistory.RecentCallsListHasChanged += CallHistory_RecentCallsListHasChanged;
}
if (codec is IPasswordPrompt pwPromptCodec)
{
pwPromptCodec.PasswordRequired += OnPasswordRequired;
}
}
private void OnPasswordRequired(object sender, PasswordPromptEventArgs args)
{
var eventMsg = new PasswordPromptEventMessage
{
Message = args.Message,
LastAttemptWasIncorrect = args.LastAttemptWasIncorrect,
LoginAttemptFailed = args.LoginAttemptFailed,
LoginAttemptCancelled = args.LoginAttemptCancelled,
EventType = "passwordPrompt"
};
PostEventMessage(eventMsg);
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e)
{
try
{
var state = new VideoCodecBaseStateMessage();
if (!(sender is CodecCallHistory codecCallHistory)) return;
var recents = codecCallHistory.RecentCalls;
if (recents != null)
{
state.RecentCalls = recents;
PostStatusMessage(state);
}
}
catch (Exception ex)
{
this.LogError(ex, "Error posting call history");
}
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void DirCodec_DirectoryResultReturned(object sender, DirectoryEventArgs e)
{
if (Codec is IHasDirectory)
SendDirectory(e.Directory);
}
/// <summary>
/// Posts the current directory
/// </summary>
protected void SendDirectory(CodecDirectory directory)
{
try
{
var state = new VideoCodecBaseStateMessage();
if (Codec is IHasDirectory dirCodec)
{
this.LogVerbose("Sending Directory. Directory Item Count: {directoryItemCount}", directory.CurrentDirectoryResults.Count);
//state.CurrentDirectory = PrefixDirectoryFolderItems(directory);
state.CurrentDirectory = directory;
Task.Run(() => PostStatusMessage(state));
}
}
catch (Exception ex)
{
this.LogError(ex, "Error sending directory");
}
}
/// <summary>
@ -217,132 +124,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
Codec.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange;
Codec.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange;
// Directory actions
if (Codec is IHasDirectory dirCodec)
{
AddAction("/getDirectory", (id, content) => GetDirectoryRoot());
AddAction("/directoryById", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
GetDirectory(msg.Value);
});
AddAction("/directorySearch", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
GetDirectory(msg.Value);
});
AddAction("/directoryBack", (id, content) => GetPreviousDirectory());
dirCodec.PhonebookSyncState.InitialSyncCompleted += PhonebookSyncState_InitialSyncCompleted;
}
// History actions
if (Codec is IHasCallHistory recCodec)
{
AddAction("/getCallHistory", (id, content) => PostCallHistory());
}
if (Codec is IHasCodecCameras cameraCodec)
{
this.LogVerbose("Adding IHasCodecCameras Actions");
cameraCodec.CameraSelected += CameraCodec_CameraSelected;
AddAction("/cameraSelect", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
cameraCodec.SelectCamera(msg.Value);
});
MapCameraActions();
if (Codec is IHasCodecRoomPresets presetsCodec)
{
this.LogVerbose("Adding IHasCodecRoomPresets Actions");
presetsCodec.CodecRoomPresetsListHasChanged += PresetsCodec_CameraPresetsListHasChanged;
AddAction("/cameraPreset", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<int>>();
presetsCodec.CodecRoomPresetSelect(msg.Value);
});
AddAction("/cameraPresetStore", (id, content) =>
{
var msg = content.ToObject<CodecRoomPreset>();
presetsCodec.CodecRoomPresetStore(msg.ID, msg.Description);
});
}
if (Codec is IHasCameraAutoMode speakerTrackCodec)
{
this.LogVerbose("Adding IHasCameraAutoMode Actions");
speakerTrackCodec.CameraAutoModeIsOnFeedback.OutputChange += CameraAutoModeIsOnFeedback_OutputChange;
AddAction("/cameraModeAuto", (id, content) => speakerTrackCodec.CameraAutoModeOn());
AddAction("/cameraModeManual", (id, content) => speakerTrackCodec.CameraAutoModeOff());
}
if (Codec is IHasCameraOff cameraOffCodec)
{
this.LogVerbose("Adding IHasCameraOff Actions");
cameraOffCodec.CameraIsOffFeedback.OutputChange += (CameraIsOffFeedback_OutputChange);
AddAction("/cameraModeOff", (id, content) => cameraOffCodec.CameraOff());
}
}
if (Codec is IHasCodecSelfView selfViewCodec)
{
this.LogVerbose("Adding IHasCodecSelfView Actions");
AddAction("/cameraSelfView", (id, content) => selfViewCodec.SelfViewModeToggle());
selfViewCodec.SelfviewIsOnFeedback.OutputChange += new EventHandler<FeedbackEventArgs>(SelfviewIsOnFeedback_OutputChange);
}
if (Codec is IHasCodecLayouts layoutsCodec)
{
this.LogVerbose("Adding IHasCodecLayouts Actions");
AddAction("/cameraRemoteView", (id, content) => layoutsCodec.LocalLayoutToggle());
AddAction("/cameraLayout", (id, content) => layoutsCodec.LocalLayoutToggle());
}
if (Codec is IPasswordPrompt pwCodec)
{
this.LogVerbose("Adding IPasswordPrompt Actions");
AddAction("/password", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<string>>();
pwCodec.SubmitPassword(msg.Value);
});
}
if (Codec is IHasFarEndContentStatus farEndContentStatus)
{
farEndContentStatus.ReceivingContent.OutputChange +=
(sender, args) => PostReceivingContent(args.BoolValue);
}
this.LogVerbose("Adding Privacy & Standby Actions");
AddAction("/privacyModeOn", (id, content) => Codec.PrivacyModeOn());
@ -393,304 +174,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
}
private void PhonebookSyncState_InitialSyncCompleted(object sender, EventArgs e)
{
try
{
var state = new VideoCodecBaseStateMessage
{
InitialPhonebookSyncComplete = true
};
PostStatusMessage(state);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting phonebook sync state");
}
}
private void CameraIsOffFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostCameraMode();
}
private void SelfviewIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostCameraSelfView();
}
private void PresetsCodec_CameraPresetsListHasChanged(object sender, EventArgs e)
{
PostCameraPresets();
}
private void CameraAutoModeIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
PostCameraMode();
}
private void CameraCodec_CameraSelected(object sender, CameraSelectedEventArgs<IHasCameraControls> e)
{
try
{
MapCameraActions();
PostSelectedCamera();
}
catch (Exception ex)
{
this.LogError(ex, "Exception handling camera selected event");
}
}
/// <summary>
/// Maps the camera control actions to the current selected camera on the codec
/// </summary>
private void MapCameraActions()
{
if (Codec is IHasCamerasWithControls cameraCodec && cameraCodec.SelectedCamera != null)
{
RemoveAction("/cameraUp");
RemoveAction("/cameraDown");
RemoveAction("/cameraLeft");
RemoveAction("/cameraRight");
RemoveAction("/cameraZoomIn");
RemoveAction("/cameraZoomOut");
RemoveAction("/cameraHome");
if (cameraCodec.SelectedCamera is IHasCameraPtzControl camera)
{
AddAction("/cameraUp", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
camera.TiltUp();
return;
}
camera.TiltStop();
}));
AddAction("/cameraDown", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
camera.TiltDown();
return;
}
camera.TiltStop();
}));
AddAction("/cameraLeft", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
camera.PanLeft();
return;
}
camera.PanStop();
}));
AddAction("/cameraRight", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
camera.PanRight();
return;
}
camera.PanStop();
}));
AddAction("/cameraZoomIn", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
camera.ZoomIn();
return;
}
camera.ZoomStop();
}));
AddAction("/cameraZoomOut", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
camera.ZoomOut();
return;
}
camera.ZoomStop();
}));
AddAction("/cameraHome", (id, content) => camera.PositionHome());
RemoveAction("/cameraAutoFocus");
RemoveAction("/cameraFocusNear");
RemoveAction("/cameraFocusFar");
if (cameraCodec is IHasCameraFocusControl focusCamera)
{
AddAction("/cameraAutoFocus", (id, content) => focusCamera.TriggerAutoFocus());
AddAction("/cameraFocusNear", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
focusCamera.FocusNear();
return;
}
focusCamera.FocusStop();
}));
AddAction("/cameraFocusFar", (id, content) => HandleCameraPressAndHold(content, (b) =>
{
if (b)
{
focusCamera.FocusFar();
return;
}
focusCamera.FocusStop();
}));
}
}
}
}
private void HandleCameraPressAndHold(JToken content, Action<bool> cameraAction)
{
var state = content.ToObject<MobileControlSimpleContent<string>>();
var timerHandler = PressAndHoldHandler.GetPressAndHoldHandler(state.Value);
if (timerHandler == null)
{
return;
}
timerHandler(state.Value, cameraAction);
cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase));
}
private string GetCameraMode()
{
string m = "";
if (Codec is IHasCameraAutoMode speakerTrackCodec)
{
m = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue
? eCameraControlMode.Auto.ToString().ToLower()
: eCameraControlMode.Manual.ToString().ToLower();
}
if (Codec is IHasCameraOff cameraOffCodec)
{
if (cameraOffCodec.CameraIsOffFeedback.BoolValue)
m = eCameraControlMode.Off.ToString().ToLower();
}
return m;
}
private void PostCallHistory()
{
try
{
var codec = (Codec as IHasCallHistory);
if (codec != null)
{
var status = new VideoCodecBaseStateMessage();
var recents = codec.CallHistory.RecentCalls;
if (recents != null)
{
status.RecentCalls = codec.CallHistory.RecentCalls;
PostStatusMessage(status);
}
}
}
catch (Exception ex)
{
this.LogError(ex, "Error posting call history");
}
}
/// <summary>
/// Helper to grab a call with string ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private CodecActiveCallItem GetCallWithId(string id)
{
return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id);
}
/// <summary>
///
/// </summary>
/// <param name="id"></param>
private void GetDirectory(string id)
{
if (!(Codec is IHasDirectory dirCodec))
{
return;
}
dirCodec.GetDirectoryFolderContents(id);
}
/// <summary>
///
/// </summary>
private void GetDirectoryRoot()
{
try
{
if (!(Codec is IHasDirectory dirCodec))
{
// do something else?
return;
}
if (!dirCodec.PhonebookSyncState.InitialSyncComplete)
{
var state = new VideoCodecBaseStateMessage
{
InitialPhonebookSyncComplete = false
};
PostStatusMessage(state);
return;
}
dirCodec.SetCurrentDirectoryToRoot();
}
catch (Exception ex)
{
this.LogError(ex, "Error getting directory root");
}
}
/// <summary>
/// Requests the parent folder contents
/// </summary>
private void GetPreviousDirectory()
{
if (!(Codec is IHasDirectory dirCodec))
{
return;
}
dirCodec.GetDirectoryParentFolderContents();
}
/// <summary>
/// Handler for codec changes
/// </summary>
@ -797,96 +280,32 @@ namespace PepperDash.Essentials.AppServer.Messengers
Task.Run(() => PostStatusMessage(GetStatus(), id));
}
private void PostReceivingContent(bool receivingContent)
{
try
{
var state = new VideoCodecBaseStateMessage
{
ReceivingContent = receivingContent
};
PostStatusMessage(state);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting receiving content");
}
}
private void PostCameraSelfView()
{
try
{
var status = new VideoCodecBaseStateMessage
{
CameraSelfViewIsOn = Codec is IHasCodecSelfView
&& (Codec as IHasCodecSelfView).SelfviewIsOnFeedback.BoolValue
};
PostStatusMessage(status);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting camera self view");
}
}
/// <summary>
///
/// Helper to grab a call with string ID
/// </summary>
private void PostCameraMode()
private CodecActiveCallItem GetCallWithId(string id)
{
try
{
var status = new VideoCodecBaseStateMessage
{
CameraMode = GetCameraMode()
};
PostStatusMessage(status);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting camera mode");
}
return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id);
}
private void PostSelectedCamera()
private string GetCameraMode()
{
try
{
var camerasCodec = Codec as IHasCodecCameras;
string m = "";
var status = new VideoCodecBaseStateMessage
{
Cameras = new CameraStatus() { SelectedCamera = GetSelectedCamera(camerasCodec) },
Presets = GetCurrentPresets()
};
PostStatusMessage(status);
}
catch (Exception e)
if (Codec is IHasCameraAutoMode speakerTrackCodec)
{
this.LogError(e, "Error posting selected camera");
m = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue
? eCameraControlMode.Auto.ToString().ToLower()
: eCameraControlMode.Manual.ToString().ToLower();
}
}
private void PostCameraPresets()
{
try
if (Codec is IHasCameraOff cameraOffCodec)
{
var status = new VideoCodecBaseStateMessage
{
Presets = GetCurrentPresets()
};
if (cameraOffCodec.CameraIsOffFeedback.BoolValue)
m = eCameraControlMode.Off.ToString().ToLower();
}
PostStatusMessage(status);
}
catch (Exception e)
{
this.LogError(e, "Error posting camera presets");
}
return m;
}
private Camera GetSelectedCamera(IHasCodecCameras camerasCodec)
@ -1145,45 +564,4 @@ namespace PepperDash.Essentials.AppServer.Messengers
public bool? CanFocus { get; set; }
}
/// <summary>
/// Represents a VideoCodecBaseEventMessage
/// </summary>
public class VideoCodecBaseEventMessage : DeviceEventMessageBase
{
}
/// <summary>
/// Represents a PasswordPromptEventMessage
/// </summary>
public class PasswordPromptEventMessage : VideoCodecBaseEventMessage
{
/// <summary>
/// Gets or sets the Message
/// </summary>
[JsonProperty("message", NullValueHandling = NullValueHandling.Ignore)]
public string Message { get; set; }
/// <summary>
/// Gets or sets the LastAttemptWasIncorrect
/// </summary>
[JsonProperty("lastAttemptWasIncorrect", NullValueHandling = NullValueHandling.Ignore)]
public bool LastAttemptWasIncorrect { get; set; }
/// <summary>
/// Gets or sets the LoginAttemptFailed
/// </summary>
[JsonProperty("loginAttemptFailed", NullValueHandling = NullValueHandling.Ignore)]
public bool LoginAttemptFailed { get; set; }
/// <summary>
/// Gets or sets the LoginAttemptCancelled
/// </summary>
[JsonProperty("loginAttemptCancelled", NullValueHandling = NullValueHandling.Ignore)]
public bool LoginAttemptCancelled { get; set; }
}
}

View file

@ -10,6 +10,7 @@ using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Core.Shades;
using PepperDash.Essentials.Devices.Common.AudioCodec;
using PepperDash.Essentials.Devices.Common.Cameras;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Devices.Common.Displays;
using PepperDash.Essentials.Devices.Common.SoftCodec;
using PepperDash.Essentials.Devices.Common.VideoCodec;
@ -95,7 +96,7 @@ namespace PepperDash.Essentials
),
new MessengerFactoryEntry(
typeof(IHasCamerasWithControls),
(d, mp, ck) => new IHasCamerasWithControlMessenger(
(d, mp, ck) => new IHasCamerasWithControlsMessenger(
$"{d.Key}-cameras-{ck}", mp, (IHasCamerasWithControls)d)
),
@ -164,6 +165,42 @@ namespace PepperDash.Essentials
(d, mp, ck) => new VideoCodecBaseMessenger(
$"{d.Key}-videoCodec-{ck}", (VideoCodecBase)d, mp)
),
new MessengerFactoryEntry(
typeof(IHasDirectory),
(d, mp, ck) => new IHasDirectoryMessenger(
$"{d.Key}-directory-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasCallHistory),
(d, mp, ck) => new IHasCallHistoryMessenger(
$"{d.Key}-callHistory-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IPasswordPrompt),
(d, mp, ck) => new IPasswordPromptMessenger(
$"{d.Key}-passwordPrompt-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasCodecCameras),
(d, mp, ck) => new IHasCodecCamerasMessenger(
$"{d.Key}-codecCameras-{ck}", mp, (VideoCodecBase)d),
predicate: d => d is VideoCodecBase && d is IHasCodecCameras
),
new MessengerFactoryEntry(
typeof(IHasCodecSelfView),
(d, mp, ck) => new IHasCodecSelfViewMessenger(
$"{d.Key}-selfView-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasCodecLayouts),
(d, mp, ck) => new IHasCodecLayoutsMessenger(
$"{d.Key}-codecLayouts-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasFarEndContentStatus),
(d, mp, ck) => new IHasFarEndContentStatusMessenger(
$"{d.Key}-farEndContent-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IDialerCallStatus),
(d, mp, ck) => new IDialerCallStatusMessenger(

View file

@ -9,6 +9,7 @@ using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.Diagnostics;
using Crestron.SimplSharpPro.UC;
using PepperDash.Core;
using PepperDash.Core.Abstractions;
using PepperDash.Core.Adapters;