Skip to content

Commit

Permalink
Added timer to heartbeat to catch cases where connection is dropped. …
Browse files Browse the repository at this point in the history
…Also added test case for this
  • Loading branch information
Tapanila committed Jul 3, 2024
1 parent 9ada45b commit 5b11b6c
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 15 deletions.
4 changes: 2 additions & 2 deletions SharpCaster.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.Build.0 = Release|Any CPU
{7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Release|Any CPU.Build.0 = Release|Any CPU
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down
33 changes: 32 additions & 1 deletion Sharpcaster.Test/ChromecastConnectionTester.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace Sharpcaster.Test
{
[Collection("SingleCollection")]
public class ChromecastConnectionTester
{

private ITestOutputHelper output;
public ChromecastConnectionTester(ITestOutputHelper outputHelper)
{
output = outputHelper;
}

[Fact]
public async Task SearchChromecastsAndConnectToIt()
{
Expand All @@ -15,5 +25,26 @@ public async Task SearchChromecastsAndConnectToIt()
Assert.NotNull(status);
}

[Fact]
public async Task SearchChromecastsAndConnectToItThenWaitForItToShutdown()
{
var client = await TestHelper.CreateConnectAndLoadAppClient(output);

Assert.NotNull(client.GetChromecastStatus());

AutoResetEvent _autoResetEvent = new AutoResetEvent(false);

client.Disconnected += (sender, args) =>
{
output.WriteLine("Chromecast did shutdown");
_autoResetEvent.Set();
};

//This checks that within 30 seconds we have noticed that device was turned off
//This need manual intervention to turn off the device
output.WriteLine("Waiting for Chromecast to shutdown");
Assert.True(_autoResetEvent.WaitOne(30000));
}

}
}
49 changes: 38 additions & 11 deletions Sharpcaster/Channels/HeartbeatChannel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using Sharpcaster.Interfaces;
using Microsoft.Extensions.Logging;
using Sharpcaster.Interfaces;
using Sharpcaster.Messages.Heartbeat;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Timers;

namespace Sharpcaster.Channels
{
Expand All @@ -10,28 +13,52 @@ namespace Sharpcaster.Channels
/// </summary>
public class HeartbeatChannel : ChromecastChannel, IHeartbeatChannel
{
private ILogger _logger = null;
private Timer _timer;

/// <summary>
/// Initializes a new instance of HeartbeatChannel class
/// </summary>
public HeartbeatChannel() : base("tp.heartbeat")
public HeartbeatChannel(ILogger logger = null) : base("tp.heartbeat")
{
_logger = logger;
_timer = new Timer(10000); // timeout is 10 seconds.
// Because Chromecast only waits for 8 seconds for response
_timer.Elapsed += TimerElapsed;
_timer.AutoReset = false;
}

public event EventHandler StatusChanged;

/// <summary>
/// Called when a message for this channel is received
/// </summary>
/// <param name="message">message to process</param>
public override async Task OnMessageReceivedAsync(IMessage message)
{
if (message is PingMessage)
{
await SendAsync(new PongMessage());
}
else
{
//TODO: Remove this if we don't hit this
Debugger.Break();
}
_logger.LogDebug("Received ping message on Heartbeat channel");
await SendAsync(new PongMessage());
_logger.LogDebug("Sent pong message on Heartbeat channel");
_timer.Stop();
_timer.Start();
}

public void StartTimeoutTimer()
{
_timer.Start();
_logger.LogDebug("Started heartbeat timeout timer");
}

public void StopTimeoutTimer()
{
_timer.Stop();
_logger.LogDebug("Stopped heartbeat timeout timer");
}

private void TimerElapsed(object sender, ElapsedEventArgs e)
{
_logger.LogDebug("Heartbeat timeout");
StatusChanged?.Invoke(this, e);
}
}
}
11 changes: 11 additions & 0 deletions Sharpcaster/ChromeCastClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,26 @@ public async Task<ChromecastStatus> ConnectChromecast(ChromecastReceiver chromec

_client = new TcpClient();
await _client.ConnectAsync(chromecastReceiver.DeviceUri.Host, chromecastReceiver.Port);

//Open SSL stream to Chromecast and bypass all SSL validation
var secureStream = new SslStream(_client.GetStream(), true, (sender, certificate, chain, sslPolicyErrors) => true);
await secureStream.AuthenticateAsClientAsync(chromecastReceiver.DeviceUri.Host);
_stream = secureStream;


ReceiveTcs = new TaskCompletionSource<bool>();
Receive();
GetChannel<IHeartbeatChannel>().StartTimeoutTimer();
GetChannel<IHeartbeatChannel>().StatusChanged += HeartBeatTimedOut;
await GetChannel<IConnectionChannel>().ConnectAsync();
return await GetChannel<IReceiverChannel>().GetChromecastStatusAsync();
}

private async void HeartBeatTimedOut(object sender, EventArgs e)
{
await DisconnectAsync();
}

private void Receive()
{
Task.Run(async () =>
Expand Down Expand Up @@ -237,6 +246,8 @@ public async Task DisconnectAsync()
{
channel.GetType().GetProperty("Status").SetValue(channel, null);
}
GetChannel<IHeartbeatChannel>().StopTimeoutTimer();
GetChannel<IHeartbeatChannel>().StatusChanged -= HeartBeatTimedOut;
await Dispose();
}

Expand Down
13 changes: 12 additions & 1 deletion Sharpcaster/Interfaces/IHeartbeatChannel.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
namespace Sharpcaster.Interfaces
using System.Net.NetworkInformation;
using System;
using System.Threading.Tasks;
using System.Timers;

namespace Sharpcaster.Interfaces
{
/// <summary>
/// Interface for the heartbeat channel
/// </summary>
interface IHeartbeatChannel : IChromecastChannel
{
void StartTimeoutTimer();
void StopTimeoutTimer();
/// <summary>
/// Raised when the status has changed
/// </summary>
event EventHandler StatusChanged;
}
}

0 comments on commit 5b11b6c

Please sign in to comment.