From db80b7b501f085b45503924165b8f64b9304560a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 Aug 2025 20:58:29 +0000
Subject: [PATCH 1/3] Initial plan
From 9946e9a9ae921426d5cc8114e43d9e65cc7dff77 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 Aug 2025 21:14:29 +0000
Subject: [PATCH 2/3] Implement CallStatusMessenger for interface-based devices
and update MobileControlSystemController
Co-authored-by: ngenovese11 <23391587+ngenovese11@users.noreply.github.com>
---
.../Messengers/CallStatusMessenger.cs | 221 ++++++++++++++++++
.../Tests/MockCallStatusDevice.cs | 123 ++++++++++
.../Tests/Program.cs | 73 ++++++
.../MobileControlSystemController.cs | 16 ++
4 files changed, 433 insertions(+)
create mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CallStatusMessenger.cs
create mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs
create mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs
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.Messengers/Tests/MockCallStatusDevice.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs
new file mode 100644
index 000000000..a510e5bdd
--- /dev/null
+++ b/src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs
@@ -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
+{
+ ///
+ /// Mock device for testing CallStatusMessenger that implements IHasDialer without VideoCodecBase
+ ///
+ public class MockCallStatusDevice : EssentialsDevice, IHasDialer, IHasContentSharing
+ {
+ public event EventHandler CallStatusChange;
+
+ private List _activeCalls = new List();
+ 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 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));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs
new file mode 100644
index 000000000..6f61cd230
--- /dev/null
+++ b/src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs
@@ -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
+{
+ ///
+ /// Simple test program to verify CallStatusMessenger functionality
+ ///
+ 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();
+ }
+ }
+}
\ 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)
{
From 94909d2c7c8913f6a2c9cca2ab83ff5def4b83a1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 Aug 2025 21:16:35 +0000
Subject: [PATCH 3/3] Finalize CallStatusMessenger implementation - remove test
files and verify clean implementation
Co-authored-by: ngenovese11 <23391587+ngenovese11@users.noreply.github.com>
---
.../Tests/MockCallStatusDevice.cs | 123 ------------------
.../Tests/Program.cs | 73 -----------
2 files changed, 196 deletions(-)
delete mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs
delete mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs
diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs
deleted file mode 100644
index a510e5bdd..000000000
--- a/src/PepperDash.Essentials.MobileControl.Messengers/Tests/MockCallStatusDevice.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-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
-{
- ///
- /// Mock device for testing CallStatusMessenger that implements IHasDialer without VideoCodecBase
- ///
- public class MockCallStatusDevice : EssentialsDevice, IHasDialer, IHasContentSharing
- {
- public event EventHandler CallStatusChange;
-
- private List _activeCalls = new List();
- 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 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));
- }
- }
-}
\ No newline at end of file
diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs
deleted file mode 100644
index 6f61cd230..000000000
--- a/src/PepperDash.Essentials.MobileControl.Messengers/Tests/Program.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-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
-{
- ///
- /// Simple test program to verify CallStatusMessenger functionality
- ///
- 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();
- }
- }
-}
\ No newline at end of file