mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-16 05:05:00 +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.Core.Web;
|
||||||
using PepperDash.Essentials.Devices.Common.AudioCodec;
|
using PepperDash.Essentials.Devices.Common.AudioCodec;
|
||||||
using PepperDash.Essentials.Devices.Common.Cameras;
|
using PepperDash.Essentials.Devices.Common.Cameras;
|
||||||
|
using PepperDash.Essentials.Devices.Common.Codec;
|
||||||
using PepperDash.Essentials.Devices.Common.Displays;
|
using PepperDash.Essentials.Devices.Common.Displays;
|
||||||
using PepperDash.Essentials.Devices.Common.Lighting;
|
using PepperDash.Essentials.Devices.Common.Lighting;
|
||||||
using PepperDash.Essentials.Devices.Common.SoftCodec;
|
using PepperDash.Essentials.Devices.Common.SoftCodec;
|
||||||
@@ -560,6 +561,21 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
messengerAdded = true;
|
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)
|
if (device is AudioCodecBase audioCodec)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user