diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CallStatusMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CallStatusMessenger.cs new file mode 100644 index 000000000..cd7edeb3f --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CallStatusMessenger.cs @@ -0,0 +1,221 @@ +using System; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Core.Logging; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.Codec; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Provides a messaging bridge for devices that implement call status interfaces + /// without requiring VideoCodecBase inheritance + /// + public class CallStatusMessenger : MessengerBase + { + /// + /// Device with dialer capabilities + /// + protected IHasDialer Dialer { get; private set; } + + /// + /// Device with content sharing capabilities (optional) + /// + protected IHasContentSharing ContentSharing { get; private set; } + + /// + /// Constructor + /// + /// + /// + /// + public CallStatusMessenger(string key, IHasDialer dialer, string messagePath) + : base(key, messagePath, dialer as IKeyName) + { + Dialer = dialer ?? throw new ArgumentNullException(nameof(dialer)); + dialer.CallStatusChange += Dialer_CallStatusChange; + + // Check for optional content sharing interface + if (dialer is IHasContentSharing contentSharing) + { + ContentSharing = contentSharing; + contentSharing.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange; + contentSharing.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange; + } + } + + /// + /// Handles call status changes + /// + /// + /// + private void Dialer_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) + { + try + { + SendFullStatus(); + } + catch (Exception ex) + { + this.LogError(ex, "Error handling call status change: {error}", ex.Message); + } + } + + /// + /// Handles content sharing status changes + /// + /// + /// + private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + PostStatusMessage(JToken.FromObject(new + { + sharingContentIsOn = e.BoolValue + })); + } + + /// + /// Handles sharing source changes + /// + /// + /// + private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + PostStatusMessage(JToken.FromObject(new + { + sharingSource = e.StringValue + })); + } + + /// + /// Gets active calls from the dialer + /// + /// + private object GetActiveCalls() + { + // Try to get active calls if the dialer has an ActiveCalls property + var dialerType = Dialer.GetType(); + var activeCallsProperty = dialerType.GetProperty("ActiveCalls"); + + if (activeCallsProperty != null && activeCallsProperty.PropertyType == typeof(System.Collections.Generic.List)) + { + var activeCalls = activeCallsProperty.GetValue(Dialer) as System.Collections.Generic.List; + return activeCalls ?? new System.Collections.Generic.List(); + } + + // Return basic call status if no ActiveCalls property + return new { isInCall = Dialer.IsInCall }; + } + + /// + /// Sends full status message + /// + public void SendFullStatus() + { + var status = new + { + isInCall = Dialer.IsInCall, + calls = GetActiveCalls() + }; + + // Add content sharing status if available + if (ContentSharing != null) + { + var statusWithSharing = new + { + isInCall = Dialer.IsInCall, + calls = GetActiveCalls(), + sharingContentIsOn = ContentSharing.SharingContentIsOnFeedback.BoolValue, + sharingSource = ContentSharing.SharingSourceFeedback.StringValue + }; + PostStatusMessage(JToken.FromObject(statusWithSharing)); + } + else + { + PostStatusMessage(JToken.FromObject(status)); + } + } + + /// + /// Registers actions for call control + /// + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, content) => SendFullStatus()); + + // Basic call control actions + AddAction("/dial", (id, content) => + { + var msg = content.ToObject>(); + Dialer.Dial(msg.Value); + }); + + AddAction("/endAllCalls", (id, content) => Dialer.EndAllCalls()); + + AddAction("/dtmf", (id, content) => + { + var msg = content.ToObject>(); + Dialer.SendDtmf(msg.Value); + }); + + // Call-specific actions (if active calls are available) + AddAction("/endCallById", (id, content) => + { + var msg = content.ToObject>(); + var call = GetCallWithId(msg.Value); + if (call != null) + Dialer.EndCall(call); + }); + + AddAction("/acceptById", (id, content) => + { + var msg = content.ToObject>(); + var call = GetCallWithId(msg.Value); + if (call != null) + Dialer.AcceptCall(call); + }); + + AddAction("/rejectById", (id, content) => + { + var msg = content.ToObject>(); + var call = GetCallWithId(msg.Value); + if (call != null) + Dialer.RejectCall(call); + }); + + // Content sharing actions if available + if (ContentSharing != null) + { + AddAction("/startSharing", (id, content) => ContentSharing.StartSharing()); + AddAction("/stopSharing", (id, content) => ContentSharing.StopSharing()); + } + } + + /// + /// Finds a call by ID + /// + /// + /// + private CodecActiveCallItem GetCallWithId(string id) + { + // Try to get call using reflection for ActiveCalls property + var dialerType = Dialer.GetType(); + var activeCallsProperty = dialerType.GetProperty("ActiveCalls"); + + if (activeCallsProperty != null && activeCallsProperty.PropertyType == typeof(System.Collections.Generic.List)) + { + var activeCalls = activeCallsProperty.GetValue(Dialer) as System.Collections.Generic.List; + if (activeCalls != null) + { + return activeCalls.FirstOrDefault(c => c.Id.Equals(id)); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index f58545168..a18d4e40b 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -27,6 +27,7 @@ using PepperDash.Essentials.Core.Web; 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.Lighting; using PepperDash.Essentials.Devices.Common.SoftCodec; @@ -560,6 +561,21 @@ device as DisplayBase messengerAdded = true; } + else if (device is IHasDialer dialer && !messengerAdded) + { + this.LogVerbose( + "Adding CallStatusMessenger for {deviceKey}", device.Key); + + var messenger = new CallStatusMessenger( + $"{device.Key}-callStatus-{Key}", + dialer, + $"/device/{device.Key}" + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } if (device is AudioCodecBase audioCodec) {