diff --git a/Meadow.CLI/Properties/launchSettings.json b/Meadow.CLI/Properties/launchSettings.json index a9bc47bc..8260665f 100644 --- a/Meadow.CLI/Properties/launchSettings.json +++ b/Meadow.CLI/Properties/launchSettings.json @@ -26,7 +26,7 @@ }, "FileDelete": { "commandName": "Project", - "commandLineArgs": "file delete -f" + "commandLineArgs": "file delete -f dns.conf" }, "FileList": { "commandName": "Project", @@ -106,7 +106,7 @@ }, "UsePort": { "commandName": "Project", - "commandLineArgs": "use port COM10" + "commandLineArgs": "use port COM7" }, "Version": { "commandName": "Project", diff --git a/Source/v2/Meadow.Cli/AppManager.cs b/Source/v2/Meadow.Cli/AppManager.cs index f80f3f76..aa09722f 100644 --- a/Source/v2/Meadow.Cli/AppManager.cs +++ b/Source/v2/Meadow.Cli/AppManager.cs @@ -27,6 +27,7 @@ private static bool IsXmlDoc(string file) } public static async Task DeployApplication( + IPackageManager packageManager, IMeadowConnection connection, string localBinaryDirectory, bool includePdbs, @@ -34,23 +35,15 @@ public static async Task DeployApplication( ILogger logger, CancellationToken cancellationToken) { - // in order to deploy, the runtime must be disabled - var wasRuntimeEnabled = await connection.IsRuntimeEnabled(); - - if (wasRuntimeEnabled) - { - logger.LogInformation("Disabling runtime..."); - - await connection.RuntimeDisable(cancellationToken); - } - // TODO: add sub-folder support when HCOM supports it var localFiles = new Dictionary(); // get a list of files to send + var dependencies = packageManager.GetDependencies(new FileInfo(Path.Combine(localBinaryDirectory, "App.dll"))); + logger.LogInformation("Generating the list of files to deploy..."); - foreach (var file in Directory.GetFiles(localBinaryDirectory)) + foreach (var file in dependencies) { // TODO: add any other filtering capability here @@ -70,21 +63,36 @@ public static async Task DeployApplication( } // get a list of files on-device, with CRCs - var deviceFiles = await connection.GetFileList(true, cancellationToken); + var deviceFiles = await connection.GetFileList(true, cancellationToken) ?? Array.Empty(); + // get a list of files of the device files that are not in the list we intend to deploy + var removeFiles = deviceFiles + .Select(f => Path.GetFileName(f.Name)) + .Except(localFiles.Keys + .Select(f => Path.GetFileName(f))); - // erase all files on device not in list of files to send - - // send any file that has a different CRC - - - if (wasRuntimeEnabled) + // delete those files + foreach (var file in removeFiles) { - // restore runtime state - logger.LogInformation("Enabling runtime..."); - - await connection.RuntimeEnable(cancellationToken); + logger.LogInformation($"Deleting file '{file}'..."); + await connection.DeleteFile(file, cancellationToken); } + // now send all files with differing CRCs + foreach (var localFile in localFiles) + { + var existing = deviceFiles.FirstOrDefault(f => Path.GetFileName(f.Name) == Path.GetFileName(localFile.Key)); + + if (existing != null) + { + if (uint.Parse(existing.Crc.Substring(2), System.Globalization.NumberStyles.HexNumber) == localFile.Value) + { + // exists and has a matching CRC, skip it + continue; + } + } + + await connection?.WriteFile(localFile.Key, null, cancellationToken); + } } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/App/AppBuildCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/App/AppBuildCommand.cs index 1296f80e..5513faa9 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/App/AppBuildCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/App/AppBuildCommand.cs @@ -40,7 +40,7 @@ protected override async ValueTask ExecuteCommand(CancellationToken cancellation if (Configuration == null) Configuration = "Release"; - Logger.LogInformation($"Building {Configuration} configuration of of {path}..."); + Logger.LogInformation($"Building {Configuration} configuration of {path}..."); // TODO: enable cancellation of this call var success = _packageManager.BuildApplication(path, Configuration); diff --git a/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs index 3ac340d5..535c718c 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs @@ -8,12 +8,15 @@ namespace Meadow.CLI.Commands.DeviceManagement; [Command("app deploy", Description = "Deploys a built Meadow application to a target device")] public class AppDeployCommand : BaseDeviceCommand { + private IPackageManager _packageManager; + [CommandParameter(0, Name = "Path to folder containing the built application", IsRequired = false)] public string? Path { get; set; } = default!; - public AppDeployCommand(MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory) + public AppDeployCommand(IPackageManager packageManager, MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory) : base(connectionManager, loggerFactory) { + _packageManager = packageManager; } protected override async ValueTask ExecuteCommand(IMeadowConnection connection, Hcom.IMeadowDevice device, CancellationToken cancellationToken) @@ -23,6 +26,33 @@ protected override async ValueTask ExecuteCommand(IMeadowConnection connection, : Path; // is the path a file? + FileInfo file; + + var lastFile = string.Empty; + + // in order to deploy, the runtime must be disabled + var wasRuntimeEnabled = await connection.IsRuntimeEnabled(); + if (wasRuntimeEnabled) + { + Logger.LogInformation("Disabling runtime..."); + + await connection.RuntimeDisable(cancellationToken); + } + + connection.FileWriteProgress += (s, e) => + { + var p = (e.completed / (double)e.total) * 100d; + + if (e.fileName != lastFile) + { + Console.Write("\n"); + lastFile = e.fileName; + } + + // Console instead of Logger due to line breaking for progress bar + Console.Write($"Writing {e.fileName}: {p:0}% \r"); + }; + if (!File.Exists(path)) { // is it a valid directory? @@ -31,27 +61,39 @@ protected override async ValueTask ExecuteCommand(IMeadowConnection connection, Logger.LogError($"Invalid application path '{path}'"); return; } + + // does the directory have an App.dll in it? + file = new FileInfo(System.IO.Path.Combine(path, "App.dll")); + if (!file.Exists) + { + // it's a directory - we need to determine the latest build (they might have a Debug and a Release config) + var candidates = Cli.PackageManager.GetAvailableBuiltConfigurations(path, "App.dll"); + + if (candidates.Length == 0) + { + Logger.LogError($"Cannot find a compiled application at '{path}'"); + return; + } + + file = candidates.OrderByDescending(c => c.LastWriteTime).First(); + } } else { // TODO: only deploy if it's App.dll + file = new FileInfo(path); } - // do we have the full app path, or just the project root? + var targetDirectory = file.DirectoryName; - // TODO: determine the latest build + await AppManager.DeployApplication(_packageManager, connection, targetDirectory, true, false, Logger, cancellationToken); - await AppManager.DeployApplication(connection, "", true, false, Logger, cancellationToken); - - var success = false; - - if (!success) - { - Logger.LogError($"Build failed!"); - } - else + if (wasRuntimeEnabled) { - Logger.LogError($"Build success."); + // restore runtime state + Logger.LogInformation("Enabling runtime..."); + + await connection.RuntimeEnable(cancellationToken); } } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs new file mode 100644 index 00000000..85caf8b2 --- /dev/null +++ b/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs @@ -0,0 +1,163 @@ +using CliFx.Attributes; +using Meadow.Cli; +using Meadow.Hcom; +using Microsoft.Extensions.Logging; + +namespace Meadow.CLI.Commands.DeviceManagement; + +[Command("app run", Description = "Builds, trims and deploys a Meadow application to a target device")] +public class AppRunCommand : BaseDeviceCommand +{ + private IPackageManager _packageManager; + private string _lastFile; + + [CommandOption("no-prefix", 'n', IsRequired = false, Description = "When set, the message source prefix (e.g. 'stdout>') is suppressed during 'listen'")] + public bool NoPrefix { get; set; } + + [CommandOption('c', Description = "The build configuration to compile", IsRequired = false)] + public string? Configuration { get; set; } + + [CommandParameter(0, Name = "Path to folder containing the built application", IsRequired = false)] + public string? Path { get; set; } = default!; + + public AppRunCommand(IPackageManager packageManager, MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory) + : base(connectionManager, loggerFactory) + { + _packageManager = packageManager; + } + + protected override async ValueTask ExecuteCommand(IMeadowConnection connection, Hcom.IMeadowDevice device, CancellationToken cancellationToken) + { + string path = Path == null + ? AppDomain.CurrentDomain.BaseDirectory + : Path; + + if (!Directory.Exists(path)) + { + Logger.LogError($"Target directory '{path}' not found."); + return; + } + + var lastFile = string.Empty; + + // in order to deploy, the runtime must be disabled + var wasRuntimeEnabled = await connection.IsRuntimeEnabled(); + if (wasRuntimeEnabled) + { + Logger.LogInformation("Disabling runtime..."); + + await connection.RuntimeDisable(cancellationToken); + } + + if (!await BuildApplication(path, cancellationToken)) + { + return; + } + + if (!await TrimApplication(path, cancellationToken)) + { + return; + } + + // illink returns before all files are actually written. That's not fun, but we must just wait a little while. + await Task.Delay(1000); + + if (!await DeployApplication(connection, path, cancellationToken)) + { + return; + } + + Logger.LogInformation("Enabling the runtime..."); + await connection.RuntimeEnable(cancellationToken); + + Logger.LogInformation("Listening for messages from Meadow...\n"); + connection.DeviceMessageReceived += OnDeviceMessageReceived; + + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000); + } + + Logger.LogInformation("Listen cancelled..."); + } + + private Task BuildApplication(string path, CancellationToken cancellationToken) + { + if (Configuration == null) Configuration = "Debug"; + + Logger.LogInformation($"Building {Configuration} configuration of {path}..."); + + // TODO: enable cancellation of this call + return Task.FromResult(_packageManager.BuildApplication(path, Configuration)); + } + + private async Task TrimApplication(string path, CancellationToken cancellationToken) + { + // it's a directory - we need to determine the latest build (they might have a Debug and a Release config) + var candidates = Cli.PackageManager.GetAvailableBuiltConfigurations(path, "App.dll"); + + if (candidates.Length == 0) + { + Logger.LogError($"Cannot find a compiled application at '{path}'"); + return false; + } + + var file = candidates.OrderByDescending(c => c.LastWriteTime).First(); + + // if no configuration was provided, find the most recently built + Logger.LogInformation($"Trimming {file.FullName} (this may take a few seconds)..."); + + await _packageManager.TrimApplication(file, false, null, cancellationToken); + + return true; + } + + private async Task DeployApplication(IMeadowConnection connection, string path, CancellationToken cancellationToken) + { + connection.FileWriteProgress += OnFileWriteProgress; + + var candidates = Cli.PackageManager.GetAvailableBuiltConfigurations(path, "App.dll"); + + if (candidates.Length == 0) + { + Logger.LogError($"Cannot find a compiled application at '{path}'"); + return false; + } + + var file = candidates.OrderByDescending(c => c.LastWriteTime).First(); + + Logger.LogInformation($"Deploying app from {file.DirectoryName}..."); + + await AppManager.DeployApplication(_packageManager, connection, file.DirectoryName, true, false, Logger, cancellationToken); + + connection.FileWriteProgress -= OnFileWriteProgress; + + return true; + } + + private void OnFileWriteProgress(object? sender, (string fileName, long completed, long total) e) + { + var p = (e.completed / (double)e.total) * 100d; + + if (e.fileName != _lastFile) + { + Console.Write("\n"); + _lastFile = e.fileName; + } + + // Console instead of Logger due to line breaking for progress bar + Console.Write($"Writing {e.fileName}: {p:0}% \r"); + } + + private void OnDeviceMessageReceived(object? sender, (string message, string? source) e) + { + if (NoPrefix) + { + Logger.LogInformation($"{e.message.TrimEnd('\n', '\r')}"); + } + else + { + Logger.LogInformation($"{e.source}> {e.message.TrimEnd('\n', '\r')}"); + } + } +} diff --git a/Source/v2/Meadow.Cli/Commands/Current/App/AppTrimCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/App/AppTrimCommand.cs index 60b4d164..6fe51fec 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/App/AppTrimCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/App/AppTrimCommand.cs @@ -39,7 +39,7 @@ protected override async ValueTask ExecuteCommand(CancellationToken cancellation return; } - // it's a directory - we need to determine the latest build (they might have a Debug and Release config) + // it's a directory - we need to determine the latest build (they might have a Debug and a Release config) var candidates = PackageManager.GetAvailableBuiltConfigurations(path, "App.dll"); if (candidates.Length == 0) diff --git a/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs new file mode 100644 index 00000000..9c2ea68f --- /dev/null +++ b/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs @@ -0,0 +1,41 @@ +using CliFx.Attributes; +using Meadow.Hcom; +using Microsoft.Extensions.Logging; + +namespace Meadow.CLI.Commands.DeviceManagement; + +[Command("file delete", Description = "Deletes a file from the device")] +public class FileDeleteCommand : BaseDeviceCommand +{ + [CommandParameter(0, Name = "MeadowFile", IsRequired = true)] + public string MeadowFile { get; set; } = default!; + + public FileDeleteCommand(MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory) + : base(connectionManager, loggerFactory) + { + } + + protected override async ValueTask ExecuteCommand(IMeadowConnection connection, Hcom.IMeadowDevice device, CancellationToken cancellationToken) + { + var fileList = await connection.GetFileList(false); + var exists = fileList?.Any(f => Path.GetFileName(f.Name) == MeadowFile) ?? false; + + if (!exists) + { + Logger.LogError($"File '{MeadowFile}' not found on device."); + } + else + { + var wasRuntimeEnabled = await device.IsRuntimeEnabled(cancellationToken); + + if (wasRuntimeEnabled) + { + Logger.LogError($"The runtime must be disabled before doing any file management. Use 'meadow runtime disable' first."); + return; + } + + Logger.LogInformation($"Deleting file '{MeadowFile}' from device..."); + await device.DeleteFile(MeadowFile, cancellationToken); + } + } +} diff --git a/Source/v2/Meadow.Cli/IPackageManager.cs b/Source/v2/Meadow.Cli/IPackageManager.cs index 55909a18..6012cd4c 100644 --- a/Source/v2/Meadow.Cli/IPackageManager.cs +++ b/Source/v2/Meadow.Cli/IPackageManager.cs @@ -2,6 +2,7 @@ public interface IPackageManager { + List GetDependencies(FileInfo file); bool BuildApplication(string projectFilePath, string configuration = "Release"); Task TrimApplication( FileInfo applicationFilePath, diff --git a/Source/v2/Meadow.Cli/PackageManager.AssemblyManager.cs b/Source/v2/Meadow.Cli/PackageManager.AssemblyManager.cs index 392ce786..a2f66282 100644 --- a/Source/v2/Meadow.Cli/PackageManager.AssemblyManager.cs +++ b/Source/v2/Meadow.Cli/PackageManager.AssemblyManager.cs @@ -8,6 +8,10 @@ namespace Meadow.Cli; public partial class PackageManager { + private const string IL_LINKER_DIR = "lib"; + public const string PostLinkDirectoryName = "postlink_bin"; + public const string PreLinkDirectoryName = "prelink_bin"; + private readonly List dependencyMap = new(); private string? _meadowAssembliesPath; @@ -29,11 +33,9 @@ private string? MeadowAssembliesPath } } - private const string IL_LINKER_DIR = "lib"; - public async Task?> TrimDependencies(FileInfo file, List dependencies, IList? noLink, ILogger? logger, bool includePdbs, bool verbose = false, string? linkerOptions = null) { - var prelink_dir = Path.Combine(file.DirectoryName, "prelink_bin"); + var prelink_dir = Path.Combine(file.DirectoryName, PreLinkDirectoryName); var prelink_app = Path.Combine(prelink_dir, file.Name); var prelink_os = Path.Combine(prelink_dir, "Meadow.dll"); @@ -63,7 +65,7 @@ private string? MeadowAssembliesPath } } - var postlink_dir = Path.Combine(file.DirectoryName, "postlink_bin"); + var postlink_dir = Path.Combine(file.DirectoryName, PostLinkDirectoryName); if (Directory.Exists(postlink_dir)) { Directory.Delete(postlink_dir, recursive: true); @@ -148,7 +150,7 @@ private string? MeadowAssembliesPath return Directory.EnumerateFiles(postlink_dir); } - private List GetDependencies(FileInfo file) + public List GetDependencies(FileInfo file) { dependencyMap.Clear(); diff --git a/Source/v2/Meadow.Cli/PackageManager.cs b/Source/v2/Meadow.Cli/PackageManager.cs index 480b959c..7f9fbd58 100644 --- a/Source/v2/Meadow.Cli/PackageManager.cs +++ b/Source/v2/Meadow.Cli/PackageManager.cs @@ -108,10 +108,6 @@ await TrimDependencies( verbose: false); } - public async Task DeployApplication() - { - } - public static FileInfo[] GetAvailableBuiltConfigurations(string rootFolder, string appName = "App.dll") { if (!Directory.Exists(rootFolder)) throw new FileNotFoundException(); @@ -127,6 +123,13 @@ void FindApp(string directory, List fileList) { foreach (var dir in Directory.GetDirectories(directory)) { + var shortname = System.IO.Path.GetFileName(dir); + + if (shortname == PackageManager.PostLinkDirectoryName || shortname == PackageManager.PreLinkDirectoryName) + { + continue; + } + var file = Directory.GetFiles(dir).FirstOrDefault(f => string.Compare(Path.GetFileName(f), appName, true) == 0); if (file != null) { @@ -134,6 +137,7 @@ void FindApp(string directory, List fileList) } FindApp(dir, fileList); + } } diff --git a/Source/v2/Meadow.Cli/Properties/launchSettings.json b/Source/v2/Meadow.Cli/Properties/launchSettings.json index 25caa7bc..ee03220d 100644 --- a/Source/v2/Meadow.Cli/Properties/launchSettings.json +++ b/Source/v2/Meadow.Cli/Properties/launchSettings.json @@ -41,7 +41,7 @@ }, "Config: Set Route Serial": { "commandName": "Project", - "commandLineArgs": "config route COM4" + "commandLineArgs": "config route COM7" }, "Config: Set Route TCP": { "commandName": "Project", @@ -75,6 +75,10 @@ "commandName": "Project", "commandLineArgs": "file list --verbose" }, + "File Delete": { + "commandName": "Project", + "commandLineArgs": "file delete meadow.log" + }, "File Read": { "commandName": "Project", "commandLineArgs": "file read test.txt \"f:\\temp\\test2.txt\"" @@ -109,7 +113,7 @@ }, "Firmware Write all": { "commandName": "Project", - "commandLineArgs": "firmware write runtime esp" + "commandLineArgs": "firmware write" }, "Firmware Write version": { "commandName": "Project", @@ -170,6 +174,18 @@ "Dfu Install 0.10": { "commandName": "Project", "commandLineArgs": "dfu install -v 0.10" + }, + "App Deploy (project folder)": { + "commandName": "Project", + "commandLineArgs": "app deploy F:\\temp\\MeadowApplication1" + }, + "App Deploy (untrimmed output)": { + "commandName": "Project", + "commandLineArgs": "app deploy F:\\temp\\MeadowApplication1\\bin\\Debug\\netstandard2.1" + }, + "App run": { + "commandName": "Project", + "commandLineArgs": "app run F:\\temp\\MeadowApplication1" } } } \ No newline at end of file diff --git a/Source/v2/Meadow.Hcom/Connections/ConnectionBase.cs b/Source/v2/Meadow.Hcom/Connections/ConnectionBase.cs index ec7984e8..e2ee803e 100644 --- a/Source/v2/Meadow.Hcom/Connections/ConnectionBase.cs +++ b/Source/v2/Meadow.Hcom/Connections/ConnectionBase.cs @@ -20,6 +20,7 @@ public abstract class ConnectionBase : IMeadowConnection, IDisposable public abstract Task GetFileList(bool includeCrcs, CancellationToken? cancellationToken = null); public abstract Task WriteFile(string localFileName, string? meadowFileName = null, CancellationToken? cancellationToken = null); public abstract Task ReadFile(string meadowFileName, string? localFileName = null, CancellationToken? cancellationToken = null); + public abstract Task DeleteFile(string meadowFileName, CancellationToken? cancellationToken = null); public abstract Task ResetDevice(CancellationToken? cancellationToken = null); public abstract Task IsRuntimeEnabled(CancellationToken? cancellationToken = null); public abstract Task RuntimeDisable(CancellationToken? cancellationToken = null); diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.ListenerProc.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.ListenerProc.cs index b8162443..c253b398 100644 --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.ListenerProc.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.ListenerProc.cs @@ -18,9 +18,30 @@ public override async Task WaitForMeadowAttach(CancellationToken? cancellationTo if (cancellationToken?.IsCancellationRequested ?? false) throw new TaskCanceledException(); if (timeout <= 0) throw new TimeoutException(); - if (State == ConnectionState.MeadowAttached) return; + if (State == ConnectionState.MeadowAttached) + { + if (Device == null) + { + // no device set - this happens when we are waiting for attach from DFU mode + await Attach(cancellationToken, 5); + } + + return; + } await Task.Delay(500); + + if (!_port.IsOpen) + { + try + { + Open(); + } + catch (Exception ex) + { + Debug.WriteLine($"Unable to open port: {ex.Message}"); + } + } } throw new TimeoutException(); @@ -32,6 +53,7 @@ private async Task ListenerProc() var decodedBuffer = new byte[8192]; var messageBytes = new CircularBuffer(8192 * 2); var delimiter = new byte[] { 0x00 }; + var receivedLength = 0; while (!_isDisposed) { @@ -41,7 +63,39 @@ private async Task ListenerProc() { Debug.WriteLine($"listening..."); - var receivedLength = _port.BaseStream.Read(readBuffer, 0, readBuffer.Length); + read: + try + { + receivedLength = _port.BaseStream.Read(readBuffer, 0, readBuffer.Length); + } + catch (OperationCanceledException) + { + Debug.WriteLine($"Device reset detected"); + + var timeout = 20; + + while (!_port.IsOpen) + { + await Task.Delay(500); + + if (timeout-- < 0) + { + return; + } + + try + { + Open(); + Debug.WriteLine($"Port re-opened"); + } + catch + { + Debug.WriteLine($"Failed to re-open port"); + } + } + + goto read; + } Debug.WriteLine($"Received {receivedLength} bytes"); @@ -257,6 +311,11 @@ private async Task ListenerProc() // common if the port is reset/closed (e.g. mono enable/disable) - don't spew confusing info Debug.WriteLine($"listen on closed port"); } + catch (OperationCanceledException) + { + // this happens on disconnect - could be cable pulled, could be device reset + Debug.WriteLine($"Operation Cancelled"); + } catch (Exception ex) { Debug.WriteLine($"listen error {ex.Message}"); diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs index 61eff517..f94a982b 100644 --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs @@ -223,7 +223,7 @@ private void Close() } } - private void CommandManager() + private async void CommandManager() { while (!_isDisposed) { @@ -236,7 +236,7 @@ private void CommandManager() // if this is a file write, we need to packetize for progress var payload = command.Serialize(); - EncodeAndSendPacket(payload); + await EncodeAndSendPacket(payload); // TODO: re-queue on fail? } @@ -461,7 +461,7 @@ private bool DecodeAndProcessPacket(Memory packetBuffer, CancellationToken return true; } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (!_isDisposed) { @@ -475,12 +475,6 @@ protected virtual void Dispose(bool disposing) } } - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - // ---------------------------------------------- // ---------------------------------------------- // ---------------------------------------------- @@ -640,14 +634,11 @@ public override async Task RuntimeEnable(CancellationToken? cancellationToken = InfoMessages.Clear(); - EnqueueRequest(command); - - // we have to give time for the device to actually reset - await Task.Delay(500); + _lastRequestConcluded = null; - var success = await WaitForResponseText(RuntimeSucessfullyEnabledToken); + EnqueueRequest(command); - if (!success) throw new Exception("Unable to enable runtime"); + await WaitForConcluded(null, cancellationToken); } public override async Task RuntimeDisable(CancellationToken? cancellationToken = null) @@ -656,14 +647,11 @@ public override async Task RuntimeDisable(CancellationToken? cancellationToken = InfoMessages.Clear(); - EnqueueRequest(command); - - // we have to give time for the device to actually reset - await Task.Delay(500); + _lastRequestConcluded = null; - var success = await WaitForResponseText(RuntimeSucessfullyDisabledToken); + EnqueueRequest(command); - if (!success) throw new Exception("Unable to disable runtime"); + await WaitForConcluded(null, cancellationToken); } public override async Task TraceEnable(CancellationToken? cancellationToken = null) @@ -1069,4 +1057,16 @@ void OnFileError(object? sender, Exception exception) FileException -= OnFileError; } } + + public override async Task DeleteFile(string meadowFileName, CancellationToken? cancellationToken = null) + { + var command = RequestBuilder.Build(); + command.MeadowFileName = meadowFileName; + + _lastRequestConcluded = null; + + EnqueueRequest(command); + + await WaitForConcluded(null, cancellationToken); + } } \ No newline at end of file diff --git a/Source/v2/Meadow.Hcom/Connections/TcpConnection.cs b/Source/v2/Meadow.Hcom/Connections/TcpConnection.cs index 49eeffd3..e4aecba4 100644 --- a/Source/v2/Meadow.Hcom/Connections/TcpConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/TcpConnection.cs @@ -112,6 +112,11 @@ public override Task ReadFile(string meadowFileName, string? localFileName throw new NotImplementedException(); } + public override Task DeleteFile(string meadowFileName, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + public override Task WriteRuntime(string localFileName, CancellationToken? cancellationToken = null) { throw new NotImplementedException(); diff --git a/Source/v2/Meadow.Hcom/IMeadowConnection.cs b/Source/v2/Meadow.Hcom/IMeadowConnection.cs index 959cce8c..c0ba7adf 100644 --- a/Source/v2/Meadow.Hcom/IMeadowConnection.cs +++ b/Source/v2/Meadow.Hcom/IMeadowConnection.cs @@ -15,6 +15,7 @@ public interface IMeadowConnection Task WriteFile(string localFileName, string? meadowFileName = null, CancellationToken? cancellationToken = null); Task ReadFile(string meadowFileName, string? localFileName = null, CancellationToken? cancellationToken = null); + Task DeleteFile(string meadowFileName, CancellationToken? cancellationToken = null); Task GetDeviceInfo(CancellationToken? cancellationToken = null); Task GetFileList(bool includeCrcs, CancellationToken? cancellationToken = null); Task ResetDevice(CancellationToken? cancellationToken = null); diff --git a/Source/v2/Meadow.Hcom/IMeadowDevice.cs b/Source/v2/Meadow.Hcom/IMeadowDevice.cs index fce2fec5..30069d4a 100644 --- a/Source/v2/Meadow.Hcom/IMeadowDevice.cs +++ b/Source/v2/Meadow.Hcom/IMeadowDevice.cs @@ -10,6 +10,7 @@ public interface IMeadowDevice Task GetFileList(bool includeCrcs, CancellationToken? cancellationToken = null); Task ReadFile(string meadowFileName, string? localFileName = null, CancellationToken? cancellationToken = null); Task WriteFile(string localFileName, string? meadowFileName = null, CancellationToken? cancellationToken = null); + Task DeleteFile(string meadowFileName, CancellationToken? cancellationToken = null); Task WriteRuntime(string localFileName, CancellationToken? cancellationToken = null); Task GetRtcTime(CancellationToken? cancellationToken = null); Task SetRtcTime(DateTimeOffset dateTime, CancellationToken? cancellationToken = null); diff --git a/Source/v2/Meadow.Hcom/MeadowDevice.cs b/Source/v2/Meadow.Hcom/MeadowDevice.cs index 9e9bacaf..775a126a 100644 --- a/Source/v2/Meadow.Hcom/MeadowDevice.cs +++ b/Source/v2/Meadow.Hcom/MeadowDevice.cs @@ -129,5 +129,10 @@ public async Task SetDeveloperParameter(ushort parameter, uint value, Cancellati { await _connection.SetDeveloperParameter(parameter, value, cancellationToken); } + + public async Task DeleteFile(string meadowFileName, CancellationToken? cancellationToken = null) + { + await _connection.DeleteFile(meadowFileName, cancellationToken); + } } } \ No newline at end of file diff --git a/Source/v2/Meadow.Hcom/Serial Requests/FileDeleteRequest.cs b/Source/v2/Meadow.Hcom/Serial Requests/FileDeleteRequest.cs new file mode 100644 index 00000000..a5eca198 --- /dev/null +++ b/Source/v2/Meadow.Hcom/Serial Requests/FileDeleteRequest.cs @@ -0,0 +1,23 @@ +using System.Text; + +namespace Meadow.Hcom; + +internal class FileDeleteRequest : Request +{ + public override RequestType RequestType => RequestType.HCOM_MDOW_REQUEST_DELETE_FILE_BY_NAME; + + public string MeadowFileName + { + get + { + if (Payload == null) return string.Empty; + return Encoding.ASCII.GetString(Payload, 44, Payload.Length - 44); + } + set + { + var nameBytes = Encoding.ASCII.GetBytes(value); + Payload = new byte[4 + 4 + 4 + 32 + nameBytes.Length]; + Array.Copy(nameBytes, 0, Payload, 44, nameBytes.Length); // file name + } + } +} diff --git a/Source/v2/Meadow.Hcom/Serial Requests/InitFileReadRequest.cs b/Source/v2/Meadow.Hcom/Serial Requests/InitFileReadRequest.cs index 44f1c9d1..cdd9001d 100644 --- a/Source/v2/Meadow.Hcom/Serial Requests/InitFileReadRequest.cs +++ b/Source/v2/Meadow.Hcom/Serial Requests/InitFileReadRequest.cs @@ -19,4 +19,4 @@ public string MeadowFileName Payload = Encoding.ASCII.GetBytes(value); } } -} +} \ No newline at end of file