diff --git a/Source/v2/Meadow.Cli/Meadow.CLI.csproj b/Source/v2/Meadow.Cli/Meadow.CLI.csproj
index 9cee05e5..7f9c4f62 100644
--- a/Source/v2/Meadow.Cli/Meadow.CLI.csproj
+++ b/Source/v2/Meadow.Cli/Meadow.CLI.csproj
@@ -10,7 +10,7 @@
Wilderness Labs, Inc
Wilderness Labs, Inc
true
- 2.0.44.0
+ 2.0.46.0
AnyCPU
http://developer.wildernesslabs.co/Meadow/Meadow.CLI/
https://github.com/WildernessLabs/Meadow.CLI
diff --git a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs
index 9d681764..75a3506c 100644
--- a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs
+++ b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs
@@ -6,5 +6,5 @@ namespace Meadow.CLI;
public static class Constants
{
- public const string CLI_VERSION = "2.0.44.0";
+ public const string CLI_VERSION = "2.0.46.0";
}
\ No newline at end of file
diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs
index 9a607bd3..3c094aa4 100755
--- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs
+++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs
@@ -1,6 +1,5 @@
using System.Buffers;
using System.IO.Ports;
-using System.Net;
using System.Security.Cryptography;
namespace Meadow.Hcom;
@@ -23,6 +22,7 @@ public partial class SerialConnection : ConnectionBase, IDisposable
private ConnectionState _state;
private readonly List _listeners = new List();
private readonly Queue _pendingCommands = new Queue();
+ private readonly AutoResetEvent _commandEvent = new AutoResetEvent(false);
private bool _maintainConnection;
private Thread? _connectionManager = null;
private readonly List _textList = new List();
@@ -77,7 +77,6 @@ private bool MaintainConnection
Name = "HCOM Connection Manager"
};
_connectionManager.Start();
-
}
}
}
@@ -210,6 +209,7 @@ public override void Detach()
var count = _messageCount;
_pendingCommands.Enqueue(command);
+ _commandEvent.Set();
while (timeout-- > 0)
{
@@ -246,6 +246,7 @@ private void CommandManager()
{
while (!_isDisposed)
{
+ _commandEvent.WaitOne();
while (_pendingCommands.Count > 0)
{
Debug.WriteLine($"There are {_pendingCommands.Count} pending commands");
@@ -261,8 +262,6 @@ private void CommandManager()
// TODO: re-queue on fail?
}
}
-
- Thread.Sleep(1000);
}
}
@@ -298,6 +297,7 @@ public void EnqueueRequest(IRequest command)
}
_pendingCommands.Enqueue(command);
+ _commandEvent.Set();
}
private void EncodeAndSendPacket(byte[] messageBytes, CancellationToken? cancellationToken = null)
@@ -1226,16 +1226,16 @@ public override async Task StartDebuggingSession(int port, ILog
throw new DeviceNotFoundException();
}
+ var debuggingServer = new DebuggingServer(this, port, logger);
+
+ logger?.LogDebug("Tell the Debugging Server to Start Listening");
+ _ = debuggingServer.StartListening(cancellationToken);
+
logger?.LogDebug($"Start Debugging on port: {port}");
await Device.StartDebugging(port, logger, cancellationToken);
await WaitForMeadowAttach(cancellationToken);
- var endpoint = new IPEndPoint(IPAddress.Loopback, port);
- var debuggingServer = new DebuggingServer(this, Device, endpoint, logger);
-
- logger?.LogDebug("Tell the Debugging Server to Start Listening");
- await debuggingServer.StartListening(cancellationToken);
return debuggingServer;
}
@@ -1267,15 +1267,5 @@ public override async Task SendDebuggerData(byte[] debuggerData, uint userData,
_lastRequestConcluded = null;
EnqueueRequest(command);
-
- var success = await WaitForResult(() =>
- {
- if (_lastRequestConcluded != null && _lastRequestConcluded == RequestType.HCOM_MDOW_REQUEST_RTC_SET_TIME_CMD)
- {
- return true;
- }
-
- return false;
- }, cancellationToken);
}
}
\ No newline at end of file
diff --git a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs
new file mode 100644
index 00000000..5849ab1c
--- /dev/null
+++ b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs
@@ -0,0 +1,185 @@
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+
+namespace Meadow.Hcom;
+
+public partial class DebuggingServer
+{
+ private class ActiveClient : IDisposable
+ {
+ private readonly IMeadowConnection _connection;
+ private readonly TcpClient _tcpClient;
+ private readonly NetworkStream _networkStream;
+ private readonly CancellationTokenSource _cts;
+ private readonly Task _receiveVsDebugDataTask;
+ private readonly Task _receiveMeadowDebugDataTask;
+ private readonly ILogger? _logger;
+ private bool _disposed;
+ private readonly BlockingCollection _debuggerMessages = new();
+ private readonly AutoResetEvent _vsDebugDataReady = new(false);
+
+ internal ActiveClient(IMeadowConnection connection, TcpClient tcpClient, ILogger? logger, CancellationToken? cancellationToken)
+ {
+ _cts = cancellationToken != null
+ ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value)
+ : new CancellationTokenSource();
+
+ _logger = logger;
+ _connection = connection;
+ _tcpClient = tcpClient;
+ _networkStream = tcpClient.GetStream();
+
+ _logger?.LogDebug("Starting receive task");
+
+ _connection.DebuggerMessageReceived += MeadowConnection_DebuggerMessageReceived;
+
+ _receiveVsDebugDataTask = Task.Factory.StartNew(SendToMeadowAsync, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+ _receiveMeadowDebugDataTask = Task.Factory.StartNew(SendToVisualStudio, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+ }
+
+ private void MeadowConnection_DebuggerMessageReceived(object sender, byte[] e)
+ {
+ _debuggerMessages.Add(e);
+ _vsDebugDataReady.Set();
+ }
+
+ private const int RECEIVE_BUFFER_SIZE = 256;
+
+ private async Task SendToMeadowAsync()
+ {
+ try
+ {
+ using var md5 = MD5.Create();
+ var receiveBuffer = ArrayPool.Shared.Rent(RECEIVE_BUFFER_SIZE);
+ var meadowBuffer = Array.Empty();
+
+ while (!_cts.Token.IsCancellationRequested)
+ {
+ if (_networkStream != null && _networkStream.CanRead)
+ {
+ int bytesRead;
+ do
+ {
+ bytesRead = await _networkStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, _cts.Token);
+
+ if (bytesRead == 0 || _cts.Token.IsCancellationRequested)
+ {
+ continue;
+ }
+
+ var destIndex = meadowBuffer.Length;
+ Array.Resize(ref meadowBuffer, destIndex + bytesRead);
+ Array.Copy(receiveBuffer, 0, meadowBuffer, destIndex, bytesRead);
+
+ _logger?.LogTrace("Received {count} bytes from VS, will forward to HCOM/Meadow. {hash}",
+ meadowBuffer.Length,
+ BitConverter.ToString(md5.ComputeHash(meadowBuffer))
+ .Replace("-", string.Empty)
+ .ToLowerInvariant());
+
+ await _connection.SendDebuggerData(meadowBuffer, 0, _cts.Token);
+ meadowBuffer = Array.Empty();
+ } while (_networkStream.DataAvailable);
+ }
+ else
+ {
+ _logger?.LogInformation("Unable to Read Data from Visual Studio");
+ _logger?.LogTrace("Unable to Read Data from Visual Studio");
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ _logger?.LogInformation("Visual Studio has Disconnected");
+ _logger?.LogTrace(ioe, "Visual Studio has Disconnected");
+ }
+ catch (ObjectDisposedException ode)
+ {
+ _logger?.LogInformation("Visual Studio has stopped debugging");
+ _logger?.LogTrace(ode, "Visual Studio has stopped debugging");
+ }
+ catch (Exception ex)
+ {
+ _logger?.LogError($"Error receiving data from Visual Studio.\nError: {ex.Message}\nStackTrace:\n{ex.StackTrace}");
+ throw;
+ }
+ }
+
+ private async Task SendToVisualStudio()
+ {
+ try
+ {
+ while (!_cts.Token.IsCancellationRequested)
+ {
+ if (_networkStream != null && _networkStream.CanWrite)
+ {
+ _vsDebugDataReady.WaitOne();
+
+ while (_debuggerMessages.Count > 0)
+ {
+ var byteData = _debuggerMessages.Take(_cts.Token);
+
+ _logger?.LogTrace("Received {count} bytes from Meadow, will forward to VS", byteData.Length);
+ if (!_tcpClient.Connected)
+ {
+ _logger?.LogDebug("Cannot forward data, Visual Studio is not connected");
+ return;
+ }
+
+ await _networkStream.WriteAsync(byteData, 0, byteData.Length, _cts.Token);
+ _logger?.LogTrace("Forwarded {count} bytes to VS", byteData.Length);
+ }
+ }
+ else
+ {
+ _logger?.LogInformation("Unable to Write Data from Visual Studio");
+ }
+ }
+ }
+ catch (OperationCanceledException oce)
+ {
+ _logger?.LogInformation("Operation Cancelled");
+ _logger?.LogTrace(oce, "Operation Cancelled");
+ }
+ catch (Exception ex)
+ {
+ _logger?.LogError($"Error sending data to Visual Studio.\nError: {ex.Message}\nStackTrace:\n{ex.StackTrace}");
+
+ if (!_cts.Token.IsCancellationRequested)
+ {
+ throw;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _logger?.LogTrace("Disposing ActiveClient");
+ _cts.Cancel();
+ try
+ {
+ Task.WhenAll(_receiveVsDebugDataTask, _receiveMeadowDebugDataTask).Wait(TimeSpan.FromSeconds(10));
+ }
+ catch (AggregateException ex)
+ {
+ _logger?.LogError("Error waiting for tasks to complete during dispose", ex);
+ }
+ _tcpClient.Dispose();
+ _networkStream.Dispose();
+ _cts.Dispose();
+
+ if (_connection != null)
+ {
+ _connection.DebuggerMessageReceived -= MeadowConnection_DebuggerMessageReceived;
+ }
+ _disposed = true;
+ }
+ }
+}
diff --git a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs
index f23373bf..ce0a097e 100644
--- a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs
+++ b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs
@@ -1,47 +1,40 @@
-using System.Buffers;
-using System.Collections.Concurrent;
-using System.Net;
+using System.Net;
using System.Net.Sockets;
-using System.Security.Cryptography;
namespace Meadow.Hcom;
// This TCP server directly interacts with Visual Studio debugging.
// What it receives from Visual Studio it forwards to Meadow.
// What it receives from Meadow it forwards to Visual Studio.
-public class DebuggingServer : IDisposable
+public partial class DebuggingServer : IDisposable
{
// VS 2019 - 4024
// VS 2017 - 4022
// VS 2015 - 4020
- public IPEndPoint LocalEndpoint { get; private set; }
private readonly object _lck = new();
private CancellationTokenSource? _cancellationTokenSource;
private readonly ILogger? _logger;
- private readonly IMeadowDevice _meadow;
private readonly IMeadowConnection _connection;
private ActiveClient? _activeClient;
- private int _activeClientCount = 0;
private readonly TcpListener _listener;
- private Task? _listenerTask;
- private bool _isReady;
+ private readonly Task? _listenerTask;
public bool Disposed;
// Constructor
///
/// Create a new DebuggingServer for proxying debug data between VS and Meadow
///
- /// The to debug
+ /// The meadow connection
/// The to listen for incoming debugger connections
/// The to logging state information
- public DebuggingServer(IMeadowConnection connection, IMeadowDevice meadow, IPEndPoint localEndpoint, ILogger? logger)
+ public DebuggingServer(IMeadowConnection connection, int port, ILogger? logger)
{
- LocalEndpoint = localEndpoint;
+ _logger = logger;
_connection = connection;
- _meadow = meadow;
- _logger = logger;
- _listener = new TcpListener(LocalEndpoint);
+ var endPoint = new IPEndPoint(IPAddress.Loopback, port);
+
+ _listener = new TcpListener(endPoint);
}
///
@@ -51,23 +44,14 @@ public DebuggingServer(IMeadowConnection connection, IMeadowDevice meadow, IPEnd
/// A representing the startup operation
public async Task StartListening(CancellationToken cancellationToken)
{
- if (cancellationToken != null)
- {
- _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- _listenerTask = Task.Factory.StartNew(StartListener, TaskCreationOptions.LongRunning);
- var startTimeout = DateTime.UtcNow.AddSeconds(60);
- while (DateTime.UtcNow < startTimeout)
- {
- if (_isReady)
- {
- return;
- }
+ _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- await Task.Delay(100, cancellationToken);
- }
+ _listener.Start();
+ _logger?.LogInformation($"Listening for Visual Studio to connect");
- throw new Exception("DebuggingServer did not start listening within the 60 second timeout.");
- }
+ // This call will wait for the client to connect, before continuing. We shouldn't need a loop.
+ var tcpClient = await _listener.AcceptTcpClientAsync();
+ _activeClient = CreateActiveClient(tcpClient);
}
///
@@ -86,61 +70,29 @@ public async Task StopListening()
}
}
- private async Task StartListener()
- {
- try
- {
- _listener.Start();
- LocalEndpoint = (IPEndPoint)_listener.LocalEndpoint;
- _logger?.LogInformation($"Listening for Visual Studio to connect on {LocalEndpoint.Address}:{LocalEndpoint.Port}" + Environment.NewLine);
- _isReady = true;
-
- // This call will wait for the client to connect, before continuing. We shouldn't need a loop.
- TcpClient tcpClient = await _listener.AcceptTcpClientAsync();
- OnConnect(tcpClient);
- }
- catch (SocketException soex)
- {
- _logger?.LogError("A Socket error occurred. The port may already be in use. Try rebooting to free up the port.");
- _logger?.LogError($"Error:\n{soex.Message} \nStack Trace:\n{soex.StackTrace}");
- }
- catch (Exception ex)
- {
- _logger?.LogError("An unhandled exception occurred while listening for debugging connections.");
- _logger?.LogError($"Error:\n{ex.Message} \nStack Trace:\n{ex.StackTrace}");
- }
- }
-
- private void OnConnect(TcpClient tcpClient)
+ private ActiveClient? CreateActiveClient(TcpClient tcpClient)
{
try
{
lock (_lck)
{
_logger?.LogInformation("Visual Studio has Connected" + Environment.NewLine);
- if (_activeClientCount > 0 && _activeClient?.Disposed == false)
+
+ if (_activeClient != null)
{
_logger?.LogDebug("Closing active client");
- Debug.Assert(_activeClientCount == 1);
- Debug.Assert(_activeClient != null);
- CloseActiveClient();
+ _activeClient?.Dispose();
+ _activeClient = null;
}
- _activeClient = new ActiveClient(_connection, tcpClient, _logger, _cancellationTokenSource?.Token);
- _activeClientCount++;
+ return new ActiveClient(_connection, tcpClient, _logger, _cancellationTokenSource?.Token);
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "An error occurred while connecting to Visual Studio");
}
- }
-
- internal void CloseActiveClient()
- {
- _activeClient?.Dispose();
- _activeClient = null;
- _activeClientCount = 0;
+ return null;
}
public void Dispose()
@@ -157,192 +109,4 @@ public void Dispose()
Disposed = true;
}
}
-
- // Embedded class
- private class ActiveClient : IDisposable
- {
- private readonly IMeadowConnection _connection;
- private readonly TcpClient _tcpClient;
- private readonly NetworkStream _networkStream;
-
- private readonly CancellationTokenSource _cts;
- private readonly Task _receiveVsDebugDataTask;
- private readonly Task _receiveMeadowDebugDataTask;
- private readonly ILogger? _logger;
- public bool Disposed = false;
- private readonly BlockingCollection _debuggerMessages = new();
-
- // Constructor
- internal ActiveClient(IMeadowConnection connection, TcpClient tcpClient, ILogger? logger, CancellationToken? cancellationToken)
- {
- if (cancellationToken != null)
- {
- _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value);
- }
- else
- {
- _cts = new CancellationTokenSource();
- }
-
- _logger = logger;
- _connection = connection;
- _tcpClient = tcpClient;
- _networkStream = tcpClient.GetStream();
- _logger?.LogDebug("Starting receive task");
- _receiveVsDebugDataTask = Task.Factory.StartNew(SendToMeadowAsync, TaskCreationOptions.LongRunning);
- _receiveMeadowDebugDataTask = Task.Factory.StartNew(SendToVisualStudio, TaskCreationOptions.LongRunning);
-
- _connection.DebuggerMessageReceived += MeadowConnection_DebuggerMessageReceived;
- }
-
- private void MeadowConnection_DebuggerMessageReceived(object sender, byte[] e)
- {
- _logger?.LogDebug("Debugger Message Received, Adding to collection");
- _debuggerMessages.Add(e);
- }
-
- private const int RECEIVE_BUFFER_SIZE = 256;
-
- private async Task SendToMeadowAsync()
- {
- try
- {
- using var md5 = MD5.Create();
- // Receive from Visual Studio and send to Meadow
- var receiveBuffer = ArrayPool.Shared.Rent(RECEIVE_BUFFER_SIZE);
- var meadowBuffer = Array.Empty();
-
- while (!_cts.IsCancellationRequested)
- {
- if (_networkStream != null && _networkStream.CanRead)
- {
- int bytesRead;
- do
- {
- bytesRead = await _networkStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, _cts.Token);
-
- if (bytesRead == 0 || _cts.IsCancellationRequested)
- {
- continue;
- }
-
- var destIndex = meadowBuffer.Length;
- Array.Resize(ref meadowBuffer, destIndex + bytesRead);
- Array.Copy(receiveBuffer, 0, meadowBuffer, destIndex, bytesRead);
-
- // Forward the RECIEVE_BUFFER_SIZE chunk to Meadow immediately
- _logger?.LogTrace("Received {count} bytes from VS, will forward to HCOM/Meadow. {hash}",
- meadowBuffer.Length,
- BitConverter.ToString(md5.ComputeHash(meadowBuffer))
- .Replace("-", string.Empty)
- .ToLowerInvariant());
-
- await _connection.SendDebuggerData(meadowBuffer, 0, _cts.Token);
- meadowBuffer = Array.Empty();
-
- // Ensure we read all the data in this message before passing it along
- // I'm not sure this is actually needed, the whole message should get read at once.
- } while (_networkStream.DataAvailable);
- }
- else
- {
- // User probably hit stop
- _logger?.LogInformation("Unable to Read Data from Visual Studio");
- _logger?.LogTrace("Unable to Read Data from Visual Studio");
- }
- }
- }
- catch (IOException ioe)
- {
- // VS client probably died
- _logger?.LogInformation("Visual Studio has Disconnected" + Environment.NewLine);
- _logger?.LogTrace(ioe, "Visual Studio has Disconnected");
- }
- catch (ObjectDisposedException ode)
- {
- // User probably hit stop
- _logger?.LogInformation("Visual Studio has stopped debugging" + Environment.NewLine);
- _logger?.LogTrace(ode, "Visual Studio has stopped debugging");
- }
- catch (Exception ex)
- {
- _logger?.LogError($"Error receiving data from Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}");
- throw;
- }
- }
-
- private async Task SendToVisualStudio()
- {
- try
- {
- while (!_cts.IsCancellationRequested)
- {
- if (_networkStream != null && _networkStream.CanWrite)
- {
- while (_debuggerMessages.Count > 0)
- {
- var byteData = _debuggerMessages.Take(_cts.Token);
- _logger?.LogTrace("Received {count} bytes from Meadow, will forward to VS", byteData.Length);
- if (!_tcpClient.Connected)
- {
- _logger?.LogDebug("Cannot forward data, Visual Studio is not connected");
- return;
- }
-
- await _networkStream.WriteAsync(byteData, 0, byteData.Length, _cts.Token);
- _logger?.LogTrace("Forwarded {count} bytes to VS", byteData.Length);
- }
- }
- else
- {
- // User probably hit stop
- _logger?.LogInformation("Unable to Write Data from Visual Studio");
- }
- }
- }
- catch (OperationCanceledException oce)
- {
- // User probably hit stop; Removed logging as User doesn't need to see this
- // Keeping it as a TODO in case we find a side effect that needs logging.
- _logger?.LogInformation("Operation Cancelled");
- _logger?.LogTrace(oce, "Operation Cancelled");
- }
- catch (Exception ex)
- {
- _logger?.LogError($"Error sending data to Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}");
-
- if (_cts.IsCancellationRequested)
- {
- throw;
- }
- }
- }
-
- public void Dispose()
- {
- lock (_tcpClient)
- {
- if (Disposed)
- {
- return;
- }
-
- _logger?.LogTrace("Disposing ActiveClient");
- _cts.Cancel(false);
- _receiveVsDebugDataTask.Wait(TimeSpan.FromSeconds(10));
- _receiveMeadowDebugDataTask.Wait(TimeSpan.FromSeconds(10));
- _receiveVsDebugDataTask?.Dispose();
- _receiveMeadowDebugDataTask?.Dispose();
- _tcpClient.Dispose();
- _networkStream.Dispose();
- _cts.Dispose();
-
- if (_connection != null)
- {
- _connection.DebuggerMessageReceived -= MeadowConnection_DebuggerMessageReceived;
- }
- Disposed = true;
- }
- }
- }
}
\ No newline at end of file