diff --git a/Source/v2/Meadow.CLI/Commands/Current/Firmware/FirmwareUpdater.cs b/Source/v2/Meadow.CLI/Commands/Current/Firmware/FirmwareUpdater.cs index ef1d41717..83af0413f 100644 --- a/Source/v2/Meadow.CLI/Commands/Current/Firmware/FirmwareUpdater.cs +++ b/Source/v2/Meadow.CLI/Commands/Current/Firmware/FirmwareUpdater.cs @@ -16,15 +16,16 @@ public class FirmwareUpdater where T : BaseDeviceCommand private string? serialNumber; private readonly MeadowConnectionManager connectionManager; private readonly ILogger? logger; + private readonly bool provisioninInProgress; private readonly CancellationToken cancellationToken; private readonly ISettingsManager settings; private readonly FileManager fileManager; - private int _lastWriteProgress = 0; + private int lastWriteProgress = 0; private BaseDeviceCommand command; - public event EventHandler UpdateProgress = default!; + public event EventHandler<(string message, double percentage)> UpdateProgress = default!; public FirmwareUpdater(BaseDeviceCommand command, ISettingsManager settings, FileManager fileManager, MeadowConnectionManager connectionManager, string? individualFile, FirmwareType[]? firmwareFileTypes, bool useDfu, string? osVersion, string? serialNumber, ILogger? logger, CancellationToken cancellationToken) { @@ -38,6 +39,7 @@ public FirmwareUpdater(BaseDeviceCommand command, ISettingsManager settings, this.osVersion = osVersion; this.serialNumber = serialNumber; this.logger = logger; + this.provisioninInProgress = logger == null; this.cancellationToken = cancellationToken; } @@ -94,19 +96,30 @@ public async Task UpdateFirmware() if (firmwareFileTypes.Contains(FirmwareType.OS)) { - UpdateProgress?.Invoke(this, "Flashing OS"); + UpdateProgress?.Invoke(this, ("Flashing OS", 20)); await WriteOSFiles(connection, deviceInfo, package, useDfu); } if (!string.IsNullOrWhiteSpace(serialNumber)) { - connection = await GetConnectionAndDisableRuntime(await MeadowConnectionManager.GetRouteFromSerialNumber(serialNumber!)); - deviceInfo = await connection.GetDeviceInfo(cancellationToken); + connection = await GetConnectionAndDisableRuntime(await MeadowConnectionManager.GetRouteFromSerialNumber(serialNumber)); + if (connection != null) + { + if (provisioninInProgress) + { + connection.ConnectionMessage += (o, e) => + { + UpdateProgress?.Invoke(this, (e, 0)); + }; + } + + deviceInfo = await connection.GetDeviceInfo(cancellationToken); + } } if (firmwareFileTypes.Contains(FirmwareType.Runtime) || Path.GetFileName(individualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.RuntimeFile) { - UpdateProgress?.Invoke(this, "Writing Runtime"); + UpdateProgress?.Invoke(this, ("Writing Runtime", 40)); await WriteRuntimeFiles(connection, deviceInfo, package, individualFile); } @@ -115,7 +128,7 @@ public async Task UpdateFirmware() || Path.GetFileName(individualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.CoprocApplicationFile || Path.GetFileName(individualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.CoprocBootloaderFile) { - UpdateProgress?.Invoke(this, "Writing ESP"); + UpdateProgress?.Invoke(this, ("Writing ESP", 60)); await WriteEspFiles(connection, deviceInfo, package); } @@ -130,7 +143,7 @@ public async Task UpdateFirmware() private async Task WriteEspFiles(IMeadowConnection? connection, DeviceInfo? deviceInfo, FirmwarePackage package) { - connection ??= await GetConnectionAndDisableRuntime(); + connection ??= await GetConnectionAndDisableRuntime(await MeadowConnectionManager.GetRouteFromSerialNumber(serialNumber)); await WriteEsp(connection, deviceInfo, package); @@ -202,7 +215,7 @@ private async Task WriteOSFiles(IMeadowConnection? connection, DeviceInfo? devic connection ??= await FindMeadowConnection(initialPorts); - await connection.WaitForMeadowAttach(); + await connection.WaitForMeadowAttach(cancellationToken); } else { @@ -254,7 +267,7 @@ private async Task WriteOSFiles(IMeadowConnection? connection, DeviceInfo? devic if (!string.IsNullOrWhiteSpace(serialNumber)) { - return meadowsInDFU.Where(device => device.GetDeviceSerialNumber() == serialNumber).FirstOrDefault(); + return meadowsInDFU.Where(device => device.SerialNumber == serialNumber).FirstOrDefault(); } else if (meadowsInDFU.Count == 1 || IgnoreSerialNumberForDfu(provider)) { //IgnoreSerialNumberForDfu is a macOS-specific hack for Mark's machine @@ -272,7 +285,7 @@ private bool IgnoreSerialNumberForDfu(LibUsbProvider provider) if (devices.Count == 2) { - if (devices[0].GetDeviceSerialNumber().Length > 12 || devices[1].GetDeviceSerialNumber().Length > 12) + if (devices[0].SerialNumber.Length > 12 || devices[1].SerialNumber.Length > 12) { return true; } @@ -286,7 +299,7 @@ private async Task GetConnectionAndDisableRuntime(string? rou { IMeadowConnection connection; - if (route != null) + if (!string.IsNullOrWhiteSpace(route)) { connection = await command.GetConnectionForRoute(route, true); } @@ -301,15 +314,15 @@ private async Task GetConnectionAndDisableRuntime(string? rou await connection.Device.RuntimeDisable(); } - _lastWriteProgress = 0; + lastWriteProgress = 0; connection.FileWriteProgress += (s, e) => { var p = (int)(e.completed / (double)e.total * 100d); // don't report < 10% increments (decrease spew on large files) - if (p - _lastWriteProgress < 10) { return; } + if (p - lastWriteProgress < 10) { return; } - _lastWriteProgress = p; + lastWriteProgress = p; logger?.LogInformation($"{Strings.Writing} {e.fileName}: {p:0}% {(p < 100 ? string.Empty : "\r\n")}"); }; @@ -349,7 +362,7 @@ private async Task WriteOsWithDfu(ILibUsbDevice libUsbDevice, string osFile, boo { //validate device if (string.IsNullOrWhiteSpace(serialNumber)) { - serialNumber = libUsbDevice.GetDeviceSerialNumber(); + serialNumber = libUsbDevice.SerialNumber; } } catch @@ -368,7 +381,7 @@ await DfuUtils.FlashFile( osFile, serialNumber, logger: logger, - format: string.IsNullOrEmpty(serialNumber) ? DfuUtils.DfuFlashFormat.ConsoleOut : DfuUtils.DfuFlashFormat.None); + format: provisioninInProgress ? DfuUtils.DfuFlashFormat.None : DfuUtils.DfuFlashFormat.ConsoleOut); } catch (ArgumentException) { @@ -474,7 +487,7 @@ private async Task> WaitForNewSerialPorts(IList? ignorePor private async Task WriteRuntime(IMeadowConnection? connection, DeviceInfo? deviceInfo, string runtimePath, string destinationFilename) { - connection ??= await GetConnectionAndDisableRuntime(); + connection ??= await GetConnectionAndDisableRuntime(await MeadowConnectionManager.GetRouteFromSerialNumber(serialNumber)); logger?.LogInformation($"{Environment.NewLine}{Strings.WritingRuntime}..."); @@ -520,7 +533,7 @@ private async Task> WaitForNewSerialPorts(IList? ignorePor private async Task WriteEsp(IMeadowConnection? connection, DeviceInfo? deviceInfo, FirmwarePackage package) { - connection ??= await GetConnectionAndDisableRuntime(); + connection ??= await GetConnectionAndDisableRuntime(await MeadowConnectionManager.GetRouteFromSerialNumber(serialNumber)); if (connection == null) { return; } // couldn't find a connected device diff --git a/Source/v2/Meadow.CLI/Commands/Current/Provision/ProvisionCommand.cs b/Source/v2/Meadow.CLI/Commands/Current/Provision/ProvisionCommand.cs index 41d9332c9..12d6a1e37 100644 --- a/Source/v2/Meadow.CLI/Commands/Current/Provision/ProvisionCommand.cs +++ b/Source/v2/Meadow.CLI/Commands/Current/Provision/ProvisionCommand.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Threading.Tasks; using CliFx.Attributes; using Meadow.CLI.Commands.DeviceManagement; using Meadow.Cloud.Client; @@ -16,13 +17,13 @@ public class ProvisionCommand : BaseDeviceCommand { public const string DefaultOSVersion = "1.12.0.0"; private string? appPath; - private string? configuration; + private string? configuration = "Release"; [CommandOption("version", 'v', Description = Strings.Provision.CommandOptionVersion, IsRequired = false)] public string? OsVersion { get; set; } = DefaultOSVersion; [CommandOption("path", 'p', Description = Strings.Provision.CommandOptionPath, IsRequired = false)] - public string? Path { get; set; } + public string? Path { get; set; } = "."; private ConcurrentQueue bootloaderDeviceQueue = new ConcurrentQueue(); @@ -47,109 +48,138 @@ public ProvisionCommand(ISettingsManager settingsManager, FileManager fileManage protected override async ValueTask ExecuteCommand() { - AnsiConsole.MarkupLine(Strings.Provision.RunningTitle); - - string path = AppTools.ValidateAndSanitizeAppPath(Path); - - if (!string.IsNullOrWhiteSpace(path) - && !File.Exists(path)) - { - deployApp = false; - AnsiConsole.MarkupLine($"[red]{Strings.Provision.FileNotFound}[/]", $"[yellow]{path}[/]"); - AnsiConsole.MarkupLine(Strings.Provision.NoAppDeployment, $"[yellow]{OsVersion}[/]"); - } - else + try { - Path = path; - } + AnsiConsole.MarkupLine(Strings.Provision.RunningTitle); - if (deployApp) - { - var provisionSettings = JsonConvert.DeserializeObject(File.ReadAllText(Path!)); - if (provisionSettings == null) + bool refreshDeviceList = false; + do { - throw new Exception("Failed to read provision.json file."); - } + UpdateDeviceList(CancellationToken); - // Use the settings from provisionSettings as needed - appPath = AppTools.ValidateAndSanitizeAppPath(provisionSettings.AppPath); - configuration = provisionSettings.Configuration; - OsVersion = provisionSettings.OsVersion; + if (bootloaderDeviceQueue.Count == 0) + { + Logger?.LogError(Strings.Provision.NoDevicesFound); + return; + } - if (!File.Exists(appPath)) - { - throw new FileNotFoundException($"No App found at location:{appPath}"); - } + var multiSelectionPrompt = new MultiSelectionPrompt() + .Title(Strings.Provision.PromptTitle) + .PageSize(15) + .NotRequired() // Can be Blank to exit + .MoreChoicesText($"[grey]{Strings.Provision.MoreChoicesInstructions}[/]") + .InstructionsText(string.Format($"[grey]{Strings.Provision.Instructions}[/]", $"[blue]<{Strings.Space}>[/]", $"[green]<{Strings.Enter}>[/]")) + .UseConverter(x => x); - AnsiConsole.MarkupLine(Strings.Provision.TrimmingApp); - await AppTools.TrimApplication(appPath!, packageManager, OsVersion!, configuration, null, null, Console, CancellationToken); - } + foreach (var device in bootloaderDeviceQueue) + { + multiSelectionPrompt.AddChoices(device.SerialNumber); + } - bool refreshDeviceList = false; - do - { - UpdateDeviceList(CancellationToken); + selectedDeviceList = AnsiConsole.Prompt(multiSelectionPrompt); - if (bootloaderDeviceQueue.Count == 0) - { - Logger?.LogError(Strings.Provision.NoDevicesFound); - return; - } + if (selectedDeviceList.Count == 0) + { + AnsiConsole.MarkupLine($"[yellow]{Strings.Provision.NoDeviceSelected}[/]"); + return; + } - var multiSelectionPrompt = new MultiSelectionPrompt() - .Title(Strings.Provision.PromptTitle) - .PageSize(15) - .NotRequired() // Can be Blank to exit - .MoreChoicesText($"[grey]{Strings.Provision.MoreChoicesInstructions}[/]") - .InstructionsText(string.Format($"[grey]{Strings.Provision.Instructions}[/]", $"[blue]<{Strings.Space}>[/]", $"[green]<{Strings.Enter}>[/]")) - .UseConverter(x => x); + var selectedDeviceTable = new Table(); + selectedDeviceTable.AddColumn(Strings.Provision.ColumnTitle); - foreach (var device in bootloaderDeviceQueue) - { - multiSelectionPrompt.AddChoices(device.GetDeviceSerialNumber()); - } + foreach (var device in selectedDeviceList) + { + selectedDeviceTable.AddRow(device); + } - selectedDeviceList = AnsiConsole.Prompt(multiSelectionPrompt); + AnsiConsole.Write(selectedDeviceTable); - if (selectedDeviceList.Count == 0) + refreshDeviceList = AnsiConsole.Confirm(Strings.Provision.RefreshDeviceList); + } while (!refreshDeviceList); + + string path = System.IO.Path.Combine(Path, "provision.json"); + + if (!string.IsNullOrWhiteSpace(path) + && !File.Exists(path)) { - AnsiConsole.MarkupLine($"[yellow]{Strings.Provision.NoDeviceSelected}[/]"); - return; + deployApp = false; + AnsiConsole.MarkupLine($"[red]{Strings.Provision.FileNotFound}[/]", $"[yellow]{path}[/]"); + AnsiConsole.MarkupLine(Strings.Provision.NoAppDeployment, $"[yellow]{OsVersion}[/]"); + } + else + { + Path = path; } - var selectedDeviceTable = new Table(); - selectedDeviceTable.AddColumn(Strings.Provision.ColumnTitle); - - foreach (var device in selectedDeviceList) + if (deployApp) { - selectedDeviceTable.AddRow(device); + var provisionSettings = JsonConvert.DeserializeObject(File.ReadAllText(Path!)); + if (provisionSettings == null) + { + throw new Exception("Failed to read provision.json file."); + } + + // Use the settings from provisionSettings as needed + try + { + appPath = AppTools.ValidateAndSanitizeAppPath(provisionSettings.AppPath); + + if (!File.Exists(appPath)) + { + throw new FileNotFoundException($"App.dll Not found at location:{appPath}"); + } + + configuration = provisionSettings.Configuration; + OsVersion = provisionSettings.OsVersion; + + AnsiConsole.MarkupLine(Strings.Provision.TrimmingApp); + await AppTools.TrimApplication(appPath!, packageManager, OsVersion!, configuration, null, null, Console, CancellationToken); + } + catch (Exception ex) + { + // Eat the exception and keep going. + deployApp = false; + // TODO put this back in for release #if DEBUG + var message = ex.Message; + var stackTrace = ex.StackTrace; + message += Environment.NewLine + stackTrace; + AnsiConsole.MarkupLine($"[red]{ex.Message}[/]"); + //#endif + AnsiConsole.MarkupLine(Strings.Provision.NoAppDeployment, $"[yellow]{OsVersion}[/]"); + } } - AnsiConsole.Write(selectedDeviceTable); + if (string.IsNullOrEmpty(OsVersion)) + { + OsVersion = DefaultOSVersion; + } - refreshDeviceList = AnsiConsole.Confirm(Strings.Provision.RefreshDeviceList); - } while (!refreshDeviceList); + // Install DFU, if it's not already installed. + var dfuInstallCommand = new DfuInstallCommand(settingsManager, LoggerFactory); + await dfuInstallCommand.ExecuteAsync(Console); - if (string.IsNullOrEmpty(OsVersion)) - { - OsVersion = DefaultOSVersion; - } + // Make sure we've downloaded the osVersion or default + var firmwareDownloadCommand = new FirmwareDownloadCommand(fileManager, meadowCloudClient, LoggerFactory) + { + Version = OsVersion, + Force = true + }; + await firmwareDownloadCommand.ExecuteAsync(Console); - // Install DFU, if it's not already installed. - var dfuInstallCommand = new DfuInstallCommand(settingsManager, LoggerFactory); - await dfuInstallCommand.ExecuteAsync(Console); - // Make sure we've downloaded the osVersion or default - var firmwareDownloadCommand = new FirmwareDownloadCommand(fileManager, meadowCloudClient, LoggerFactory) + // If we've reached here we're ready to Flash + await FlashingAttachedDevices(); + } + catch (Exception ex) { - Version = OsVersion, - Force = true - }; - await firmwareDownloadCommand.ExecuteAsync(Console); - - // If we've reached here we're ready to Flash - await FlashingAttachedDevices(); + var message = ex.Message; +#if DEBUG + var stackTrace = ex.StackTrace; + message += Environment.NewLine + stackTrace; +#endif + AnsiConsole.MarkupLine($"[red]{message}[/]"); + } } private void UpdateDeviceList(CancellationToken cancellationToken) @@ -185,13 +215,15 @@ private void UpdateDeviceList(CancellationToken cancellationToken) } catch (Exception) { - //footerMessage = ex.Message; return null; } } public async Task FlashingAttachedDevices() { + var succeedCount = 0; + var errorList = new List<(string SerialNumber, string Message, string StackTrace)>(); + await AnsiConsole.Progress() .AutoRefresh(true) .HideCompleted(false) @@ -209,43 +241,85 @@ await AnsiConsole.Progress() var formatedDevice = $"[green]{deviceSerialNumber}[/]"; var task = ctx.AddTask(formatedDevice, maxValue: 100); - var firmareUpdater = new FirmwareUpdater(this, settingsManager, fileManager, this.connectionManager, null, null, true, OsVersion, deviceSerialNumber, null, CancellationToken); - firmareUpdater.UpdateProgress += (o, e) => + try { - task.Increment(20.00); - task.Description = $"{formatedDevice}: {e}"; - }; + var firmareUpdater = new FirmwareUpdater(this, settingsManager, fileManager, this.connectionManager, null, null, true, OsVersion, deviceSerialNumber, null, CancellationToken); + firmareUpdater.UpdateProgress += (o, e) => + { + if (e.percentage > 0) + { + task.Value = e.percentage; + } + task.Description = $"{formatedDevice}: {e.message}"; + }; + + if (!await firmareUpdater.UpdateFirmware()) + { + task.Description = $"{formatedDevice}: [red]{Strings.Provision.UpdateFailed}[/]"; + task.StopTask(); + } + + if (deployApp) + { + task.Increment(20.00); + task.Description = $"{Strings.Provision.DeployingApp}"; + + var route = await MeadowConnectionManager.GetRouteFromSerialNumber(deviceSerialNumber!); + if (!string.IsNullOrWhiteSpace(route)) + { + var connection = await GetConnectionForRoute(route, true); + var appDir = System.IO.Path.GetDirectoryName(appPath); + await AppManager.DeployApplication(packageManager, connection, OsVersion!, appDir!, true, false, null, CancellationToken); + + await connection?.Device?.RuntimeEnable(CancellationToken); + } + } + + task.Value = 100.00; + task.Description = string.Format($"{formatedDevice}: [green]{Strings.Provision.UpdateComplete}[/]"); - if (!await firmareUpdater.UpdateFirmware()) - { - task.Description = $"{formatedDevice}: [red]{Strings.Provision.UpdateFailed}[/]"; task.StopTask(); - } - if (deployApp) + await Task.Delay(2000); // TODO May not be required, futher testing needed + + succeedCount++; + } + catch (Exception ex) { - task.Increment(20.00); - task.Description = $"{Strings.Provision.DeployingApp}"; + task.Description = $"{formatedDevice}: [red]{ex.Message}[/]"; + task.StopTask(); - var route = await MeadowConnectionManager.GetRouteFromSerialNumber(deviceSerialNumber!); - if (!string.IsNullOrWhiteSpace(route)) + if (!string.IsNullOrWhiteSpace(ex.StackTrace)) { - var connection = await GetConnectionForRoute(route, true); - var appDir = System.IO.Path.GetDirectoryName(appPath); - await AppManager.DeployApplication(packageManager, connection, OsVersion!, appDir!, true, false, null, CancellationToken); + errorList.Add((deviceSerialNumber, ex.Message, ex.StackTrace)); } } + } + }); - task.Value = 100.00; - task.Description = string.Format($"{formatedDevice}: [green]{Strings.Provision.UpdateComplete}[/]"); - - task.StopTask(); + if (succeedCount == selectedDeviceList.Count) + { + AnsiConsole.MarkupLine($"[green]{Strings.Provision.AllDevicesFlashed}[/]"); + } + else + { + AnsiConsole.MarkupLine($"[yellow]{Strings.Provision.IssuesFound}[/]"); + var showErrorMessages = AnsiConsole.Confirm(Strings.Provision.ShowErrorMessages); + if (showErrorMessages) + { + var errorTable = new Table(); + errorTable.AddColumn(Strings.Provision.ErrorSerialNumberColumnTitle); + errorTable.AddColumn(Strings.Provision.ErrorMessageColumnTitle); + errorTable.AddColumn(Strings.Provision.ErrorStackTraceColumnTitle); - await Task.Delay(2000); + foreach (var error in errorList) + { + errorTable.AddRow(error.SerialNumber, error.Message, error.StackTrace); } - }); - AnsiConsole.MarkupLine($"[green]{Strings.Provision.AllDevicesFlashed}[/]"); + AnsiConsole.Write(errorTable); + } + } } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs index ca54771ef..ce4da2ab9 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs @@ -1,10 +1,6 @@ using CliFx.Attributes; -using Meadow.CLI.Core.Internals.Dfu; -using Meadow.Hcom; -using Meadow.LibUsb; using Meadow.Software; using Microsoft.Extensions.Logging; -using System.Runtime.InteropServices; namespace Meadow.CLI.Commands.DeviceManagement; @@ -43,228 +39,8 @@ public FirmwareWriteCommand(ISettingsManager settingsManager, FileManager fileMa Settings = settingsManager; } - private int _lastWriteProgress = 0; - - private async Task GetConnectionAndDisableRuntime(string? route = null) - { - IMeadowConnection connection; - - if (route != null) - { - connection = await GetConnectionForRoute(route, true); - } - else - { - connection = await GetCurrentConnection(true); - } - - if (await connection.Device!.IsRuntimeEnabled()) - { - Logger?.LogInformation($"{Strings.DisablingRuntime}..."); - await connection.Device.RuntimeDisable(); - } - - _lastWriteProgress = 0; - - connection.FileWriteProgress += (s, e) => - { - var p = (int)(e.completed / (double)e.total * 100d); - // don't report < 10% increments (decrease spew on large files) - if (p - _lastWriteProgress < 10) { return; } - - _lastWriteProgress = p; - - Logger?.LogInformation($"{Strings.Writing} {e.fileName}: {p:0}% {(p < 100 ? string.Empty : "\r\n")}"); - }; - connection.DeviceMessageReceived += (s, e) => - { - if (e.message.Contains("% downloaded")) - { // don't echo this, as we're already reporting % written - } - else - { - Logger?.LogInformation(e.message); - } - }; - connection.ConnectionMessage += (s, message) => - { - Logger?.LogInformation(message); - }; - - return connection; - } - - private bool RequiresDfuForRuntimeUpdates(DeviceInfo info) - { - return true; - /* - restore this when we support OtA-style updates again - if (System.Version.TryParse(info.OsVersion, out var version)) - { - return version.Major >= 2; - } - */ - } - - private bool RequiresDfuForEspUpdates(DeviceInfo info) - { - return true; - } - protected override async ValueTask ExecuteCommand() { - /*var package = await GetSelectedPackage(); - - if (package == null) - { - return; - } - - if (IndividualFile != null) - { - // check the file exists - var fullPath = Path.GetFullPath(IndividualFile); - if (!File.Exists(fullPath)) - { - throw new CommandException(string.Format(Strings.InvalidFirmwareForSpecifiedPath, fullPath), CommandExitCode.FileNotFound); - } - - // set the file type - FirmwareFileTypes = Path.GetFileName(IndividualFile) switch - { - F7FirmwarePackageCollection.F7FirmwareFiles.OSWithBootloaderFile => new[] { FirmwareType.OS }, - F7FirmwarePackageCollection.F7FirmwareFiles.OsWithoutBootloaderFile => new[] { FirmwareType.OS }, - F7FirmwarePackageCollection.F7FirmwareFiles.RuntimeFile => new[] { FirmwareType.Runtime }, - F7FirmwarePackageCollection.F7FirmwareFiles.CoprocApplicationFile => new[] { FirmwareType.ESP }, - F7FirmwarePackageCollection.F7FirmwareFiles.CoprocBootloaderFile => new[] { FirmwareType.ESP }, - F7FirmwarePackageCollection.F7FirmwareFiles.CoprocPartitionTableFile => new[] { FirmwareType.ESP }, - _ => throw new CommandException(string.Format(Strings.UnknownSpecifiedFirmwareFile, Path.GetFileName(IndividualFile))) - }; - - Logger?.LogInformation(string.Format($"{Strings.WritingSpecifiedFirmwareFile}...", fullPath)); - } - else if (FirmwareFileTypes == null) - { - Logger?.LogInformation(string.Format(Strings.WritingAllFirmwareForSpecifiedVersion, package.Version)); - - FirmwareFileTypes = new FirmwareType[] - { - FirmwareType.OS, - FirmwareType.Runtime, - FirmwareType.ESP - }; - } - else if (FirmwareFileTypes.Length == 1 && FirmwareFileTypes[0] == FirmwareType.Runtime) - { //use the "DFU" path when only writing the runtime - UseDfu = true; - } - - IMeadowConnection? connection = null; - DeviceInfo? deviceInfo = null; - - if (FirmwareFileTypes.Contains(FirmwareType.OS)) - { - string? osFileWithBootloader = null; - string? osFileWithoutBootloader = null; - - if (string.IsNullOrWhiteSpace(IndividualFile)) - { - osFileWithBootloader = package.GetFullyQualifiedPath(package.OSWithBootloader); - osFileWithoutBootloader = package.GetFullyQualifiedPath(package.OsWithoutBootloader); - - if (osFileWithBootloader == null && osFileWithoutBootloader == null) - { - throw new CommandException(string.Format(Strings.OsFileNotFoundForSpecifiedVersion, package.Version)); - } - } - else - { - osFileWithBootloader = IndividualFile; - } - - // do we have a dfu device attached, or is DFU specified? - var provider = new LibUsbProvider(); - var dfuDevice = GetLibUsbDeviceForCurrentEnvironment(provider); - bool ignoreSerial = IgnoreSerialNumberForDfu(provider); - - if (dfuDevice != null) - { - Logger?.LogInformation($"{Strings.DfuDeviceDetected} - {Strings.UsingDfuToWriteOs}"); - UseDfu = true; - } - else - { - if (UseDfu) - { - throw new CommandException(Strings.NoDfuDeviceDetected); - } - - connection = await GetConnectionAndDisableRuntime(); - - deviceInfo = await connection.GetDeviceInfo(CancellationToken); - } - - if (UseDfu || dfuDevice != null || osFileWithoutBootloader == null || RequiresDfuForRuntimeUpdates(deviceInfo!)) - { - // get a list of ports - it will not have our meadow in it (since it should be in DFU mode) - var initialPorts = await MeadowConnectionManager.GetSerialPorts(); - - try - { - await WriteOsWithDfu(dfuDevice!, osFileWithBootloader!, ignoreSerial); - } - finally - { - dfuDevice?.Dispose(); - } - - await Task.Delay(1500); - - connection = await FindMeadowConnection(initialPorts); - - await connection.WaitForMeadowAttach(); - } - else - { - await connection!.Device!.WriteFile(osFileWithoutBootloader, $"/{AppTools.MeadowRootFolder}/update/os/{package.OsWithoutBootloader}"); - } - } - - if (FirmwareFileTypes.Contains(FirmwareType.Runtime) || Path.GetFileName(IndividualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.RuntimeFile) - { - if (string.IsNullOrEmpty(IndividualFile)) - { - connection = await WriteRuntime(connection, deviceInfo, package); - } - else - { - connection = await WriteRuntime(connection, deviceInfo, IndividualFile, Path.GetFileName(IndividualFile)); - } - - if (connection == null) - { - throw CommandException.MeadowDeviceNotFound; - } - - await connection.WaitForMeadowAttach(); - } - - if (FirmwareFileTypes.Contains(FirmwareType.ESP) - || Path.GetFileName(IndividualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.CoprocPartitionTableFile - || Path.GetFileName(IndividualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.CoprocApplicationFile - || Path.GetFileName(IndividualFile) == F7FirmwarePackageCollection.F7FirmwareFiles.CoprocBootloaderFile) - { - connection = await GetConnectionAndDisableRuntime(); - - await WriteEspFiles(connection, deviceInfo, package); - - // reset device - if (connection != null && connection.Device != null) - { - await connection.Device.Reset(); - } - }*/ - var firmareUpdater = new FirmwareUpdater(this, Settings, FileManager, ConnectionManager, IndividualFile, FirmwareFileTypes, UseDfu, Version, SerialNumber, Logger, CancellationToken); if (await firmareUpdater.UpdateFirmware()) @@ -272,294 +48,4 @@ protected override async ValueTask ExecuteCommand() Logger?.LogInformation(Strings.FirmwareUpdatedSuccessfully); } } - - private async Task FindMeadowConnection(IList portsToIgnore) - { - IMeadowConnection? connection = null; - - var newPorts = await WaitForNewSerialPorts(portsToIgnore); - string newPort = string.Empty; - - if (newPorts == null) - { - throw CommandException.MeadowDeviceNotFound; - } - - if (newPorts.Count == 1) - { - connection = await GetConnectionAndDisableRuntime(newPorts[0]); - newPort = newPorts[0]; - } - else - { - foreach (var port in newPorts) - { - try - { - connection = await GetConnectionAndDisableRuntime(port); - newPort = port; - break; - } - catch - { - throw CommandException.MeadowDeviceNotFound; - } - } - } - - Logger?.LogInformation($"{Strings.MeadowFoundAt} {newPort}"); - - await connection!.WaitForMeadowAttach(); - - // configure the route to that port for the user - Settings.SaveSetting(SettingsManager.PublicSettings.Route, newPort); - - return connection; - } - - private async Task WriteRuntime(IMeadowConnection? connection, DeviceInfo? deviceInfo, FirmwarePackage package) - { - Logger?.LogInformation($"{Environment.NewLine}{Strings.GettingRuntimeFor} {package.Version}..."); - - if (package.Runtime == null) { return null; } - - // get the path to the runtime file - var rtpath = package.GetFullyQualifiedPath(package.Runtime); - - return await WriteRuntime(connection, deviceInfo, rtpath, package.Runtime); - } - - private async Task WriteRuntime(IMeadowConnection? connection, DeviceInfo? deviceInfo, string runtimePath, string destinationFilename) - { - connection ??= await GetConnectionAndDisableRuntime(); - - Logger?.LogInformation($"{Environment.NewLine}{Strings.WritingRuntime}..."); - - deviceInfo ??= await connection.GetDeviceInfo(CancellationToken); - - if (deviceInfo == null) - { - throw new CommandException(Strings.UnableToGetDeviceInfo); - } - - if (UseDfu || RequiresDfuForRuntimeUpdates(deviceInfo)) - { - var initialPorts = await MeadowConnectionManager.GetSerialPorts(); - - write_runtime: - if (!await connection!.Device!.WriteRuntime(runtimePath, CancellationToken)) - { - // TODO: implement a retry timeout - Logger?.LogInformation($"{Strings.ErrorWritingRuntime} - {Strings.Retrying}"); - goto write_runtime; - } - - connection = await GetCurrentConnection(true); - - if (connection == null) - { - var newPort = await WaitForNewSerialPort(initialPorts) ?? throw CommandException.MeadowDeviceNotFound; - connection = await GetCurrentConnection(true); - - Logger?.LogInformation($"{Strings.MeadowFoundAt} {newPort}"); - - // configure the route to that port for the user - Settings.SaveSetting(SettingsManager.PublicSettings.Route, newPort); - } - } - else - { - await connection.Device!.WriteFile(runtimePath, $"/{AppTools.MeadowRootFolder}/update/os/{destinationFilename}"); - } - - return connection; - } - - private async Task WriteEspFiles(IMeadowConnection? connection, DeviceInfo? deviceInfo, FirmwarePackage package) - { - connection ??= await GetConnectionAndDisableRuntime(); - - if (connection == null) { return; } // couldn't find a connected device - - Logger?.LogInformation($"{Environment.NewLine}{Strings.WritingCoprocessorFiles}..."); - - string[] fileList; - - if (IndividualFile != null) - { - fileList = new string[] { IndividualFile }; - } - else - { - fileList = new string[] - { - package.GetFullyQualifiedPath(package.CoprocApplication), - package.GetFullyQualifiedPath(package.CoprocBootloader), - package.GetFullyQualifiedPath(package.CoprocPartitionTable), - }; - } - - deviceInfo ??= await connection.GetDeviceInfo(CancellationToken); - - if (deviceInfo == null) { throw new CommandException(Strings.UnableToGetDeviceInfo); } - - if (UseDfu || RequiresDfuForEspUpdates(deviceInfo)) - { - await connection.Device!.WriteCoprocessorFiles(fileList, CancellationToken); - } - else - { - foreach (var file in fileList) - { - await connection!.Device!.WriteFile(file, $"/{AppTools.MeadowRootFolder}/update/os/{Path.GetFileName(file)}"); - } - } - } - - private ILibUsbDevice? GetLibUsbDeviceForCurrentEnvironment(LibUsbProvider? provider) - { - provider ??= new LibUsbProvider(); - - var devices = provider.GetDevicesInBootloaderMode(); - - var meadowsInDFU = devices.Where(device => device.IsMeadow()).ToList(); - - if (meadowsInDFU.Count == 0) - { - return null; - } - - if (meadowsInDFU.Count == 1 || IgnoreSerialNumberForDfu(provider)) - { //IgnoreSerialNumberForDfu is a macOS-specific hack for Mark's machine - return meadowsInDFU.FirstOrDefault(); - } - - throw new CommandException(Strings.MultipleDfuDevicesFound); - } - - private async Task GetSelectedPackage() - { - await FileManager.Refresh(); - - var collection = FileManager.Firmware["Meadow F7"]; - FirmwarePackage package; - - if (Version != null) - { - // make sure the requested version exists - var existing = collection.FirstOrDefault(v => v.Version == Version); - - if (existing == null) - { - Logger?.LogError(string.Format(Strings.SpecifiedFirmwareVersionNotFound, Version)); - return null; - } - package = existing; - } - else - { - Version = collection.DefaultPackage?.Version ?? throw new CommandException($"{Strings.NoDefaultVersionSet}. {Strings.UseCommandFirmwareDefault}."); - - package = collection.DefaultPackage; - } - - return package; - } - - private async Task WriteOsWithDfu(ILibUsbDevice libUsbDevice, string osFile, bool ignoreSerialNumber = false) - { - string serialNumber; - - try - { //validate device - if (!string.IsNullOrWhiteSpace(SerialNumber)) - { - serialNumber = SerialNumber; - } - else - { - serialNumber = libUsbDevice.GetDeviceSerialNumber(); - } - } - catch - { - throw new CommandException($"{Strings.FirmwareWriteFailed} - {Strings.UnableToReadSerialNumber} ({Strings.MakeSureDeviceisConnected})"); - } - - try - { - if (ignoreSerialNumber) - { - serialNumber = string.Empty; - } - - await DfuUtils.FlashFile( - osFile, - serialNumber, - logger: Logger, - format: DfuUtils.DfuFlashFormat.ConsoleOut); - } - catch (ArgumentException) - { - throw new CommandException($"{Strings.FirmwareWriteFailed} - {Strings.IsDfuUtilInstalled} {Strings.RunMeadowDfuInstall}"); - } - catch (Exception ex) - { - Logger?.LogError($"Exception type: {ex.GetType().Name}"); - - // TODO: scope this to the right exception type for Win 10 access violation thing - // TODO: catch the Win10 DFU error here and change the global provider configuration to "classic" - Settings.SaveSetting(SettingsManager.PublicSettings.LibUsb, "classic"); - - throw new CommandException("This machine requires an older version of LibUsb. The CLI settings have been updated, re-run the 'firmware write' command to update your device."); - } - } - - private async Task> WaitForNewSerialPorts(IList? ignorePorts) - { - var ports = await MeadowConnectionManager.GetSerialPorts(); - - var retryCount = 0; - - while (ports.Count == 0) - { - if (retryCount++ > 10) - { - throw new CommandException(Strings.NewMeadowDeviceNotFound); - } - await Task.Delay(500); - ports = await MeadowConnectionManager.GetSerialPorts(); - } - - if (ignorePorts != null) - { - return ports.Except(ignorePorts).ToList(); - } - return ports.ToList(); - } - - private async Task WaitForNewSerialPort(IList? ignorePorts) - { - var ports = await WaitForNewSerialPorts(ignorePorts); - - return ports.FirstOrDefault(); - } - - private bool IgnoreSerialNumberForDfu(LibUsbProvider provider) - { //hack check for Mark's Mac - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - var devices = provider.GetDevicesInBootloaderMode(); - - if (devices.Count == 2) - { - if (devices[0].GetDeviceSerialNumber().Length > 12 || devices[1].GetDeviceSerialNumber().Length > 12) - { - return true; - } - } - } - - return false; - } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Properties/launchSettings.json b/Source/v2/Meadow.Cli/Properties/launchSettings.json index 4c48e2663..8f63f372b 100644 --- a/Source/v2/Meadow.Cli/Properties/launchSettings.json +++ b/Source/v2/Meadow.Cli/Properties/launchSettings.json @@ -279,6 +279,10 @@ "commandName": "Project", "commandLineArgs": "cloud package publish d867bb8b6e56418ba26ebe4e2b3ef6db -c 9d5f9780e6964c22b4ec15c44b886545" }, + "Provision Multiple Devices": { + "commandName": "Project", + "commandLineArgs": "provision" + }, "legacy list ports": { "commandName": "Project", "commandLineArgs": "list ports" diff --git a/Source/v2/Meadow.Cli/Strings.cs b/Source/v2/Meadow.Cli/Strings.cs index a2daea1d7..628b026dd 100644 --- a/Source/v2/Meadow.Cli/Strings.cs +++ b/Source/v2/Meadow.Cli/Strings.cs @@ -109,5 +109,10 @@ public static class Provision public const string NoAppDeployment = "Skipping App Deployment and using default version: {0}"; public const string DeployingApp = "Deploying App"; public const string TrimmingApp = "Trimming App, before we get started"; + public const string ShowErrorMessages = "Show all error messages (y=Show Messages, n=Exit Immediately)?"; + public const string IssuesFound = "There were issues during the last provision."; + public const string ErrorSerialNumberColumnTitle = "Serial Number"; + public const string ErrorMessageColumnTitle = "Message"; + public const string ErrorStackTraceColumnTitle = "Stack Trace"; } } \ No newline at end of file diff --git a/Source/v2/Meadow.Dfu/DfuUtils.cs b/Source/v2/Meadow.Dfu/DfuUtils.cs index b83e0bec1..57d704b1e 100644 --- a/Source/v2/Meadow.Dfu/DfuUtils.cs +++ b/Source/v2/Meadow.Dfu/DfuUtils.cs @@ -1,17 +1,14 @@ -using System; +using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net.Http; using System.Runtime.InteropServices; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Meadow.Hcom; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using static System.Net.Mime.MediaTypeNames; namespace Meadow.CLI.Core.Internals.Dfu; diff --git a/Source/v2/Meadow.Firmware/FirmwareWriter.cs b/Source/v2/Meadow.Firmware/FirmwareWriter.cs index 2541c9e94..07ca5c5d7 100644 --- a/Source/v2/Meadow.Firmware/FirmwareWriter.cs +++ b/Source/v2/Meadow.Firmware/FirmwareWriter.cs @@ -47,7 +47,7 @@ public Task WriteOsWithDfu(string osFile, ILogger? logger = null, bool useLegacy default: throw new Exception("Multiple devices found in bootloader mode - only connect one device"); } - var serialNumber = devices.First().GetDeviceSerialNumber(); + var serialNumber = devices.First().SerialNumber; Debug.WriteLine($"DFU Writing file {osFile}"); diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs old mode 100755 new mode 100644 index 20a3345ff..ebc3e96df --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs @@ -189,46 +189,51 @@ public override void Detach() public override async Task Attach(CancellationToken? cancellationToken = null, int timeoutSeconds = 10) { + CancellationTokenSource? timeoutCts = null; try { // ensure the port is open Open(); + timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds * 2)); + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken ?? CancellationToken.None); + var ct = linkedCts.Token; + // search for the device via HCOM - we'll use a simple command since we don't have a "ping" var command = RequestBuilder.Build(); // sequence numbers are only for file retrieval - Setting it to non-zero will cause it to hang - _port.DiscardInBuffer(); - // wait for a response - var timeout = timeoutSeconds * 2; - var dataReceived = false; - // local function so we can unsubscribe var count = _messageCount; - _pendingCommands.Enqueue(command); - _commandEvent.Set(); + EnqueueRequest(command); + + // Wait for a response + var taskCompletionSource = new TaskCompletionSource(); - while (timeout-- > 0) + _ = Task.Run(async () => { - if (cancellationToken?.IsCancellationRequested ?? false) return null; - if (timeout <= 0) throw new TimeoutException(); - - if (count != _messageCount) + while (!ct.IsCancellationRequested) { - dataReceived = true; - break; + if (count != _messageCount) + { + taskCompletionSource.TrySetResult(true); + break; + } + await Task.Delay(500, ct); } - - await Task.Delay(500); - } + if (!taskCompletionSource.Task.IsCompleted) + { + taskCompletionSource.TrySetException(new TimeoutException("Timeout waiting for device response")); + } + }, ct); // if HCOM fails, check for DFU/bootloader mode? only if we're doing an OS thing, so maybe no - // create the device instance - if (dataReceived) + // Create the device instance if the response is received + if (await taskCompletionSource.Task) { Device = new MeadowDevice(this); } @@ -240,6 +245,10 @@ public override void Detach() _logger?.LogError(e, "Failed to connect"); throw; } + finally + { + timeoutCts?.Dispose(); + } } private void CommandManager() diff --git a/Source/v2/Meadow.Hcom/Meadow.HCom.csproj b/Source/v2/Meadow.Hcom/Meadow.HCom.csproj index 6c0525fa8..78f63c2be 100644 --- a/Source/v2/Meadow.Hcom/Meadow.HCom.csproj +++ b/Source/v2/Meadow.Hcom/Meadow.HCom.csproj @@ -16,7 +16,7 @@ - + diff --git a/Source/v2/Meadow.Tooling.Core/Connection/MeadowConnectionManager.cs b/Source/v2/Meadow.Tooling.Core/Connection/MeadowConnectionManager.cs index d236f8f40..159161547 100644 --- a/Source/v2/Meadow.Tooling.Core/Connection/MeadowConnectionManager.cs +++ b/Source/v2/Meadow.Tooling.Core/Connection/MeadowConnectionManager.cs @@ -210,7 +210,7 @@ public static async Task> GetMeadowSerialPortsForLinux(string? ser return await Task.Run(() => { - const string devicePath = "/dev/serial/by-id"; + const string devicePath = "/dev/serial/by-id/"; var psi = new ProcessStartInfo() { FileName = "ls", @@ -230,9 +230,7 @@ public static async Task> GetMeadowSerialPortsForLinux(string? ser return Array.Empty(); } - if (string.IsNullOrWhiteSpace(serialNumber)) - { - return output + var wlSerialPorts = output .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Where(x => x.Contains("Wilderness_Labs")) .Select( @@ -242,20 +240,15 @@ public static async Task> GetMeadowSerialPortsForLinux(string? ser var target = parts[1]; var port = Path.GetFullPath(Path.Combine(devicePath, target)); return port; - }).ToArray(); + }); + + if (string.IsNullOrWhiteSpace(serialNumber)) + { + return wlSerialPorts.ToArray(); } else { - return output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .Where(x => x.Contains("Wilderness_Labs")) - .Select( - line => - { - var parts = line.Split(new[] { "-> " }, StringSplitOptions.RemoveEmptyEntries); - var target = parts[1]; - var port = Path.GetFullPath(Path.Combine(devicePath, target)); - return port; - }) + return wlSerialPorts .Where(line => !string.IsNullOrWhiteSpace(serialNumber) && line.Contains(serialNumber)) .ToArray(); } @@ -335,7 +328,7 @@ public static IList GetMeadowSerialPortsForWindows(string? serialNumber) } } - public static async Task GetRouteFromSerialNumber(string serialNumber) + public static async Task GetRouteFromSerialNumber(string? serialNumber) { var results = await GetSerialPorts(serialNumber); return results.FirstOrDefault(); diff --git a/Source/v2/Meadow.UsbLib.Core/ILibUsbDevice.cs b/Source/v2/Meadow.UsbLib.Core/ILibUsbDevice.cs index 61fb302f3..9942fb836 100644 --- a/Source/v2/Meadow.UsbLib.Core/ILibUsbDevice.cs +++ b/Source/v2/Meadow.UsbLib.Core/ILibUsbDevice.cs @@ -10,7 +10,7 @@ public interface ILibUsbProvider public interface ILibUsbDevice : IDisposable { - string GetDeviceSerialNumber(); + string SerialNumber { get; } bool IsMeadow(); } \ No newline at end of file diff --git a/Source/v2/Meadow.UsbLib/LibUsbDevice.cs b/Source/v2/Meadow.UsbLib/LibUsbDevice.cs index 36086157c..116825622 100644 --- a/Source/v2/Meadow.UsbLib/LibUsbDevice.cs +++ b/Source/v2/Meadow.UsbLib/LibUsbDevice.cs @@ -7,9 +7,11 @@ namespace Meadow.LibUsb; public class LibUsbProvider : ILibUsbProvider { private const int UsbBootLoaderVendorID = 1155; + private const int UsbMeadowVendorID = 11882; internal static UsbContext _context; internal static List? _devices; + static LibUsbProvider() { // only ever create one of these - there's a bug in the LibUsbDotNet library and when this disposes, things go sideways @@ -30,41 +32,30 @@ public List GetDevicesInBootloaderMode() public class LibUsbDevice : ILibUsbDevice { private readonly IUsbDevice _device; + private string? serialNumber; + + public string? SerialNumber => serialNumber; public LibUsbDevice(IUsbDevice usbDevice) { _device = usbDevice; - } - - public ushort productId; - - public void Dispose() - { - _device?.Dispose(); - } - - public string GetDeviceSerialNumber() - { - var serialNumber = string.Empty; _device.Open(); if (_device.IsOpen) { serialNumber = _device.Info?.SerialNumber ?? string.Empty; - productId = _device.Info?.ProductId ?? 0; _device.Close(); } + } - return serialNumber; + public void Dispose() + { + _device?.Dispose(); } public bool IsMeadow() { - if (_device.VendorId != 1155) - { - return false; - } - if (GetDeviceSerialNumber().Length > 12) + if (serialNumber?.Length > 12) { return false; } diff --git a/Source/v2/Meadow.UsbLibClassic/ClassicLibUsbDevice.cs b/Source/v2/Meadow.UsbLibClassic/ClassicLibUsbDevice.cs index 5161e084e..b9a309319 100644 --- a/Source/v2/Meadow.UsbLibClassic/ClassicLibUsbDevice.cs +++ b/Source/v2/Meadow.UsbLibClassic/ClassicLibUsbDevice.cs @@ -10,70 +10,71 @@ public class ClassicLibUsbProvider : ILibUsbProvider { private const string UsbStmName = "STM32 BOOTLOADER"; private const int UsbBootLoaderVendorID = 1155; + private const int UsbMeadowVendorID = 11882; - public List GetDevicesInBootloaderMode() - { - var propName = (Environment.OSVersion.Platform == PlatformID.Win32NT) + private string propName = (Environment.OSVersion.Platform == PlatformID.Win32NT) ? "FriendlyName" : "DeviceDesc"; + public List GetDevicesInBootloaderMode() + { return UsbDevice .AllDevices .Where(d => d.DeviceProperties[propName].ToString() == UsbStmName) .Select(d => new ClassicLibUsbDevice(d)) - .Cast() - .ToList(); + .ToList(); } -} - -public class ClassicLibUsbDevice : ILibUsbDevice -{ - private readonly UsbRegistry _device; - public ClassicLibUsbDevice(UsbRegistry usbDevice) + public class ClassicLibUsbDevice : ILibUsbDevice { - _device = usbDevice; - } + private readonly UsbRegistry _device; + private string? serialNumber; - public void Dispose() - { - _device.Device.Close(); - } + public string? SerialNumber => serialNumber; - public string GetDeviceSerialNumber() - { - if (_device != null && _device.DeviceProperties != null) + public ClassicLibUsbDevice(UsbRegistry usbDevice) { - switch (Environment.OSVersion.Platform) + _device = usbDevice; + + _device.Device.Open(); + if (_device.Device.IsOpen) { - case PlatformID.Win32NT: - var deviceID = _device.DeviceProperties["DeviceID"].ToString(); - if (!string.IsNullOrWhiteSpace(deviceID)) - { - return deviceID.Substring(deviceID.LastIndexOf("\\") + 1); - } - else + if (_device.DeviceProperties != null) + { + switch (Environment.OSVersion.Platform) { - return string.Empty; + case PlatformID.Win32NT: + var deviceID = _device.DeviceProperties["DeviceID"].ToString(); + if (!string.IsNullOrWhiteSpace(deviceID)) + { + serialNumber = deviceID.Substring(deviceID.LastIndexOf("\\") + 1); + } + else + { + serialNumber = string.Empty; + } + break; + default: + serialNumber = _device.DeviceProperties["SerialNumber"].ToString() ?? string.Empty; + break; } - default: - return _device.DeviceProperties["SerialNumber"].ToString() ?? string.Empty; + _device.Device.Close(); + } } } - return string.Empty; - } - - public bool IsMeadow() - { - if (_device.Vid != 1155) + public void Dispose() { - return false; + _device.Device.Close(); } - if (GetDeviceSerialNumber().Length > 12) + + public bool IsMeadow() { - return false; + if (SerialNumber?.Length > 12) + { + return false; + } + return true; } - return true; } } \ No newline at end of file