mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-11 19:44:52 +00:00
Implement CallStatusMessenger for interface-based devices and update MobileControlSystemController
Co-authored-by: ngenovese11 <23391587+ngenovese11@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a messaging bridge for devices that implement call status interfaces
|
||||
/// without requiring VideoCodecBase inheritance
|
||||
/// </summary>
|
||||
public class CallStatusMessenger : MessengerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Device with dialer capabilities
|
||||
/// </summary>
|
||||
protected IHasDialer Dialer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Device with content sharing capabilities (optional)
|
||||
/// </summary>
|
||||
protected IHasContentSharing ContentSharing { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="dialer"></param>
|
||||
/// <param name="messagePath"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles call status changes
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Dialer_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
SendFullStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Error handling call status change: {error}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles content sharing status changes
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
{
|
||||
PostStatusMessage(JToken.FromObject(new
|
||||
{
|
||||
sharingContentIsOn = e.BoolValue
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sharing source changes
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
{
|
||||
PostStatusMessage(JToken.FromObject(new
|
||||
{
|
||||
sharingSource = e.StringValue
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets active calls from the dialer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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<CodecActiveCallItem>))
|
||||
{
|
||||
var activeCalls = activeCallsProperty.GetValue(Dialer) as System.Collections.Generic.List<CodecActiveCallItem>;
|
||||
return activeCalls ?? new System.Collections.Generic.List<CodecActiveCallItem>();
|
||||
}
|
||||
|
||||
// Return basic call status if no ActiveCalls property
|
||||
return new { isInCall = Dialer.IsInCall };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends full status message
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers actions for call control
|
||||
/// </summary>
|
||||
protected override void RegisterActions()
|
||||
{
|
||||
base.RegisterActions();
|
||||
|
||||
AddAction("/fullStatus", (id, content) => SendFullStatus());
|
||||
|
||||
// Basic call control actions
|
||||
AddAction("/dial", (id, content) =>
|
||||
{
|
||||
var msg = content.ToObject<MobileControlSimpleContent<string>>();
|
||||
Dialer.Dial(msg.Value);
|
||||
});
|
||||
|
||||
AddAction("/endAllCalls", (id, content) => Dialer.EndAllCalls());
|
||||
|
||||
AddAction("/dtmf", (id, content) =>
|
||||
{
|
||||
var msg = content.ToObject<MobileControlSimpleContent<string>>();
|
||||
Dialer.SendDtmf(msg.Value);
|
||||
});
|
||||
|
||||
// Call-specific actions (if active calls are available)
|
||||
AddAction("/endCallById", (id, content) =>
|
||||
{
|
||||
var msg = content.ToObject<MobileControlSimpleContent<string>>();
|
||||
var call = GetCallWithId(msg.Value);
|
||||
if (call != null)
|
||||
Dialer.EndCall(call);
|
||||
});
|
||||
|
||||
AddAction("/acceptById", (id, content) =>
|
||||
{
|
||||
var msg = content.ToObject<MobileControlSimpleContent<string>>();
|
||||
var call = GetCallWithId(msg.Value);
|
||||
if (call != null)
|
||||
Dialer.AcceptCall(call);
|
||||
});
|
||||
|
||||
AddAction("/rejectById", (id, content) =>
|
||||
{
|
||||
var msg = content.ToObject<MobileControlSimpleContent<string>>();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a call by ID
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
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<CodecActiveCallItem>))
|
||||
{
|
||||
var activeCalls = activeCallsProperty.GetValue(Dialer) as System.Collections.Generic.List<CodecActiveCallItem>;
|
||||
if (activeCalls != null)
|
||||
{
|
||||
return activeCalls.FirstOrDefault(c => c.Id.Equals(id));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Devices.Common.Codec;
|
||||
|
||||
namespace PepperDash.Essentials.AppServer.Messengers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Mock device for testing CallStatusMessenger that implements IHasDialer without VideoCodecBase
|
||||
/// </summary>
|
||||
public class MockCallStatusDevice : EssentialsDevice, IHasDialer, IHasContentSharing
|
||||
{
|
||||
public event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange;
|
||||
|
||||
private List<CodecActiveCallItem> _activeCalls = new List<CodecActiveCallItem>();
|
||||
private bool _isInCall;
|
||||
private bool _sharingContentIsOn;
|
||||
private string _sharingSource = "";
|
||||
|
||||
public MockCallStatusDevice(string key, string name) : base(key, name)
|
||||
{
|
||||
SharingContentIsOnFeedback = new BoolFeedback(key + "-SharingContentIsOnFeedback", () => _sharingContentIsOn);
|
||||
SharingSourceFeedback = new StringFeedback(key + "-SharingSourceFeedback", () => _sharingSource);
|
||||
AutoShareContentWhileInCall = false;
|
||||
}
|
||||
|
||||
public bool IsInCall
|
||||
{
|
||||
get => _isInCall;
|
||||
private set
|
||||
{
|
||||
if (_isInCall != value)
|
||||
{
|
||||
_isInCall = value;
|
||||
OnCallStatusChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<CodecActiveCallItem> ActiveCalls => _activeCalls;
|
||||
|
||||
public BoolFeedback SharingContentIsOnFeedback { get; private set; }
|
||||
public StringFeedback SharingSourceFeedback { get; private set; }
|
||||
public bool AutoShareContentWhileInCall { get; private set; }
|
||||
|
||||
public void Dial(string number)
|
||||
{
|
||||
// Mock implementation
|
||||
var call = new CodecActiveCallItem
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = $"Call to {number}",
|
||||
Number = number,
|
||||
Status = eCodecCallStatus.Dialing,
|
||||
Direction = eCodecCallDirection.Outgoing
|
||||
};
|
||||
|
||||
_activeCalls.Add(call);
|
||||
IsInCall = true;
|
||||
}
|
||||
|
||||
public void EndCall(CodecActiveCallItem activeCall)
|
||||
{
|
||||
if (activeCall != null && _activeCalls.Contains(activeCall))
|
||||
{
|
||||
_activeCalls.Remove(activeCall);
|
||||
IsInCall = _activeCalls.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void EndAllCalls()
|
||||
{
|
||||
_activeCalls.Clear();
|
||||
IsInCall = false;
|
||||
}
|
||||
|
||||
public void AcceptCall(CodecActiveCallItem item)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
item.Status = eCodecCallStatus.Connected;
|
||||
IsInCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RejectCall(CodecActiveCallItem item)
|
||||
{
|
||||
if (item != null && _activeCalls.Contains(item))
|
||||
{
|
||||
_activeCalls.Remove(item);
|
||||
IsInCall = _activeCalls.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendDtmf(string digit)
|
||||
{
|
||||
// Mock implementation - nothing to do
|
||||
}
|
||||
|
||||
public void StartSharing()
|
||||
{
|
||||
_sharingContentIsOn = true;
|
||||
_sharingSource = "Local";
|
||||
SharingContentIsOnFeedback.FireUpdate();
|
||||
SharingSourceFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
public void StopSharing()
|
||||
{
|
||||
_sharingContentIsOn = false;
|
||||
_sharingSource = "";
|
||||
SharingContentIsOnFeedback.FireUpdate();
|
||||
SharingSourceFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
private void OnCallStatusChange()
|
||||
{
|
||||
CallStatusChange?.Invoke(this, new CodecCallStatusItemChangeEventArgs(
|
||||
_activeCalls.Count > 0 ? _activeCalls[0] : null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Devices.Common.Codec;
|
||||
using PepperDash.Essentials.AppServer.Messengers;
|
||||
using PepperDash.Essentials.AppServer.Messengers.Tests;
|
||||
|
||||
namespace PepperDash.Essentials.MobileControl.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple test program to verify CallStatusMessenger functionality
|
||||
/// </summary>
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Testing CallStatusMessenger with IHasDialer device...");
|
||||
|
||||
try
|
||||
{
|
||||
// Create a mock device that implements IHasDialer but not VideoCodecBase
|
||||
var mockDevice = new MockCallStatusDevice("mock-codec-1", "Mock Call Device");
|
||||
|
||||
// Create the new CallStatusMessenger
|
||||
var messenger = new CallStatusMessenger(
|
||||
"test-messenger-1",
|
||||
mockDevice,
|
||||
"/device/mock-codec-1"
|
||||
);
|
||||
|
||||
Console.WriteLine("✓ Successfully created CallStatusMessenger with IHasDialer device");
|
||||
|
||||
// Test basic call functionality
|
||||
Console.WriteLine("\nTesting call functionality:");
|
||||
|
||||
Console.WriteLine("- Initial call status: " + (mockDevice.IsInCall ? "In Call" : "Not In Call"));
|
||||
|
||||
// Test dialing a number
|
||||
mockDevice.Dial("1234567890");
|
||||
Console.WriteLine("- After dialing: " + (mockDevice.IsInCall ? "In Call" : "Not In Call"));
|
||||
|
||||
// Test ending all calls
|
||||
mockDevice.EndAllCalls();
|
||||
Console.WriteLine("- After ending all calls: " + (mockDevice.IsInCall ? "In Call" : "Not In Call"));
|
||||
|
||||
// Test content sharing if supported
|
||||
if (mockDevice is IHasContentSharing sharingDevice)
|
||||
{
|
||||
Console.WriteLine("\nTesting content sharing:");
|
||||
Console.WriteLine("- Initial sharing status: " + sharingDevice.SharingContentIsOnFeedback.BoolValue);
|
||||
|
||||
sharingDevice.StartSharing();
|
||||
Console.WriteLine("- After start sharing: " + sharingDevice.SharingContentIsOnFeedback.BoolValue);
|
||||
|
||||
sharingDevice.StopSharing();
|
||||
Console.WriteLine("- After stop sharing: " + sharingDevice.SharingContentIsOnFeedback.BoolValue);
|
||||
}
|
||||
|
||||
Console.WriteLine("\n✓ All tests passed! CallStatusMessenger works with interface-based devices.");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("✗ Test failed: " + ex.Message);
|
||||
Console.WriteLine("Stack trace: " + ex.StackTrace);
|
||||
}
|
||||
|
||||
Console.WriteLine("\nPress any key to exit...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ using PepperDash.Essentials.Core.Shades;
|
||||
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 @@ namespace PepperDash.Essentials
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user