diff --git a/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs b/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs index 209d2e10..f8bacd16 100644 --- a/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs +++ b/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs @@ -4,6 +4,7 @@ using System.IO.Ports; using System.Linq; using System.Management; +using System.Net; using System.Threading; using System.Threading.Tasks; using Meadow.CLI.Internals.MeadowComms.RecvClasses; @@ -279,28 +280,48 @@ public static void ForwardMonoDataToVisualStudio(byte[] debuggerData) debuggingServer.SendToVisualStudio(debuggerData); } + // Creates a DebuggingServer that can listen on the given port on all network interfaces. + public static Task CreateDebuggingServer(MeadowSerialDevice meadow, int vsDebugPort = 0) + { + if (vsDebugPort == 0) + { + Console.WriteLine($"Without '--VSDebugPort' being specified, will assume Visual Studio 2019 using default port {DefaultVS2019DebugPort}"); + vsDebugPort = DefaultVS2019DebugPort; + } + return CreateDebuggingServer(meadow, new IPEndPoint(IPAddress.Any, vsDebugPort)); + } + // Enter StartDebugging mode. - public static async Task StartDebugging(MeadowSerialDevice meadow, int vsDebugPort) + public static async Task CreateDebuggingServer(MeadowSerialDevice meadow, IPEndPoint localEndpoint) { + // Create the DebuggingServer first so we aren't racing for it in ForwardMonoDataToVisualStudio after Meadow restarts + debuggingServer = new DebuggingServer(localEndpoint); + // Tell meadow to start it's debugging server, after restarting. _meadowRequestType = HcomMeadowRequestType.HCOM_MDOW_REQUEST_MONO_START_DBG_SESSION; await new SendTargetData(meadow).SendSimpleCommand(_meadowRequestType); // The previous command caused Meadow to restart. Therefore, we must reestablish // Meadow communication. - meadow.AttemptToReconnectToMeadow(); - - // Create an instance of the TCP socket send/receiver class and - // start it receiving. - if (vsDebugPort == 0) + var attempts = 0; + retry: + try { - Console.WriteLine($"Without '--VSDebugPort' being specified, will assume Visual Studio 2019 using default port {DefaultVS2019DebugPort}"); - vsDebugPort = DefaultVS2019DebugPort; + attempts++; + meadow.AttemptToReconnectToMeadow(); + } catch (Exception ex) when (ex is IOException || ex.InnerException is IOException) + { + if (attempts < 5) + { + await Task.Yield(); + goto retry; + } else + { + throw; + } } - // Start the local Meadow.CLI debugging server - debuggingServer = new DebuggingServer(vsDebugPort); - debuggingServer.StartListening(meadow); + return debuggingServer; } public static void EnterEchoMode(MeadowSerialDevice meadow) @@ -332,22 +353,22 @@ public static async Task Esp32Restart(MeadowSerialDevice meadow) //public static async Task // GetInitialFileBytes(MeadowSerialDevice meadow, string fileName) - // { - // await ProcessCommand(meadow, HcomMeadowRequestType.HCOM_MDOW_REQUEST_GET_INITIAL_FILE_BYTES); - + // { + // await ProcessCommand(meadow, HcomMeadowRequestType.HCOM_MDOW_REQUEST_GET_INITIAL_FILE_BYTES); + // _meadowRequestType = HcomMeadowRequestType.HCOM_MDOW_REQUEST_GET_INITIAL_FILE_BYTES; // await new SendTargetData(meadow).SendSimpleCommand(_meadowRequestType); - // return await WaitForResponseMessage(meadow, p => p.MessageType == MeadowMessageType.Data, millisecondDelay: 1000); - - /* + // return await WaitForResponseMessage(meadow, p => p.MessageType == MeadowMessageType.Data, millisecondDelay: 1000); + + /* await ProcessCommand(meadow, HcomMeadowRequestType.HCOM_MDOW_REQUEST_GET_INITIAL_FILE_BYTES); await new SendTargetData(meadow).SendSimpleCommand(_meadowRequestType); - return await WaitForResponseMessage(meadow, p => p.MessageType == MeadowMessageType.DeviceInfo, millisecondDelay: timeoutMs); - */ + return await WaitForResponseMessage(meadow, p => p.MessageType == MeadowMessageType.DeviceInfo, millisecondDelay: timeoutMs); + */ // } - public static async Task DeployApp(MeadowSerialDevice meadow, string applicationFilePath) + public static async Task DeployApp(MeadowSerialDevice meadow, string applicationFilePath, bool includeDebugSymbols = true) { if (!File.Exists(applicationFilePath)) { @@ -371,8 +392,7 @@ public static async Task DeployApp(MeadowSerialDevice meadow, string application var files = new List(); var crcs = new List(); - - foreach (var file in paths) + void AddFile(string file, bool lookForPDB) { using (FileStream fs = File.Open(file, FileMode.Open)) { @@ -388,6 +408,17 @@ public static async Task DeployApp(MeadowSerialDevice meadow, string application files.Add(Path.GetFileName(file)); crcs.Add(crc); } + if (lookForPDB) + { + var pdbFile = Path.ChangeExtension(file, "pdb"); + if (File.Exists(pdbFile)) + AddFile(pdbFile, false); + } + } + + foreach (var file in paths) + { + AddFile(file, includeDebugSymbols); } var dependences = AssemblyManager.GetDependencies(fi.Name, fi.DirectoryName); @@ -395,20 +426,7 @@ public static async Task DeployApp(MeadowSerialDevice meadow, string application //crawl dependences foreach (var file in dependences) { - using (FileStream fs = File.Open(Path.Combine(fi.DirectoryName, file), FileMode.Open)) - { - var len = (int)fs.Length; - var bytes = new byte[len]; - - fs.Read(bytes, 0, len); - - //0x - var crc = CrcTools.Crc32part(bytes, len, 0);// 0x04C11DB7); - - Console.WriteLine($"{file} crc is {crc}"); - files.Add(Path.GetFileName(file)); - crcs.Add(crc); - } + AddFile(Path.Combine(fi.DirectoryName, file), includeDebugSymbols); } // delete unused files @@ -460,12 +478,12 @@ public static async Task ProcessCommand(MeadowSerialDevice meadow, HcomMeadowReq } } - /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// /// public static async Task<(bool Success, string Message, MeadowMessageType MessageType)> WaitForResponseMessage( diff --git a/Meadow.CLI.Core/Internals/MeadowComms/MeadowSerialDataProcessor.cs b/Meadow.CLI.Core/Internals/MeadowComms/MeadowSerialDataProcessor.cs index 01fcca6e..ba7a71c0 100644 --- a/Meadow.CLI.Core/Internals/MeadowComms/MeadowSerialDataProcessor.cs +++ b/Meadow.CLI.Core/Internals/MeadowComms/MeadowSerialDataProcessor.cs @@ -332,7 +332,7 @@ bool ParseAndProcessReceivedPacket(byte[] receivedMsg, int receivedMsgLen) // Debug message from Meadow for Visual Studio case (ushort)HcomHostRequestType.HCOM_HOST_REQUEST_DEBUGGING_MONO_DATA: - ConsoleOut($"Debugging message from Meadow for Visual Studio"); // TESTING + //ConsoleOut($"Debugging message from Meadow for Visual Studio"); // TESTING MeadowDeviceManager.ForwardMonoDataToVisualStudio(processor.MessageData); break; diff --git a/Meadow.CLI.Core/Internals/MeadowComms/RecvClasses/DebuggingServer.cs b/Meadow.CLI.Core/Internals/MeadowComms/RecvClasses/DebuggingServer.cs index 21d3d46c..1b817540 100644 --- a/Meadow.CLI.Core/Internals/MeadowComms/RecvClasses/DebuggingServer.cs +++ b/Meadow.CLI.Core/Internals/MeadowComms/RecvClasses/DebuggingServer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Sockets; @@ -16,51 +17,68 @@ public class DebuggingServer // VS 2019 - 4024 // VS 2017 - 4022 // VS 2015 - 4020 - int vsPort; + public IPEndPoint LocalEndpoint { get; private set; } ActiveClient activeClient; int activeClientCount = 0; + List buffers = new List(); + // Constructor - public DebuggingServer(int visualStudioPort) + public DebuggingServer(IPEndPoint localEndpoint) { - vsPort = visualStudioPort; + LocalEndpoint = localEndpoint; } public async void StartListening(MeadowSerialDevice meadow) { try { - IPHostEntry ipHostInfo = Dns.GetHostEntry("localhost"); - IPAddress ipAddress = ipHostInfo.AddressList[0]; - IPEndPoint localEndPoint = new IPEndPoint(ipAddress, vsPort); - - TcpListener tcpListener = new TcpListener(localEndPoint); + TcpListener tcpListener = new TcpListener(LocalEndpoint); tcpListener.Start(); + LocalEndpoint = (IPEndPoint)tcpListener.LocalEndpoint; Console.WriteLine("Listening for Visual Studio to connect"); while(true) { - await Task.Run(async () => - { - // Wait for client to connect - TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync(); + // Wait for client to connect + TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync(); + OnConnect(meadow, tcpClient); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } - // tcpClient valid after connection - Console.WriteLine("Visual Studio has connected"); - if (activeClientCount > 0) - { - Debug.Assert(activeClientCount == 1); - Debug.Assert(activeClient != null); - activeClient.Close(); - activeClient = null; - activeClientCount = 0; - } - - activeClient = new ActiveClient(this, tcpClient); - activeClient.ReceiveVSDebug(meadow); - activeClientCount++; - }); + public void Connect(MeadowSerialDevice meadow) + { + TcpClient tcpClient = new TcpClient(); + tcpClient.Connect(LocalEndpoint); + OnConnect(meadow, tcpClient); + } + + void OnConnect(MeadowSerialDevice meadow, TcpClient tcpClient) + { + try + { + Console.WriteLine("Visual Studio has connected"); + if (activeClientCount > 0) + { + Debug.Assert(activeClientCount == 1); + Debug.Assert(activeClient != null); + CloseActiveClient(); + } + + activeClient = new ActiveClient(this, tcpClient); + lock (buffers) + { + foreach (var buffer in buffers) + activeClient.SendToVisualStudio(buffer); + buffers.Clear(); } + activeClient.ReceiveVSDebug(meadow); + activeClientCount++; } catch (Exception ex) { @@ -73,11 +91,21 @@ internal void CloseActiveClient() activeClient.Close(); activeClient = null; activeClientCount = 0; + lock (buffers) + buffers.Clear(); } public void SendToVisualStudio(byte[] byteData) { - activeClient.SendToVisualStudio(byteData); + if (activeClient is ActiveClient ac) + { + ac.SendToVisualStudio(byteData); + return; + } + + // Buffer the data until VS connects + lock (buffers) + buffers.Add(byteData); } // Imbedded class @@ -112,25 +140,31 @@ internal async void ReceiveVSDebug(MeadowSerialDevice meadow) // Receive from Visual Studio and send to Meadow await Task.Run(async () => { + var recvdBuffer = new byte[490]; + var meadowBuffer = Array.Empty(); while (tcpClient.Connected && okayToRun) { - var recvdBuffer = new byte[490]; - var bytesRead = await networkStream.ReadAsync(recvdBuffer, 0, recvdBuffer.Length); - if (!okayToRun) + int bytesRead; + + read: + bytesRead = await networkStream.ReadAsync(recvdBuffer, 0, recvdBuffer.Length); + if (bytesRead == 0 || !okayToRun) break; + + var destIndex = meadowBuffer.Length; + Array.Resize(ref meadowBuffer, destIndex + bytesRead); + Array.Copy(recvdBuffer, 0, meadowBuffer, destIndex, bytesRead); - if (bytesRead > 0) - { - // Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}-Received {bytesRead} bytes from VS will forward to HCOM"); + // Ensure we read all the data in this message before passing it along + if (networkStream.DataAvailable) + goto read; - // Need a buffer the exact size of received data to work with CLI - var meadowBuffer = new byte[bytesRead]; - Array.Copy(recvdBuffer, 0, meadowBuffer, 0, bytesRead); + // Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}-Received {bytesRead} bytes from VS will forward to HCOM"); - // Forward to Meadow - MeadowDeviceManager.ForwardVisualStudioDataToMono(meadowBuffer, meadow, 0); - //Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}-Forwarded {bytesRead} from VS to Meadow"); - } + // Forward to Meadow + MeadowDeviceManager.ForwardVisualStudioDataToMono(meadowBuffer, meadow, 0); + //Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}-Forwarded {bytesRead} from VS to Meadow"); + meadowBuffer = Array.Empty(); } }); } @@ -161,10 +195,7 @@ public async void SendToVisualStudio(byte[] byteData) return; } - await Task.Run(async () => - { - await networkStream.WriteAsync(byteData, 0, byteData.Length); - }); + await networkStream.WriteAsync(byteData, 0, byteData.Length); } catch (Exception e) { diff --git a/Meadow.CLI/Program.cs b/Meadow.CLI/Program.cs index 1977747a..d26d3b7e 100644 --- a/Meadow.CLI/Program.cs +++ b/Meadow.CLI/Program.cs @@ -464,7 +464,7 @@ static async Task ProcessHcom(Options options) } else if (options.StartDebugging) { - MeadowDeviceManager.StartDebugging(device, options.VSDebugPort); + (await MeadowDeviceManager.CreateDebuggingServer(device, options.VSDebugPort)).StartListening(device); Console.WriteLine($"Ready for Visual Studio debugging"); options.KeepAlive = true; } diff --git a/Meadow.CLI/Properties/launchSettings.json b/Meadow.CLI/Properties/launchSettings.json index 7cfc4273..a0fd8faa 100644 --- a/Meadow.CLI/Properties/launchSettings.json +++ b/Meadow.CLI/Properties/launchSettings.json @@ -12,6 +12,10 @@ "commandName": "Project", "commandLineArgs": "--Logout" }, + "StartDebugging": { + "commandName": "Project", + "commandLineArgs": "--StartDebugging" + }, "FlashOS": { "commandName": "Project", "commandLineArgs": "--FlashOS" diff --git a/README.md b/README.md index 20121689..8da4ebbf 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,16 @@ You can set the debug trace level to values 0, 1, 2, or 3. 2 is the most useful. `MeadowCLI.exe --GetDeviceInfo` - Outputs Meadow OS version and other information `MeadowCLI.exe --GetDeviceName` - Outputs Meadow device name contained in configuration file +### Debugging + +`MeadowCLI.exe --VSDebugPort XXXX --StartDebugging` - Starts listening for debugging connection (substitute XXXX for a free port number) + +Note: you can use SDB command line debugger from https://github.com/mono/sdb. Just build it according to its readme, run the above command and then: + +`sdb "connect 127.0.0.1 XXXX"` (substitute XXXX for the same port number as above) + + + ## Running applications You'll typically need at least 5 files installed to the Meadow flash to run a Meadow app: