diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a5a9d5fe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,213 @@ +# EditorConfig for Visual Studio 2022: https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022 + +# This is a top-most .editorconfig file +root = true + +#===================================================== +# +# nanoFramework specific settings +# +# +#===================================================== +[*] +# Generic EditorConfig settings +end_of_line = crlf +charset = utf-8-bom + +# Visual Studio spell checker +spelling_languages = en-us +spelling_checkable_types = strings,identifiers,comments +spelling_error_severity = information +spelling_exclusion_path = spelling_exclusion.dic + +#===================================================== +# +# Settings copied from the .NET runtime +# +# https://github.com/dotnet/runtime +# +#===================================================== +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] +generated_code = true + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_collection_expression = when_types_exactly_match +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# License header +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf \ No newline at end of file diff --git a/README.md b/README.md index 4f1c518f..619eed8f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Build Status](https://dev.azure.com/nanoframework/nanoFirmwareFlasher/_apis/build/status/nanoFirmwareFlasher?repoName=nanoframework%2FnanoFirmwareFlasher&branchName=main)](https://dev.azure.com/nanoframework/nanoFirmwareFlasher/_build/latest?definitionId=45&repoName=nanoframework%2FnanoFirmwareFlasher&branchName=main) [![NuGet](https://img.shields.io/nuget/v/nanoff.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoff/) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) +[![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Build Status](https://dev.azure.com/nanoframework/nanoFirmwareFlasher/_apis/build/status/nanoFirmwareFlasher?repoName=nanoframework%2FnanoFirmwareFlasher&branchName=main)](https://dev.azure.com/nanoframework/nanoFirmwareFlasher/_build/latest?definitionId=45&repoName=nanoframework%2FnanoFirmwareFlasher&branchName=main) [![NuGet](https://img.shields.io/nuget/v/nanoff.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoff/) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) ![nanoFramework logo](https://raw.githubusercontent.com/nanoframework/Home/main/resources/logo/nanoFramework-repo-logo.png) @@ -504,6 +504,30 @@ When this option is included in the command no other options are processed. nanoff --clearcache ``` +## Firmware archive + +By default, *nanoff* uses the online repository to look for firmware packages. It is also possible to use a local directory as the source of firmware. The firmware archive can be populated via the *--updatefwarchive* option: + +```console +nanoff --updatefwarchive --target ESP32_S3_ALL --fwarchivepath c:\...\firmware +nanoff --updatefwarchive --platform esp32 --fwarchivepath c:\...\firmware +``` + +For a list of archived firmware: +```console +nanoff --listtargets --fromfwarchive --fwarchivepath c:\...\firmware +``` + +To install firmware on a device, use the same command line arguments as usual, but add *--fromfwarchive* and *--fwarchivepath*: + +```console +nanoff --nanodevice --update --serialport COM9 --fromfwarchive --fwarchivepath c:\...\firmware +``` + +## Bypass version check + +By default nanoff checks whether a new version of the tool has been published. If that is not necessary, the option *--suppressnanoffversioncheck* can be added to suppress the check. + ## Exit codes The exit codes can be checked in [this source file](https://github.com/nanoframework/nanoFirmwareFlasher/blob/main/nanoFirmwareFlasher.Library/ExitCodes.cs). diff --git a/nanoFirmwareFlasher.Library/CC13x26x2Device.cs b/nanoFirmwareFlasher.Library/CC13x26x2Device.cs index 3e9cb276..815c317e 100644 --- a/nanoFirmwareFlasher.Library/CC13x26x2Device.cs +++ b/nanoFirmwareFlasher.Library/CC13x26x2Device.cs @@ -57,7 +57,7 @@ public ExitCodes FlashHexFiles(IList files) //{ // if (Verbosity >= VerbosityLevel.Normal) // { - // Console.Write("Mass erase device..."); + // OutputWriter.Write("Mass erase device..."); // } // cliOutput = RunUniflashCli($"-c SN={DeviceId} UR -ME"); @@ -69,11 +69,11 @@ public ExitCodes FlashHexFiles(IList files) // if (Verbosity >= VerbosityLevel.Normal) // { - // Console.WriteLine(" OK"); + // OutputWriter.WriteLine(" OK"); // } // else // { - // Console.WriteLine(""); + // OutputWriter.WriteLine(""); // } // // toggle mass erase so it's only performed before the first file is flashed @@ -82,11 +82,11 @@ public ExitCodes FlashHexFiles(IList files) if (Verbosity == VerbosityLevel.Normal) { - Console.Write("Flashing device..."); + OutputWriter.Write("Flashing device..."); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine("Flashing device..."); + OutputWriter.WriteLine("Flashing device..."); } // program HEX file(s) @@ -94,10 +94,10 @@ public ExitCodes FlashHexFiles(IList files) { if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine($"{Path.GetFileName(hexFile)}"); + OutputWriter.WriteLine($"{Path.GetFileName(hexFile)}"); } - var cliOutput = RunUniflashCli($" flash -c {ConfigurationFile} -f -v {hexFile}"); + string cliOutput = RunUniflashCli($" flash -c {ConfigurationFile} -f -v {hexFile}"); if (!cliOutput.Contains("Program verification successful")) { @@ -107,11 +107,11 @@ public ExitCodes FlashHexFiles(IList files) if (Verbosity == VerbosityLevel.Normal) { - Console.WriteLine(" OK"); + OutputWriter.WriteLine(" OK"); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine("Flashing completed..."); + OutputWriter.WriteLine("Flashing completed..."); } return ExitCodes.OK; @@ -166,24 +166,24 @@ public ExitCodes FlashBinFiles(IList files, IList addresses) //{ // if (Verbosity >= VerbosityLevel.Normal) // { - // Console.Write("Mass erase device..."); + // OutputWriter.Write("Mass erase device..."); // } // cliOutput = RunUniflashCli($"-b"); // if (!cliOutput.Contains("Flash memory erased.")) // { - // Console.WriteLine(""); + // OutputWriter.WriteLine(""); // return ExitCodes.E5005; // } // if (Verbosity >= VerbosityLevel.Normal) // { - // Console.WriteLine(" OK"); + // OutputWriter.WriteLine(" OK"); // } // else // { - // Console.WriteLine(""); + // OutputWriter.WriteLine(""); // } // // toggle mass erase so it's only performed before the first file is flashed @@ -192,11 +192,11 @@ public ExitCodes FlashBinFiles(IList files, IList addresses) if (Verbosity == VerbosityLevel.Normal) { - Console.Write("Flashing device..."); + OutputWriter.Write("Flashing device..."); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine("Flashing device..."); + OutputWriter.WriteLine("Flashing device..."); } // program BIN file(s) @@ -205,10 +205,10 @@ public ExitCodes FlashBinFiles(IList files, IList addresses) { if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine($"{Path.GetFileName(binFile)} @ {addresses.ElementAt(index)}"); + OutputWriter.WriteLine($"{Path.GetFileName(binFile)} @ {addresses.ElementAt(index)}"); } - var cliOutput = RunUniflashCli($" flash -c {ConfigurationFile} -f -v {binFile},{addresses.ElementAt(index++)}"); + string cliOutput = RunUniflashCli($" flash -c {ConfigurationFile} -f -v {binFile},{addresses.ElementAt(index++)}"); if (!cliOutput.Contains("Program verification successful")) { @@ -218,11 +218,11 @@ public ExitCodes FlashBinFiles(IList files, IList addresses) if (Verbosity == VerbosityLevel.Normal) { - Console.WriteLine(" OK"); + OutputWriter.WriteLine(" OK"); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine("Flashing completed..."); + OutputWriter.WriteLine("Flashing completed..."); } return ExitCodes.OK; @@ -234,21 +234,21 @@ public ExitCodes FlashBinFiles(IList files, IList addresses) public ExitCodes ResetMcu() { // try to connect to device with RESET - var cliOutput = RunUniflashCli($" flash -c {ConfigurationFile} -r 0"); + string cliOutput = RunUniflashCli($" flash -c {ConfigurationFile} -r 0"); if (!cliOutput.Contains("CPU Reset is issued")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); return ExitCodes.E5010; } if (Verbosity >= VerbosityLevel.Normal) { - Console.WriteLine(" OK"); + OutputWriter.WriteLine(" OK"); } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } return ExitCodes.OK; diff --git a/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs b/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs index 6ebb4039..b301967c 100644 --- a/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs +++ b/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. namespace nanoFramework.Tools.FirmwareFlasher { @@ -21,10 +19,10 @@ public CC13x26x2Firmware( { } - internal new System.Threading.Tasks.Task DownloadAndExtractAsync() + internal new System.Threading.Tasks.Task DownloadAndExtractAsync(string archiveDirectoryPath) { // perform download and extract - return base.DownloadAndExtractAsync(); + return base.DownloadAndExtractAsync(archiveDirectoryPath); } } } diff --git a/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs b/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs index 25166d09..6f730693 100644 --- a/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs +++ b/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; @@ -21,6 +19,8 @@ public class CC13x26x2Operations /// Name of the target to update. /// Firmware version to update to. /// Set to to use preview version to update. + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and never be downloaded. /// Set to to force download of firmware package. /// Path to application to update along with the firmware update. /// Flash address to use when deploying an aplication. @@ -30,6 +30,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( string targetName, string fwVersion, bool preview, + string archiveDirectoryPath, bool updateFw, string applicationPath, string deploymentAddress, @@ -54,7 +55,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // need to download update package? if (updateFw) { - var operationResult = await firmware.DownloadAndExtractAsync(); + ExitCodes operationResult = await firmware.DownloadAndExtractAsync(archiveDirectoryPath); if (operationResult != ExitCodes.OK) { return operationResult; diff --git a/nanoFirmwareFlasher.Library/Esp32DeviceInfo.cs b/nanoFirmwareFlasher.Library/Esp32DeviceInfo.cs index 9dc578d2..97a2d3e7 100644 --- a/nanoFirmwareFlasher.Library/Esp32DeviceInfo.cs +++ b/nanoFirmwareFlasher.Library/Esp32DeviceInfo.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System.Text; using System.Text.RegularExpressions; @@ -81,6 +79,7 @@ public class Esp32DeviceInfo /// Flash device type ID. /// The size of the flash in bytes. /// Availability of PSRAM. + /// Size of the PSRAM public Esp32DeviceInfo( string chipType, string chipName, @@ -163,7 +162,7 @@ public override string ToString() private string GetFlashManufacturer() { - var match = Regex.Match(FlashChipIds, $"(#define )(?.*_ID.*0x{FlashManufacturerId:X})"); + Match match = Regex.Match(FlashChipIds, $"(#define )(?.*_ID.*0x{FlashManufacturerId:X})"); if (match.Success) { @@ -183,7 +182,7 @@ private string GetFlashDeviceId() int mfgLIndex; string mfgId; - var match = Regex.Match(FlashChipIds, $"(#define )(?.*_ID.*0x{FlashManufacturerId:X})"); + Match match = Regex.Match(FlashChipIds, $"(#define )(?.*_ID.*0x{FlashManufacturerId:X})"); if (match.Success) { @@ -202,7 +201,7 @@ private string GetFlashDeviceId() { string deviceLine = match.Groups["deviceid"].ToString(); - var tabIndex = deviceLine.IndexOf("\t"); + int tabIndex = deviceLine.IndexOf("\t"); return deviceLine.Substring(0, tabIndex); } diff --git a/nanoFirmwareFlasher.Library/Esp32Firmware.cs b/nanoFirmwareFlasher.Library/Esp32Firmware.cs index f64aa933..b89e431e 100644 --- a/nanoFirmwareFlasher.Library/Esp32Firmware.cs +++ b/nanoFirmwareFlasher.Library/Esp32Firmware.cs @@ -1,9 +1,6 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -40,17 +37,17 @@ internal class Esp32Firmware : FirmwarePackage public Esp32Firmware( string targetName, string fwVersion, - bool stable, + bool preview, PartitionTableSize? partitionTableSize) : base( targetName, fwVersion, - stable) + preview) { _partitionTableSize = partitionTableSize; } - internal async System.Threading.Tasks.Task DownloadAndExtractAsync(Esp32DeviceInfo deviceInfo) + internal async System.Threading.Tasks.Task DownloadAndExtractAsync(Esp32DeviceInfo deviceInfo, string archiveDirectoryPath) { int flashSize = deviceInfo.FlashSize; @@ -66,14 +63,14 @@ internal async System.Threading.Tasks.Task DownloadAndExtractAsync(Es { if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine($"There is no firmware available for ESP32 with {Esp32DeviceInfo.GetFlashSizeAsString(flashSize)} flash size!{Environment.NewLine}Only the following flash sizes are supported: {string.Join(", ", SupportedFlashSizes.Select(size => size >= 0x10000 ? $"{size / 0x100000}MB" : $"{size / 0x400}kB."))}"); + OutputWriter.WriteLine($"There is no firmware available for ESP32 with {Esp32DeviceInfo.GetFlashSizeAsString(flashSize)} flash size!{Environment.NewLine}Only the following flash sizes are supported: {string.Join(", ", SupportedFlashSizes.Select(size => size >= 0x10000 ? $"{size / 0x100000}MB" : $"{size / 0x400}kB."))}"); } return ExitCodes.E4001; } // perform download and extract - var executionResult = await DownloadAndExtractAsync(); + ExitCodes executionResult = await DownloadAndExtractAsync(archiveDirectoryPath); if (executionResult == ExitCodes.OK) { diff --git a/nanoFirmwareFlasher.Library/Esp32Operations.cs b/nanoFirmwareFlasher.Library/Esp32Operations.cs index cca27c95..4a1420a4 100644 --- a/nanoFirmwareFlasher.Library/Esp32Operations.cs +++ b/nanoFirmwareFlasher.Library/Esp32Operations.cs @@ -1,10 +1,7 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; @@ -58,7 +55,7 @@ public static ExitCodes BackupFlash( fileName = $"{device.ChipName}_0x{device.MacAddress}_{DateTime.UtcNow.ToShortDateString()}.bin"; } - var backupFilePath = Path.Combine(backupPath, fileName); + string backupFilePath = Path.Combine(backupPath, fileName); // check file existence if (File.Exists(fileName)) @@ -75,17 +72,17 @@ public static ExitCodes BackupFlash( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"Backing up the firmware to \r\n{backupFilePath}..."); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine($"Backing up the firmware to \r\n{backupFilePath}..."); + OutputWriter.ForegroundColor = ConsoleColor.White; } tool.BackupFlash(backupFilePath, device.FlashSize); if (verbosity > VerbosityLevel.Quiet) { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine($"Flash backup saved to {fileName}"); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine($"Flash backup saved to {fileName}"); } return ExitCodes.OK; @@ -100,8 +97,11 @@ public static ExitCodes BackupFlash( /// Set to to force download of firmware package. /// Firmware version to update to. /// Set to to use preview version to update. + /// Only show which firmware to use; do not deploy anything to the device. + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and never be downloaded. /// Path to application to update along with the firmware update. - /// Flash address to use when deploying an aplication. + /// Flash address to use when deploying an application. /// Path to CLR file to use for firmware update. /// to perform validation of update package against connected target. /// If perform mass erase on device before updating. @@ -115,6 +115,8 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( bool updateFw, string fwVersion, bool preview, + bool showFwOnly, + string archiveDirectoryPath, string applicationPath, string deploymentAddress, string clrFile, @@ -123,7 +125,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( VerbosityLevel verbosity, PartitionTableSize? partitionTableSize) { - var operationResult = ExitCodes.OK; + ExitCodes operationResult = ExitCodes.OK; uint address = 0; bool updateCLRfile = !string.IsNullOrEmpty(clrFile); @@ -136,13 +138,13 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( esp32Device.ChipType != "ESP32-S3") { // connected to a device not supported - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("******************************* WARNING *******************************"); - Console.WriteLine("Seems that the connected device is not supported by .NET nanoFramework"); - Console.WriteLine("Most likely it won't boot"); - Console.WriteLine("************************************************************************"); - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************************* WARNING *******************************"); + OutputWriter.WriteLine("Seems that the connected device is not supported by .NET nanoFramework"); + OutputWriter.WriteLine("Most likely it won't boot"); + OutputWriter.WriteLine("************************************************************************"); + OutputWriter.WriteLine(""); } // if a target name wasn't specified try to guess from the device characteristics @@ -162,9 +164,9 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } else { - var revisionSuffix = "_REV0"; - var psRamSegment = ""; - var otherSegment = ""; + string revisionSuffix = "_REV0"; + string psRamSegment = ""; + string otherSegment = ""; if (esp32Device.ChipName.Contains("revision v3")) { @@ -200,26 +202,26 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( esp32Device.ChipName.Contains("revision v2"))) { // trying to use a target that's not compatible with the connected device - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("***************************************** WARNING ****************************************"); - Console.WriteLine("Seems that the firmware image that's about to be used is for a revision 3 device, but the"); - Console.WriteLine($"connected device is {esp32Device.ChipName}."); - Console.WriteLine("******************************************************************************************"); - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("***************************************** WARNING ****************************************"); + OutputWriter.WriteLine("Seems that the firmware image that's about to be used is for a revision 3 device, but the"); + OutputWriter.WriteLine($"connected device is {esp32Device.ChipName}."); + OutputWriter.WriteLine("******************************************************************************************"); + OutputWriter.WriteLine(""); } if (targetName.Contains("BLE") && !esp32Device.Features.Contains(", BT,")) { // trying to use a traget with BT and the connected device doens't have support for it - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("******************************************* WARNING *******************************************"); - Console.WriteLine("Seems that the firmware image that's about to be used includes Bluetooth features, but the"); - Console.WriteLine($"connected device does not have support for it. You should use a target without BLE in the name."); - Console.WriteLine("************************************************************************************************"); - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************************************* WARNING *******************************************"); + OutputWriter.WriteLine("Seems that the firmware image that's about to be used includes Bluetooth features, but the"); + OutputWriter.WriteLine($"connected device does not have support for it. You should use a target without BLE in the name."); + OutputWriter.WriteLine("************************************************************************************************"); + OutputWriter.WriteLine(""); } } } @@ -245,13 +247,13 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } else { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine($"Unsupported ESP32_C3 revision."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Unsupported ESP32_C3 revision."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E9000; } @@ -272,13 +274,13 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } else { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine($"Unsupported ESP32_C6 revision."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Unsupported ESP32_C6 revision."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E9000; } @@ -299,13 +301,13 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } else { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine($"Unsupported ESP32_H2 revision."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Unsupported ESP32_H2 revision."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E9000; } @@ -322,14 +324,14 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // can't guess with certainty for this series, better request a target name to the user - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine($"For ESP32-S2 series nanoff isn't able to make an educated guess on the best target to use."); - Console.WriteLine($"Please provide a valid target name using this option '--target MY_ESP32_S2_TARGET' instead of '--platform esp32'."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"For ESP32-S2 series nanoff isn't able to make an educated guess on the best target to use."); + OutputWriter.WriteLine($"Please provide a valid target name using this option '--target MY_ESP32_S2_TARGET' instead of '--platform esp32'."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E9000; } @@ -350,13 +352,13 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } else { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine($"Unsupported ESP32_S3 revision."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Unsupported ESP32_S3 revision."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E9000; } @@ -365,13 +367,23 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( targetName = $"ESP32_S3{revisionSuffix}"; } - Console.ForegroundColor = ConsoleColor.Blue; + if (showFwOnly) + { + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Target '{targetName}' best matches the device characteristics."); + OutputWriter.WriteLine(""); + return ExitCodes.OK; + } + else + { + OutputWriter.ForegroundColor = ConsoleColor.Blue; - Console.WriteLine(""); - Console.WriteLine($"No target name was provided! Using '{targetName}' based on the device characteristics."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"No target name was provided! Using '{targetName}' based on the device characteristics."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; + } } Esp32Firmware firmware = new Esp32Firmware( @@ -409,7 +421,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // need to download update package? if (updateFw) { - operationResult = await firmware.DownloadAndExtractAsync(esp32Device); + operationResult = await firmware.DownloadAndExtractAsync(esp32Device, archiveDirectoryPath); if (operationResult != ExitCodes.OK) { @@ -473,8 +485,8 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // updating fw calls for a flash erase if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Erasing flash..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Erasing flash..."); } operationResult = espTool.EraseFlash(); @@ -483,12 +495,12 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( { if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK".PadRight(110)); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK".PadRight(110)); } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } } } @@ -507,15 +519,15 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( { if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Backup configuration..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Backup configuration..."); } // can't do this without a partition table // compose path to partition file string partitionCsvFile = Path.Combine(firmware.LocationPath, $"partitions_nanoclr_{Esp32DeviceInfo.GetFlashSizeAsString(esp32Device.FlashSize).ToLowerInvariant()}.csv"); - var partitionDetails = File.ReadAllText(partitionCsvFile); + string partitionDetails = File.ReadAllText(partitionCsvFile); // grab details for the config partition string pattern = @"config,.*?(0x[0-9A-Fa-f]+),.*?(0x[0-9A-Fa-f]+),"; @@ -535,27 +547,27 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( configPartitionBackup, configPartitionAddress, configPartitionSize); - + if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Backup configuration..."); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK".PadRight(110)); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Backup configuration..."); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK".PadRight(110)); } - + firmware.FlashPartitions.Add(configPartitionAddress, configPartitionBackup); } } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; if (verbosity < VerbosityLevel.Normal) { // output the start of operation message for verbosity lower than normal // otherwise the progress from esptool is shown - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Flashing firmware..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Flashing firmware..."); } // write to flash @@ -566,36 +578,36 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( if (verbosity < VerbosityLevel.Normal) { // operation completed output - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK".PadRight(110)); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK".PadRight(110)); } if (verbosity >= VerbosityLevel.Normal) { // output the full message as usual after the progress from esptool - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Flashing firmware..."); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK".PadRight(110)); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Flashing firmware..."); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK".PadRight(110)); // warn user if reboot is not possible if (espTool.CouldntResetTarget) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("**********************************************"); - Console.WriteLine("The connected device is in 'download mode'."); - Console.WriteLine("Please reset the chip manually to run nanoCLR."); - Console.WriteLine("**********************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("**********************************************"); + OutputWriter.WriteLine("The connected device is in 'download mode'."); + OutputWriter.WriteLine("Please reset the chip manually to run nanoCLR."); + OutputWriter.WriteLine("**********************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } } @@ -612,14 +624,14 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // don't care } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } return operationResult; } /// - /// Deplay application on a ESP32 device. + /// Deploy application on a ESP32 device. /// /// to use when performing update. /// of device to update. @@ -649,13 +661,13 @@ public static async System.Threading.Tasks.Task DeployApplicationAsyn esp32Device.ChipType != "ESP32-S3") { // connected to a device not supported - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("******************************* WARNING *******************************"); - Console.WriteLine("Seems that the connected device is not supported by .NET nanoFramework"); - Console.WriteLine("Most likely it won't boot"); - Console.WriteLine("************************************************************************"); - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************************* WARNING *******************************"); + OutputWriter.WriteLine("Seems that the connected device is not supported by .NET nanoFramework"); + OutputWriter.WriteLine("Most likely it won't boot"); + OutputWriter.WriteLine("************************************************************************"); + OutputWriter.WriteLine(""); } Esp32Firmware firmware = new Esp32Firmware( @@ -704,11 +716,11 @@ public static async System.Threading.Tasks.Task DeployApplicationAsyn } } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; if (verbosity >= VerbosityLevel.Normal) { - Console.Write($"Flashing deployment partition..."); + OutputWriter.Write($"Flashing deployment partition..."); } // write to flash @@ -718,31 +730,31 @@ public static async System.Threading.Tasks.Task DeployApplicationAsyn { if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK".PadRight(110)); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK".PadRight(110)); // warn user if reboot is not possible if (espTool.CouldntResetTarget) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("**********************************************"); - Console.WriteLine("The connected device is in 'download mode'."); - Console.WriteLine("Please reset the chip manually to run nanoCLR."); - Console.WriteLine("**********************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("**********************************************"); + OutputWriter.WriteLine("The connected device is in 'download mode'."); + OutputWriter.WriteLine("Please reset the chip manually to run nanoCLR."); + OutputWriter.WriteLine("**********************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return operationResult; } diff --git a/nanoFirmwareFlasher.Library/EspTool.cs b/nanoFirmwareFlasher.Library/EspTool.cs index e7d629fa..b3dc3943 100644 --- a/nanoFirmwareFlasher.Library/EspTool.cs +++ b/nanoFirmwareFlasher.Library/EspTool.cs @@ -104,16 +104,16 @@ public EspTool( { if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.DarkRed; + OutputWriter.ForegroundColor = ConsoleColor.DarkRed; - Console.WriteLine(""); - Console.WriteLine("******************** EXCEPTION ******************"); - Console.WriteLine($"Exception occurred while trying to open <{serialPort}>:"); - Console.WriteLine($"{ex.Message}"); - Console.WriteLine("*************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************** EXCEPTION ******************"); + OutputWriter.WriteLine($"Exception occurred while trying to open <{serialPort}>:"); + OutputWriter.WriteLine($"{ex.Message}"); + OutputWriter.WriteLine("*************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } // presume any exception here is caused by the serial not existing or not possible to open @@ -130,7 +130,7 @@ public EspTool( if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine($"Using {serialPort} @ {baudRate} baud to connect to ESP32."); + OutputWriter.WriteLine($"Using {serialPort} @ {baudRate} baud to connect to ESP32."); } // set properties @@ -152,8 +152,8 @@ public Esp32DeviceInfo GetDeviceDetails( if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Reading details from chip..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Reading details from chip..."); } // execute flash_id command and parse the result @@ -167,14 +167,14 @@ public Esp32DeviceInfo GetDeviceDetails( { if (messages.Contains("A fatal error occurred: Failed to connect to Espressif device: No serial data received.")) { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine("Can't connect to ESP32 bootloader. Try to put the board in bootloader manually."); - Console.WriteLine("For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("Can't connect to ESP32 bootloader. Try to put the board in bootloader manually."); + OutputWriter.WriteLine("For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } throw new EspToolExecutionException(messages); @@ -197,7 +197,7 @@ public Esp32DeviceInfo GetDeviceDetails( } } - var match = Regex.Match(messages, + Match match = Regex.Match(messages, $"(Detecting chip type... )(?[ESP32\\-ICOCH6]+)(.*?[\r\n]*)*(Chip is )(?.*)(.*?[\r\n]*)*(Features: )(?.*)(.*?[\r\n]*)*(Crystal is )(?.*)(.*?[\r\n]*)*(MAC: )(?.*)(.*?[\r\n]*)*(Manufacturer: )(?.*)(.*?[\r\n]*)*(Device: )(?.*)(.*?[\r\n]*)*(Detected flash size: )(?.*)"); if (!match.Success) @@ -262,9 +262,9 @@ public Esp32DeviceInfo GetDeviceDetails( if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK".PadRight(110)); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK".PadRight(110)); + OutputWriter.ForegroundColor = ConsoleColor.White; } return new Esp32DeviceInfo( @@ -292,7 +292,7 @@ private PSRamAvailability FindPSRamAvailable( { // don't want to output anything from esptool // backup current verbosity setting - var bkpVerbosity = Verbosity; + VerbosityLevel bkpVerbosity = Verbosity; Verbosity = VerbosityLevel.Quiet; // default to no PSRAM @@ -305,7 +305,7 @@ private PSRamAvailability FindPSRamAvailable( { // adjust flash size according to the series // defautl to 2MB for ESP32 series - var flashSize = 2 * 1024 * 1024; + int flashSize = 2 * 1024 * 1024; if (_chipType == "esp32s2" || _chipType == "esp32s3") @@ -373,7 +373,7 @@ private PSRamAvailability FindPSRamAvailable( Thread.Sleep(TimeSpan.FromSeconds(2)); // ... read output from bootloader - var bootloaderOutput = espDevice.ReadExisting(); + string bootloaderOutput = espDevice.ReadExisting(); espDevice.Close(); @@ -387,7 +387,7 @@ private PSRamAvailability FindPSRamAvailable( // I(209) esp_psram: PSRAM initialized, cache is in low / high(2 - core) mode. // extract PSRAM size - var match = Regex.Match(bootloaderOutput, @"Found (?\d+)MB PSRAM device"); + Match match = Regex.Match(bootloaderOutput, @"Found (?\d+)MB PSRAM device"); if (match.Success) { psRamSize = int.Parse(match.Groups["size"].Value); @@ -444,7 +444,7 @@ internal void BackupFlash(string backupFilename, throw new ReadEsp32FlashException(messages); } - var match = Regex.Match(messages, "(?Read .*)(.*?\n)*"); + Match match = Regex.Match(messages, "(?Read .*)(.*?\n)*"); if (!match.Success) { throw new ReadEsp32FlashException(messages); @@ -452,7 +452,7 @@ internal void BackupFlash(string backupFilename, if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine(match.Groups["message"].ToString().Trim()); + OutputWriter.WriteLine(match.Groups["message"].ToString().Trim()); } } @@ -480,7 +480,7 @@ internal ExitCodes BackupConfigPartition( throw new ReadEsp32FlashException(messages); } - var match = Regex.Match(messages, "(?Read .*)(.*?\n)*"); + Match match = Regex.Match(messages, "(?Read .*)(.*?\n)*"); if (!match.Success) { throw new ReadEsp32FlashException(messages); @@ -488,7 +488,7 @@ internal ExitCodes BackupConfigPartition( if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine(match.Groups["message"].ToString().Trim()); + OutputWriter.WriteLine(match.Groups["message"].ToString().Trim()); } return ExitCodes.OK; @@ -512,7 +512,7 @@ internal ExitCodes EraseFlash() throw new EraseEsp32FlashException(messages); } - var match = Regex.Match(messages, "(?Chip erase completed successfully.*)(.*?\n)*"); + Match match = Regex.Match(messages, "(?Chip erase completed successfully.*)(.*?\n)*"); if (!match.Success) { throw new EraseEsp32FlashException(messages); @@ -542,7 +542,7 @@ internal ExitCodes EraseFlashSegment(uint startAddress, uint length) throw new EraseEsp32FlashException(messages); } - var match = Regex.Match(messages, "(?Erase completed successfully.*)(.*?\n)*"); + Match match = Regex.Match(messages, "(?Erase completed successfully.*)(.*?\n)*"); if (!match.Success) { throw new EraseEsp32FlashException(messages); @@ -550,7 +550,7 @@ internal ExitCodes EraseFlashSegment(uint startAddress, uint length) if (Verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine(match.Groups["message"].ToString().Trim()); + OutputWriter.WriteLine(match.Groups["message"].ToString().Trim()); } return ExitCodes.OK; @@ -572,7 +572,7 @@ internal ExitCodes WriteFlash( int counter = 1; var regexGroupNames = new List(); - foreach (var part in partsToWrite) + foreach (KeyValuePair part in partsToWrite) { // start address followed by filename partsArguments.Append($"0x{part.Key:X} \"{part.Value}\" "); @@ -702,12 +702,12 @@ private bool RunEspTool( if (Verbosity == VerbosityLevel.Diagnostic) { - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; - Console.WriteLine(""); - Console.WriteLine("Executing esptool with the following parameters:"); - Console.WriteLine($"'{parameter}'"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("Executing esptool with the following parameters:"); + OutputWriter.WriteLine($"'{parameter}'"); + OutputWriter.WriteLine(""); } // start esptool and wait for exit @@ -722,7 +722,7 @@ private bool RunEspTool( connectPromptShown = false; connectPatternFound = false; connectTimeStamp = DateTime.UtcNow; - var progressStarted = false; + bool progressStarted = false; // showing progress is a little bit tricky if (Verbosity > VerbosityLevel.Quiet) @@ -750,14 +750,14 @@ private bool RunEspTool( if (!progressStarted) { // need to print the first line of the progress message - Console.Write("\r"); + OutputWriter.Write("\r"); progressStarted = true; } // print progress... and set the cursor to the beginning of the line (\r) - Console.Write(progress); - Console.Write("\r"); + OutputWriter.Write(progress); + OutputWriter.Write("\r"); } ProcessConnectPattern(messageBuilder); @@ -769,7 +769,7 @@ private bool RunEspTool( // need to clear all progress lines for (int i = 0; i < messageBuilder.Length; i++) { - Console.Write("\b"); + OutputWriter.Write("\b"); } } @@ -850,7 +850,7 @@ private void ProcessConnectPattern(StringBuilder messageBuilder) // try to find a connect pattern connectPatternFound = FindConnectPattern(messageBuilder); - var timeToConnect = DateTime.UtcNow.Subtract(connectTimeStamp).TotalSeconds; + double timeToConnect = DateTime.UtcNow.Subtract(connectTimeStamp).TotalSeconds; // if esptool is struggling to connect for more than 5 seconds @@ -859,11 +859,11 @@ private void ProcessConnectPattern(StringBuilder messageBuilder) connectPatternFound && timeToConnect > 5) { - Console.ForegroundColor = ConsoleColor.Magenta; + OutputWriter.ForegroundColor = ConsoleColor.Magenta; - Console.WriteLine("*** Hold down the BOOT/FLASH button in ESP32 board ***"); + OutputWriter.WriteLine("*** Hold down the BOOT/FLASH button in ESP32 board ***"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; // set flag connectPromptShown = true; @@ -874,8 +874,8 @@ private bool FindConnectPattern(StringBuilder messageBuilder) { if (messageBuilder.Length > 2) { - var previousChar = messageBuilder[messageBuilder.Length - 2]; - var newChar = messageBuilder[messageBuilder.Length - 1]; + char previousChar = messageBuilder[messageBuilder.Length - 2]; + char newChar = messageBuilder[messageBuilder.Length - 1]; // don't look for double dot (..) sequence so it doesn't mistake it with an ellipsis (...) return ((previousChar == '.' diff --git a/nanoFirmwareFlasher.Library/ExitCodes.cs b/nanoFirmwareFlasher.Library/ExitCodes.cs index e829983b..9d664d65 100644 --- a/nanoFirmwareFlasher.Library/ExitCodes.cs +++ b/nanoFirmwareFlasher.Library/ExitCodes.cs @@ -1,9 +1,6 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using System; using System.ComponentModel.DataAnnotations; namespace nanoFramework.Tools.FirmwareFlasher @@ -213,6 +210,12 @@ public enum ExitCodes [Display(Name = "Need to specify a COM port.")] E6001 = 6001, + /// + /// Timeout of the connection + /// + [Display(Name = "Couldn't access serial device. Another (nanoFramework) application has exclusive access to the device.")] + E6002 = 6002, + /////////////// // TI Errors // /////////////// diff --git a/nanoFirmwareFlasher.Library/FileDeployment/FileDeploymentManager.cs b/nanoFirmwareFlasher.Library/FileDeployment/FileDeploymentManager.cs index 2936db3e..6c437463 100644 --- a/nanoFirmwareFlasher.Library/FileDeployment/FileDeploymentManager.cs +++ b/nanoFirmwareFlasher.Library/FileDeployment/FileDeploymentManager.cs @@ -1,14 +1,12 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using nanoFramework.Tools.Debugger; -using nanoFramework.Tools.Debugger.Extensions; -using Newtonsoft.Json; using System; using System.IO; using System.Threading.Tasks; +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.Extensions; +using Newtonsoft.Json; namespace nanoFramework.Tools.FirmwareFlasher.FileDeployment { @@ -54,11 +52,11 @@ public async Task DeployAsync() device = serialDebugClient.NanoFrameworkDevices[0]; } - catch (Exception ex) + catch (Exception) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Error connecting to nanoDevice on {_serialPort} to deploy files"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine($"Error connecting to nanoDevice on {_serialPort} to deploy files"); + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E2000; } @@ -68,7 +66,7 @@ public async Task DeployAsync() device.CreateDebugEngine(); if (_verbosity >= VerbosityLevel.Normal) { - Console.WriteLine($"Debug engine created."); + OutputWriter.WriteLine($"Debug engine created."); } } @@ -78,11 +76,11 @@ public async Task DeployAsync() bool connectResult = device.DebugEngine.Connect(5000, true, true); if (retryCount == 0 && _verbosity >= VerbosityLevel.Normal) { - Console.WriteLine($"Device connected and ready for file deployment."); + OutputWriter.WriteLine($"Device connected and ready for file deployment."); } else if (_verbosity >= VerbosityLevel.Normal) { - Console.WriteLine($"Device connect result is {connectResult}. Attempt {retryCount}/{_numberOfRetries}"); + OutputWriter.WriteLine($"Device connect result is {connectResult}. Attempt {retryCount}/{_numberOfRetries}"); } if (!connectResult) @@ -97,9 +95,9 @@ public async Task DeployAsync() } else { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Error connecting to debug engine on nanoDevice on {_serialPort} to deploy files"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine($"Error connecting to debug engine on nanoDevice on {_serialPort} to deploy files"); + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E2000; } } @@ -111,9 +109,9 @@ public async Task DeployAsync() { if (_verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"Device status verified as being in initialized state. Requesting to resume execution. Attempt {retryCount}/{_numberOfRetries}."); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine($"Device status verified as being in initialized state. Requesting to resume execution. Attempt {retryCount}/{_numberOfRetries}."); + OutputWriter.ForegroundColor = ConsoleColor.White; } // set flag @@ -132,7 +130,7 @@ public async Task DeployAsync() { if (_verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine($"Device has completed initialization."); + OutputWriter.WriteLine($"Device has completed initialization."); } // done here @@ -142,7 +140,7 @@ public async Task DeployAsync() if (_verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine($"Waiting for device to report initialization completed ({retryCount}/{_numberOfRetries})."); + OutputWriter.WriteLine($"Waiting for device to report initialization completed ({retryCount}/{_numberOfRetries})."); } // provide feedback to user on the 1st pass @@ -150,7 +148,7 @@ public async Task DeployAsync() { if (_verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine($"Waiting for device to initialize."); + OutputWriter.WriteLine($"Waiting for device to initialize."); } } @@ -158,7 +156,7 @@ public async Task DeployAsync() { if (_verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine($"Device reported running nanoBooter. Requesting to load nanoCLR."); + OutputWriter.WriteLine($"Device reported running nanoBooter. Requesting to load nanoCLR."); } // request nanoBooter to load CLR @@ -168,9 +166,9 @@ public async Task DeployAsync() { if (_verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"Device reported running nanoCLR. Requesting to reboot nanoCLR."); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine($"Device reported running nanoCLR. Requesting to reboot nanoCLR."); + OutputWriter.ForegroundColor = ConsoleColor.White; } await Task.Run(delegate @@ -191,44 +189,44 @@ await Task.Run(delegate if (!deviceIsInInitializeState) { // Deploy each file - foreach (var file in _configuration.Files) + foreach (DeploymentFile file in _configuration.Files) { try { if (string.IsNullOrEmpty(file.SourceFilePath)) { // deleting - Console.Write($"Deleting file {file.DestinationFilePath}..."); + OutputWriter.Write($"Deleting file {file.DestinationFilePath}..."); if (device.DebugEngine.DeleteStorageFile(file.DestinationFilePath) != Debugger.WireProtocol.StorageOperationErrorCode.NoError) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(); - Console.WriteLine($"Error deleting file {file.DestinationFilePath}, it may not exist on the storage."); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(); + OutputWriter.WriteLine($"Error deleting file {file.DestinationFilePath}, it may not exist on the storage."); + OutputWriter.ForegroundColor = ConsoleColor.White; } else { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine($"OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } else { - Console.Write($"Deploying file {file.SourceFilePath} to {file.DestinationFilePath}..."); - var ret = device.DebugEngine.AddStorageFile(file.DestinationFilePath, File.ReadAllBytes(file.SourceFilePath)); + OutputWriter.Write($"Deploying file {file.SourceFilePath} to {file.DestinationFilePath}..."); + Debugger.WireProtocol.StorageOperationErrorCode ret = device.DebugEngine.AddStorageFile(file.DestinationFilePath, File.ReadAllBytes(file.SourceFilePath)); if (ret != Debugger.WireProtocol.StorageOperationErrorCode.NoError) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(); - Console.WriteLine($"Error deploying content file {file.SourceFilePath} to {file.DestinationFilePath}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine(); + OutputWriter.WriteLine($"Error deploying content file {file.SourceFilePath} to {file.DestinationFilePath}"); + OutputWriter.ForegroundColor = ConsoleColor.White; } else { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine($"OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } } @@ -236,10 +234,10 @@ await Task.Run(delegate { if (_verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(); - Console.WriteLine($"Exception deploying content file {file.SourceFilePath} to {file.DestinationFilePath}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine(); + OutputWriter.WriteLine($"Exception deploying content file {file.SourceFilePath} to {file.DestinationFilePath}"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } } diff --git a/nanoFirmwareFlasher.Library/FirmwareArchiveManager.cs b/nanoFirmwareFlasher.Library/FirmwareArchiveManager.cs new file mode 100644 index 00000000..bd9890de --- /dev/null +++ b/nanoFirmwareFlasher.Library/FirmwareArchiveManager.cs @@ -0,0 +1,216 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace nanoFramework.Tools.FirmwareFlasher +{ + /// + /// The manager of a firmware archive directory. It supports downloading firmware packages and the runtimes + /// for the Virtual Device to a local directory from the online archive. That directory can then be used as + /// a source for list and deployment operations instead of the online archive. + /// + public sealed class FirmwareArchiveManager + { + private readonly string _archivePath; + private const string INFOFILE_EXTENSION = ".json"; + + #region Construction + /// + /// Create a new manager for the firmware archive + /// + /// Path to the firmware archive directory + public FirmwareArchiveManager(string fwArchivePath) + { + _archivePath = Path.GetFullPath(fwArchivePath); + } + #endregion + + #region Methods + /// + /// List the targets for which firmware is present in the firmware archive + /// + /// Option for preview version. + /// Platform code to use on search. + /// VerbosityLevel to use when outputting progress and error messages. + /// List of with details on target firmware packages. + public List GetTargetList( + bool preview, + SupportedPlatform? platform, + VerbosityLevel verbosity) + { + List targetPackages = []; + + if (verbosity > VerbosityLevel.Normal) + { + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine($"Listing {platform} targets from firmware archive '{_archivePath}'..."); + } + + if (Directory.Exists(_archivePath)) + { + foreach (string filePath in Directory.EnumerateFiles(_archivePath, $"*{INFOFILE_EXTENSION}")) + { + PersistedPackageInformation packageInformation = JsonConvert.DeserializeObject(File.ReadAllText(filePath)); + if (packageInformation.IsPreview == preview && + (platform is null || platform.Value.ToString().Equals(packageInformation.Platform, StringComparison.OrdinalIgnoreCase))) + { + targetPackages.Add(packageInformation); + } + } + } + + return targetPackages; + } + + /// + /// Download a firmware package from the repository and add it to the archive directory + /// + /// Option for preview version. + /// Platform code to use on search. This should not be null otherwise the firmware may not be found by nanoff. + /// Name of the target the firmware is created for. Must be assigned. + /// Version of the firmware to download; can be null. + /// VerbosityLevel to use when outputting progress and error messages. + /// The result of the task + public async Task DownloadFirmwareFromRepository( + bool preview, + SupportedPlatform? platform, + string targetName, + string version, + VerbosityLevel verbosity) + { + // Find the requested firmware in the repository + var remoteTargets = new Dictionary(); + foreach (bool isCommunityTarget in new bool[] { false, true }) + { + if (preview && isCommunityTarget) + { + continue; + } + foreach (CloudSmithPackageDetail targetInfo in FirmwarePackage.GetTargetList( + isCommunityTarget, + preview, + platform, + verbosity)) + { + if ((targetName is null || targetInfo.Name == targetName) + && (version is null || version == targetInfo.Version)) + { + var thisVersion = new Version(targetInfo.Version); + if (!remoteTargets.TryGetValue(targetInfo.Name, out (CloudSmithPackageDetail package, Version version) latestVersion) + || latestVersion.version < thisVersion) + { + remoteTargets[targetInfo.Name] = (targetInfo, thisVersion); + if (targetName is not null && version is not null) + { + break; + } + } + } + } + if (targetName is not null && version is not null && remoteTargets.Count > 0) + { + break; + } + } + if (remoteTargets.Count == 0) + { + return ExitCodes.E9005; + } + + ExitCodes result = ExitCodes.OK; + + foreach ((CloudSmithPackageDetail remoteTarget, Version _) in remoteTargets.Values) + { + // Download the firmware + var package = new ArchiveFirmwarePackage(remoteTarget.Name, remoteTarget.Version, preview) + { + Verbosity = verbosity + }; + (ExitCodes exitCode, string fwFilePath) = await package.DownloadPackageAsync(_archivePath, null, false, false); + if (exitCode != ExitCodes.OK) + { + result = exitCode; + if (verbosity >= VerbosityLevel.Normal) + { + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine($"Could not download target {remoteTarget.Name} {remoteTarget.Version}"); + OutputWriter.ForegroundColor = ConsoleColor.White; + } + continue; + } + else if (verbosity > VerbosityLevel.Normal) + { + OutputWriter.WriteLine($"Added target {remoteTarget.Name} {remoteTarget.Version} to the archive"); + } + + // Write the file with package information + var packageInformation = new PersistedPackageInformation + { + IsPreview = preview, + Name = remoteTarget.Name, + Platform = platform.ToString(), + Version = remoteTarget.Version, + }; + File.WriteAllText( + $"{(fwFilePath.EndsWith(".zip") ? fwFilePath : Path.GetDirectoryName(fwFilePath))}{INFOFILE_EXTENSION}", + JsonConvert.SerializeObject(packageInformation) + ); + } + return result; + } + #endregion + + #region Auxiliary classes that should only be used by the archive manager + + #region Persisted firmware information + /// + /// Class with details of a CloudSmith package. The base class is the one + /// used for listing targets. The extra fields are required to select the + /// correct package for the firmware that should be installed. + /// + private sealed class PersistedPackageInformation : CloudSmithPackageDetail + { + /// + /// Platform code + /// + public string Platform { get; set; } + + /// + /// Indicates whether this is a preview + /// + public bool IsPreview { get; set; } + } + #endregion + + #region Firmware package + /// + /// A lot of business logic for downloading the firmware is coded in the + /// class. Unfortunately the constructor required + /// for the archive manager is accessible only via a derived class. + /// + private sealed class ArchiveFirmwarePackage : FirmwarePackage + { + /// + /// Constructor + /// + /// Target name as designated in the repositories. + /// The firmware version. + /// Whether to use preview versions. + internal ArchiveFirmwarePackage( + string targetName, + string fwVersion, + bool preview) + : base(targetName, fwVersion, preview) + { + } + } + #endregion + + #endregion + } +} diff --git a/nanoFirmwareFlasher.Library/FirmwarePackage.cs b/nanoFirmwareFlasher.Library/FirmwarePackage.cs index 8f1bf341..e7d9c1dc 100644 --- a/nanoFirmwareFlasher.Library/FirmwarePackage.cs +++ b/nanoFirmwareFlasher.Library/FirmwarePackage.cs @@ -1,13 +1,6 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.DataContracts; -using Microsoft.Extensions.Configuration; -using nanoFramework.Tools.Debugger; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -16,7 +9,11 @@ using System.Net.Http; using System.Reflection; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; +using Microsoft.ApplicationInsights.DataContracts; +using nanoFramework.Tools.Debugger; +using Newtonsoft.Json; namespace nanoFramework.Tools.FirmwareFlasher { @@ -26,28 +23,31 @@ namespace nanoFramework.Tools.FirmwareFlasher public abstract class FirmwarePackage : IDisposable { // HttpClient is intended to be instantiated once per application, rather than per-use. - static readonly HttpClient _cloudsmithClient; + static readonly HttpClient s_cloudsmithClient; - private const string _refTargetsDevRepo = "nanoframework-images-dev"; - private const string _refTargetsStableRepo = "nanoframework-images"; - private const string _communityTargetsRepo = "nanoframework-images-community-targets"; + private const string RefTargetsDevRepo = "nanoframework-images-dev"; + private const string RefTargetsStableRepo = "nanoframework-images"; + private const string CommunityTargetsRepo = "nanoframework-images-community-targets"; private readonly string _targetName; private readonly bool _preview; - private const string _readmeContent = "This folder contains nanoFramework firmware files. Can safely be removed."; + private const string ReadmeContent = "This folder contains nanoFramework firmware files. Can safely be removed."; + + private static readonly string s_defaultLocationPathBase = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".nanoFramework", + "fw_cache"); + private static readonly AsyncLocal s_locationPathBase = new(); /// /// Path with the base location for firmware packages. /// public static string LocationPathBase { - get - { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".nanoFramework", - "fw_cache"); - } + get => s_locationPathBase.Value is null ? s_defaultLocationPathBase : s_locationPathBase.Value; + + // The path must be assignable for testability + internal set => s_locationPathBase.Value = value; } /// @@ -103,11 +103,11 @@ public static string LocationPathBase static FirmwarePackage() { - _cloudsmithClient = new HttpClient + s_cloudsmithClient = new HttpClient { BaseAddress = new Uri("https://api.cloudsmith.io/v1/packages/net-nanoframework/") }; - _cloudsmithClient.DefaultRequestHeaders.Add("Accept", "*/*"); + s_cloudsmithClient.DefaultRequestHeaders.Add("Accept", "*/*"); } /// @@ -151,9 +151,9 @@ public static List GetTargetList( SupportedPlatform? platform, VerbosityLevel verbosity) { - string repoName = communityTargets ? _communityTargetsRepo : preview ? _refTargetsDevRepo : _refTargetsStableRepo; + string repoName = communityTargets ? CommunityTargetsRepo : preview ? RefTargetsDevRepo : RefTargetsStableRepo; - // NOTE: the query seems to be the oposite, it should be LESS THAN. + // NOTE: the query seems to be the opposite, it should be LESS THAN. // this has been reported to Cloudsmith and it's being checked. Maybe need to revisit this if changes are made in their API. // Because new stable releases are published on a regular basis and preview very rarely, we query for stable versions published in past month and preview versions published during the past 6 months. string requestUri = $"{repoName}/?page_size=500&q=uploaded:'>{(preview ? "6" : "1")} month ago' {(platform.HasValue ? "AND tag:" + platform.Value : "")}"; @@ -162,26 +162,26 @@ public static List GetTargetList( if (verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; - Console.Write($"Listing {platform} targets from '{repoName}' repository"); + OutputWriter.Write($"Listing {platform} targets from '{repoName}' repository"); if (!communityTargets) { if (preview) { - Console.Write(" [PREVIEW]"); + OutputWriter.Write(" [PREVIEW]"); } else { - Console.Write(" [STABLE]"); + OutputWriter.Write(" [STABLE]"); } } - Console.WriteLine("..."); + OutputWriter.WriteLine("..."); } - HttpResponseMessage response = _cloudsmithClient.GetAsync(requestUri).GetAwaiter().GetResult(); + HttpResponseMessage response = s_cloudsmithClient.GetAsync(requestUri).GetAwaiter().GetResult(); string responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); @@ -190,7 +190,7 @@ public static List GetTargetList( { if (verbosity > VerbosityLevel.Quiet) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } // can't find this target @@ -205,19 +205,11 @@ public static List GetTargetList( /// /// Download the firmware zip, extract this zip file, and get the firmware parts /// - /// a dictionary which keys are the start addresses and the values are the complete filenames (the bin files) - internal async Task DownloadAndExtractAsync() + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and never be downloaded. + /// The result of the download and extract operation + internal async Task DownloadAndExtractAsync(string archiveDirectoryPath) { - string fwFileName = null; - - string downloadUrl = string.Empty; - - // flag to signal if the work-flow step was successful - bool stepSuccessful = false; - - // flag to skip download if the fw package exists and it's recent - bool skipDownload = false; - // setup download folder // set download path try @@ -230,41 +222,168 @@ internal async Task DownloadAndExtractAsync() Path.Combine( LocationPathBase, "README.txt"), - _readmeContent); + ReadmeContent); // set location path to target folder LocationPath = Path.Combine( LocationPathBase, _targetName); - - Directory.CreateDirectory(LocationPath); } catch { - Console.WriteLine(""); + OutputWriter.WriteLine(""); return ExitCodes.E9006; } + // download the firmware package + (ExitCodes exitCode, string fwFilePath) = await DownloadPackageAsync(LocationPath, archiveDirectoryPath, true, true); + if (exitCode != ExitCodes.OK) + { + return exitCode; + } + + if (fwFilePath.EndsWith(".zip")) + { + // unzip the firmware + if (Verbosity >= VerbosityLevel.Normal) + { + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Extracting {Path.GetFileName(fwFilePath)}..."); + } + ZipFile.ExtractToDirectory( + fwFilePath, + LocationPath); + if (Verbosity >= VerbosityLevel.Normal) + { + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; + } + } + + // be nice to the user and delete any fw packages older than a month + Directory.GetFiles(LocationPath) + .Select(f => new FileInfo(f)) + .Where(f => f.Name != Path.GetFileName(fwFilePath) && f.Extension == Path.GetExtension(fwFilePath) && f.LastWriteTime < DateTime.Now.AddMonths(-1)) + .ToList() + .ForEach(f => f.Delete()); + + PostProcessDownloadAndExtract(); + + if (Verbosity >= VerbosityLevel.Normal) + { + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Updating to {Version}"); + OutputWriter.WriteLine(""); + + OutputWriter.ForegroundColor = ConsoleColor.White; + } + + return ExitCodes.OK; + } + + /// + /// Download the firmware zip to the . + /// + /// The directory to download the zip file to + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and is false. + /// If the download fails and there is a matching zip file present, use that zip instead. + /// The match is done by version only, so pass true only if the is specific for the target. + /// Removes existing files in with the same extensions as files that can be present in the zip file. + /// The result of the operation, and the full path to the downloaded file in case of success. + internal async Task<(ExitCodes exitCode, string fwFilePath)> DownloadPackageAsync(string locationPath, string archiveDirectoryPath, bool useExistingIfDownloadFails, bool cleanupUnpackedFiles) + { + LocationPath = locationPath; + string fwFileName = null!; + string extension = null!; List fwFiles = []; - if (_preview) + void UpdateLocationPathAndFileName(bool initial) { - fwFiles = Directory.GetFiles(LocationPath) + bool withPreview = _preview; + + if (_targetName == "WIN_DLL_nanoCLR" || _targetName == "WIN32_nanoCLR") + { + fwFileName = "nanoFramework.nanoCLR.dll"; + extension = ".dll"; + if (Version is not null) + { + LocationPath = Path.Combine(locationPath, $"{_targetName}-{Version}{(_preview ? "-preview" : "")}"); + initial = true; + withPreview = false; + } + } + else + { + extension = ".zip"; + fwFileName = $"{_targetName}-{Version}{(_preview ? "-preview" : "")}{extension}"; + } + if (initial) + { + Directory.CreateDirectory(LocationPath); + + if (withPreview) + { + fwFiles = [.. Directory.GetFiles(LocationPath) + .Select(f => new FileInfo(f)) + .Where(f => f.Name.Contains("-preview.") && f.Extension == extension) + .OrderByDescending(f => f.Name)]; + } + else + { + fwFiles = [.. Directory.GetFiles(LocationPath) .Select(f => new FileInfo(f)) - .Where(f => f.Name.Contains("-preview.") && f.Extension == ".zip") - .OrderByDescending(f => f.Name) - .ToList(); + .Where(f => !f.Name.Contains("-preview.") && f.Extension == extension) + .OrderByDescending(f => f.Name)]; + } + } + } - else + + // create the download folder + try { - fwFiles = Directory.GetFiles(LocationPath) - .Select(f => new FileInfo(f)) - .Where(f => !f.Name.Contains("-preview.") && f.Extension == ".zip") - .OrderByDescending(f => f.Name) - .ToList(); + UpdateLocationPathAndFileName(true); } + catch + { + OutputWriter.WriteLine(""); + + return (ExitCodes.E9006, null); + } + + // flag to skip download if the fw package exists and it's recent + // Not sure what "recent" is, version number should be enough to identify a unique version + bool skipDownload = (from f in fwFiles + where f.Name == fwFileName + select f).Any(); + if (archiveDirectoryPath is not null) + { + // Make sure the package is present in the archive + string archiveFileName = Path.Combine(archiveDirectoryPath, fwFileName); + if (!File.Exists(archiveFileName)) + { + // Package is considered to be not present, even if it does exist in the cache location + return (ExitCodes.E9007, null); + } + + if (!skipDownload) + { + // Copy the package to the cache + File.Copy(archiveFileName, Path.Combine(LocationPath, fwFileName)); + skipDownload = true; + } + } + + // flag to signal if the work-flow step was successful + bool stepSuccessful = skipDownload; + + string downloadUrl = string.Empty; if (!skipDownload) { @@ -277,27 +396,41 @@ internal async Task DownloadAndExtractAsync() if (downloadResult.Outcome != ExitCodes.OK) { - return downloadResult.Outcome; + return (downloadResult.Outcome, null); } downloadUrl = downloadResult.Url; // update with version from package about to be downloaded Version = downloadResult.Version; + try + { + UpdateLocationPathAndFileName(false); + } + catch + { + return (ExitCodes.E9006, null); + } + skipDownload = (from f in fwFiles + where f.Name == fwFileName + select f).Any(); stepSuccessful = !string.IsNullOrEmpty(downloadUrl); } - // cleanup any fw file in the folder - var filesToDelete = Directory.EnumerateFiles(LocationPath, "*.bin").ToList(); - filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.hex").ToList()); - filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.s19").ToList()); - filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.dfu").ToList()); - filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.csv").ToList()); - - foreach (var file in filesToDelete) + if (cleanupUnpackedFiles) { - File.Delete(file); + // cleanup any fw file in the folder + var filesToDelete = Directory.EnumerateFiles(LocationPath, "*.bin").ToList(); + filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.hex").ToList()); + filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.s19").ToList()); + filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.dfu").ToList()); + filesToDelete.AddRange(Directory.EnumerateFiles(LocationPath, "*.csv").ToList()); + + foreach (string file in filesToDelete) + { + File.Delete(file); + } } // check for file existence or download one @@ -307,83 +440,70 @@ internal async Task DownloadAndExtractAsync() // reset flag stepSuccessful = false; - fwFileName = $"{_targetName}-{Version}.zip"; - // check if we already have the file - if (!File.Exists( - Path.Combine( - LocationPath, - fwFileName))) + if (Verbosity >= VerbosityLevel.Normal) { - if (Verbosity >= VerbosityLevel.Normal) - { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Downloading firmware package..."); - } + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Downloading firmware package..."); + } - try + try + { + // setup and perform download request + using (HttpResponseMessage fwFileResponse = await s_cloudsmithClient.GetAsync(downloadUrl)) { - // setup and perform download request - using (var fwFileResponse = await _cloudsmithClient.GetAsync(downloadUrl)) + if (fwFileResponse.IsSuccessStatusCode) { - if (fwFileResponse.IsSuccessStatusCode) - { - using var readStream = await fwFileResponse.Content.ReadAsStreamAsync(); - using var fileStream = new FileStream( - Path.Combine(LocationPath, fwFileName), - FileMode.Create, FileAccess.Write); - await readStream.CopyToAsync(fileStream); - } - else - { - return ExitCodes.E9007; - } + using Stream readStream = await fwFileResponse.Content.ReadAsStreamAsync(); + using var fileStream = new FileStream( + Path.Combine(LocationPath, fwFileName), + FileMode.Create, FileAccess.Write); + await readStream.CopyToAsync(fileStream); } - - if (Verbosity >= VerbosityLevel.Normal) + else { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + return (ExitCodes.E9007, null); } + } - stepSuccessful = true; - - // send telemetry data on successful download - if (NanoTelemetryClient.TelemetryClient is not null) - { - AssemblyInformationalVersionAttribute nanoffVersion = null; + if (Verbosity >= VerbosityLevel.Normal) + { + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; + } - try - { - nanoffVersion = Attribute.GetCustomAttribute( - Assembly.GetEntryAssembly()!, - typeof(AssemblyInformationalVersionAttribute)) - as AssemblyInformationalVersionAttribute; - } - catch - { - // OK to fail here, just telemetry - } + stepSuccessful = true; - var packageTelemetry = new EventTelemetry("PackageDownloaded"); - packageTelemetry.Properties.Add("TargetName", _targetName); - packageTelemetry.Properties.Add("Version", Version); - packageTelemetry.Properties.Add("nanoffVersion", nanoffVersion == null ? "unknown" : nanoffVersion.InformationalVersion); + // send telemetry data on successful download + if (NanoTelemetryClient.TelemetryClient is not null) + { + AssemblyInformationalVersionAttribute nanoffVersion = null; - NanoTelemetryClient.TelemetryClient.TrackEvent(packageTelemetry); - NanoTelemetryClient.TelemetryClient.Flush(); + try + { + nanoffVersion = Attribute.GetCustomAttribute( + Assembly.GetEntryAssembly()!, + typeof(AssemblyInformationalVersionAttribute)) + as AssemblyInformationalVersionAttribute; } - } - catch - { - // exception with download, assuming it's something with network connection or Cloudsmith API + catch + { + // OK to fail here, just telemetry + } + + var packageTelemetry = new EventTelemetry("PackageDownloaded"); + packageTelemetry.Properties.Add("TargetName", _targetName); + packageTelemetry.Properties.Add("Version", Version); + packageTelemetry.Properties.Add("nanoffVersion", nanoffVersion == null ? "unknown" : nanoffVersion.InformationalVersion); + + NanoTelemetryClient.TelemetryClient.TrackEvent(packageTelemetry); + NanoTelemetryClient.TelemetryClient.Flush(); } } - else + catch { - // file already exists - stepSuccessful = true; + // exception with download, assuming it's something with network connection or Cloudsmith API } } @@ -392,7 +512,7 @@ internal async Task DownloadAndExtractAsync() // couldn't download the fw file // check if there is one available - if (fwFiles.Any()) + if (useExistingIfDownloadFails && fwFiles.Count != 0) { if (string.IsNullOrEmpty(Version)) {// take the 1st one @@ -400,27 +520,21 @@ internal async Task DownloadAndExtractAsync() } else { - string targetFileName = $"{_targetName}-{Version}.zip"; - fwFileName = fwFiles.Where(w => w.Name == targetFileName).Select(s => s.FullName).FirstOrDefault(); - } - - if (string.IsNullOrEmpty(fwFileName)) - { - return ExitCodes.E9007; + return (ExitCodes.E9007, null); } // get the version form the file name - var pattern = @"(\d+\.\d+\.\d+)(\.\d+|-.+)(?=\.zip)"; - var match = Regex.Matches(fwFileName, pattern, RegexOptions.IgnoreCase); + string pattern = $@"(\d+\.\d+\.\d+)(\.\d+|-.+)(?=\{extension})"; + MatchCollection match = Regex.Matches(fwFileName, pattern, RegexOptions.IgnoreCase); // set property Version = match[0].Value; if (Verbosity > VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Using cached firmware package"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine("Using cached firmware package"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } else @@ -429,56 +543,24 @@ internal async Task DownloadAndExtractAsync() if (Verbosity > VerbosityLevel.Quiet) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Failure to download package and couldn't find one in the cache."); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + if (useExistingIfDownloadFails) + { + OutputWriter.WriteLine("Failure to download package and couldn't find one in the cache."); + } + else + { + OutputWriter.WriteLine("Failure to download package."); + } + OutputWriter.ForegroundColor = ConsoleColor.White; } - return ExitCodes.E9007; + return (ExitCodes.E9007, null); } } // got here, must have a file! - - // unzip the firmware - if (Verbosity >= VerbosityLevel.Normal) - { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Extracting {Path.GetFileName(fwFileName)}..."); - } - - ZipFile.ExtractToDirectory( - Path.Combine(LocationPath, fwFileName), - LocationPath); - - if (Verbosity >= VerbosityLevel.Normal) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; - } - - // be nice to the user and delete any fw packages older than a month - Directory.GetFiles(LocationPath) - .Select(f => new FileInfo(f)) - .Where(f => f.Extension == ".zip" && f.LastWriteTime < DateTime.Now.AddMonths(-1)) - .ToList() - .ForEach(f => f.Delete()); - - PostProcessDownloadAndExtract(); - - if (Verbosity >= VerbosityLevel.Normal) - { - Console.ForegroundColor = ConsoleColor.Yellow; - - Console.WriteLine(""); - Console.WriteLine($"Updating to {Version}"); - Console.WriteLine(""); - - Console.ForegroundColor = ConsoleColor.White; - } - - return ExitCodes.OK; + return (ExitCodes.OK, Path.Combine(LocationPath, fwFileName)); } private static async Task GetDownloadUrlAsync( @@ -488,10 +570,10 @@ private static async Task GetDownloadUrlAsync( VerbosityLevel verbosity) { // reference targets - var repoName = isPreview ? _refTargetsDevRepo : _refTargetsStableRepo; + string repoName = isPreview ? RefTargetsDevRepo : RefTargetsStableRepo; // get the firmware version if it is defined - var fwVersionParam = string.IsNullOrEmpty(fwVersion) ? "latest" : fwVersion; + string fwVersionParam = string.IsNullOrEmpty(fwVersion) ? "latest" : fwVersion; string downloadUrl = string.Empty; string version = string.Empty; @@ -500,11 +582,11 @@ private static async Task GetDownloadUrlAsync( { if (verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Trying to find {targetName} in {(isPreview ? "development" : "stable")} repository..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Trying to find {targetName} in {(isPreview ? "development" : "stable")} repository..."); } - HttpResponseMessage response = await _cloudsmithClient.GetAsync($"{repoName}/?query=name:^{targetName}$ version:^{fwVersionParam}$"); + HttpResponseMessage response = await s_cloudsmithClient.GetAsync($"{repoName}/?query=name:^{targetName}$ version:^{fwVersionParam}$"); string responseBody = await response.Content.ReadAsStringAsync(); @@ -518,21 +600,21 @@ private static async Task GetDownloadUrlAsync( { if (verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Not found"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine("Not found"); + OutputWriter.ForegroundColor = ConsoleColor.White; } // try now with community targets - repoName = _communityTargetsRepo; + repoName = CommunityTargetsRepo; if (verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Trying to find {targetName} in community targets repository..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Trying to find {targetName} in community targets repository..."); } - response = await _cloudsmithClient.GetAsync($"{repoName}/?query=name:^{targetName}$ version:^{fwVersionParam}$"); + response = await s_cloudsmithClient.GetAsync($"{repoName}/?query=name:^{targetName}$ version:^{fwVersionParam}$"); responseBody = await response.Content.ReadAsStringAsync(); } @@ -547,16 +629,16 @@ private static async Task GetDownloadUrlAsync( packageInfo = JsonConvert.DeserializeObject>(responseBody); // sanity check - if (packageInfo.Count() != 1) + if (packageInfo.Count != 1) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); if (verbosity > VerbosityLevel.Quiet) { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Several hits returned, expecting only one!"); - Console.Write("Please report this issue."); + OutputWriter.WriteLine($"Several hits returned, expecting only one!"); + OutputWriter.Write("Please report this issue."); return new DownloadUrlResult(string.Empty, string.Empty, ExitCodes.E9005); } @@ -589,14 +671,14 @@ private static async Task GetDownloadUrlAsync( { targetNotFound = true; - Console.WriteLine(""); + OutputWriter.WriteLine(""); if (verbosity > VerbosityLevel.Quiet) { - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"There's a mismatch in the target name. Requested '{targetName}' but got '{packageInfo.ElementAt(0).TargetName}'!"); - Console.Write("Please report this issue."); + OutputWriter.WriteLine($"There's a mismatch in the target name. Requested '{targetName}' but got '{packageInfo.ElementAt(0).TargetName}'!"); + OutputWriter.Write("Please report this issue."); return new DownloadUrlResult(string.Empty, string.Empty, ExitCodes.E9005); } @@ -617,21 +699,21 @@ private static async Task GetDownloadUrlAsync( { // can't find this target - Console.WriteLine(""); + OutputWriter.WriteLine(""); if (verbosity > VerbosityLevel.Quiet) { // output helpful message - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(""); - Console.WriteLine("*************************** ERROR **************************"); - Console.WriteLine("Couldn't find this target in our Cloudsmith repositories!"); - Console.WriteLine("To list the available targets use this option --listtargets."); - Console.WriteLine("************************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("*************************** ERROR **************************"); + OutputWriter.WriteLine("Couldn't find this target in our Cloudsmith repositories!"); + OutputWriter.WriteLine("To list the available targets use this option --listtargets."); + OutputWriter.WriteLine("************************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } return new DownloadUrlResult(string.Empty, string.Empty, ExitCodes.E9005); @@ -639,24 +721,24 @@ private static async Task GetDownloadUrlAsync( if (verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine($"OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } if (packageOutdated) { - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine(); + OutputWriter.ForegroundColor = ConsoleColor.DarkYellow; + OutputWriter.WriteLine(); - Console.WriteLine("******************************* WARNING ******************************"); - Console.WriteLine($"** This firmware package was released at {packageInfo.ElementAt(0).PackageDate.ToShortDateString()} **"); - Console.WriteLine("** The target it's probably outdated. **"); - Console.WriteLine("** Please check the current target names here: https://git.io/JyfuI **"); - Console.WriteLine("**********************************************************************"); + OutputWriter.WriteLine("******************************* WARNING ******************************"); + OutputWriter.WriteLine($"** This firmware package was released at {packageInfo.ElementAt(0).PackageDate.ToShortDateString()} **"); + OutputWriter.WriteLine("** The target it's probably outdated. **"); + OutputWriter.WriteLine("** Please check the current target names here: https://git.io/JyfuI **"); + OutputWriter.WriteLine("**********************************************************************"); - Console.WriteLine(); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(); + OutputWriter.ForegroundColor = ConsoleColor.White; } // set exposed property @@ -708,6 +790,7 @@ void IDisposable.Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); + GC.SuppressFinalize(this); } #endregion @@ -716,17 +799,17 @@ internal record struct DownloadUrlResult(string Url, string Version, ExitCodes O { } - private uint FindStartAddressInHexFile(string hexFilePath) + private static uint FindStartAddressInHexFile(string hexFilePath) { uint address = 0; // find out what's the block start // do this by reading the HEX format file... - var textLines = File.ReadAllLines(hexFilePath); + string[] textLines = File.ReadAllLines(hexFilePath); // ... and decoding the start address - var addressRecord = textLines.FirstOrDefault(); + string addressRecord = textLines.FirstOrDefault(); string startAddress = string.Empty; // 1st line can be either: @@ -796,7 +879,7 @@ private void FindBooterStartAddress() } // find out what's the booter block start - BooterStartAddress = FindStartAddressInHexFile(NanoBooterFile); + BooterStartAddress = FirmwarePackage.FindStartAddressInHexFile(NanoBooterFile); } private void FindClrStartAddress() @@ -808,7 +891,7 @@ private void FindClrStartAddress() } // find out what's the CLR block start - ClrStartAddress = FindStartAddressInHexFile(NanoClrFile); + ClrStartAddress = FirmwarePackage.FindStartAddressInHexFile(NanoClrFile); } internal void PostProcessDownloadAndExtract() diff --git a/nanoFirmwareFlasher.Library/JLinkCli.cs b/nanoFirmwareFlasher.Library/JLinkCli.cs index a7d221a9..d50b00f3 100644 --- a/nanoFirmwareFlasher.Library/JLinkCli.cs +++ b/nanoFirmwareFlasher.Library/JLinkCli.cs @@ -92,15 +92,15 @@ public ExitCodes ExecuteMassErase(string probeId) { if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Mass erase device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Mass erase device..."); } - var cliOutput = RunJLinkCLI(Path.Combine(CmdFilesDir, "erase_gg11.jlink")); + string cliOutput = RunJLinkCLI(Path.Combine(CmdFilesDir, "erase_gg11.jlink")); if (!cliOutput.Contains("Erasing done.")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -109,17 +109,17 @@ public ExitCodes ExecuteMassErase(string probeId) if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } ShowCLIOutput(cliOutput); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -138,7 +138,7 @@ public ExitCodes ExecuteFlashBinFiles( { List shadowFiles = []; - var processFileResult = ProcessFilePaths( + ExitCodes processFileResult = ProcessFilePaths( files, shadowFiles); @@ -187,7 +187,7 @@ public ExitCodes ExecuteFlashBinFiles( // erase flash if (DoMassErase) { - var eraseResult = ExecuteMassErase(probeId); + ExitCodes eraseResult = ExecuteMassErase(probeId); if (eraseResult != ExitCodes.OK) { @@ -200,13 +200,13 @@ public ExitCodes ExecuteFlashBinFiles( if (Verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Flashing device..."); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine("Flashing device..."); } // program BIN file(s) @@ -217,20 +217,20 @@ public ExitCodes ExecuteFlashBinFiles( { if (Verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"{Path.GetFileName(binFile)} @ {addresses.ElementAt(index)}"); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.WriteLine($"{Path.GetFileName(binFile)} @ {addresses.ElementAt(index)}"); } // compose JLink command file - var jlinkCmdContent = FlashSingleFileCommandTemplate.Replace(FilePathToken, binFile).Replace(FlashAddressToken, addresses.ElementAt(index++)); - var jlinkCmdFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.jlink"); + string jlinkCmdContent = FlashSingleFileCommandTemplate.Replace(FilePathToken, binFile).Replace(FlashAddressToken, addresses.ElementAt(index++)); + string jlinkCmdFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.jlink"); // create file - var jlinkCmdFile = File.CreateText(jlinkCmdFilePath); + StreamWriter jlinkCmdFile = File.CreateText(jlinkCmdFilePath); jlinkCmdFile.Write(jlinkCmdContent); jlinkCmdFile.Close(); - var cliOutput = RunJLinkCLI(jlinkCmdFilePath); + string cliOutput = RunJLinkCLI(jlinkCmdFilePath); // OK to delete the JLink command file File.Delete(jlinkCmdFilePath); @@ -240,20 +240,20 @@ public ExitCodes ExecuteFlashBinFiles( { warningPromptShown = true; - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; if (Verbosity == VerbosityLevel.Normal) { - Console.WriteLine(); + OutputWriter.WriteLine(); } - Console.WriteLine(""); - Console.WriteLine("******************* WARNING *********************"); - Console.WriteLine("Skip flashing. Contents already match the update."); - Console.WriteLine("*************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************* WARNING *********************"); + OutputWriter.WriteLine("Skip flashing. Contents already match the update."); + OutputWriter.WriteLine("*************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else if (!(cliOutput.Contains("Flash download: Program & Verify") && cliOutput.Contains("O.K."))) @@ -270,17 +270,17 @@ public ExitCodes ExecuteFlashBinFiles( { if (!warningPromptShown) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("Flashing completed..."); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("Flashing completed..."); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -297,7 +297,7 @@ public ExitCodes ExecuteFlashHexFiles( { List shadowFiles = []; - var processFileResult = ProcessFilePaths( + ExitCodes processFileResult = ProcessFilePaths( files, shadowFiles); @@ -309,7 +309,7 @@ public ExitCodes ExecuteFlashHexFiles( // erase flash if (DoMassErase) { - var eraseResult = ExecuteMassErase(probeId); + ExitCodes eraseResult = ExecuteMassErase(probeId); if (eraseResult != ExitCodes.OK) { @@ -322,13 +322,13 @@ public ExitCodes ExecuteFlashHexFiles( if (Verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Flashing device..."); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine("Flashing device..."); } // program HEX file(s) @@ -338,28 +338,28 @@ public ExitCodes ExecuteFlashHexFiles( { if (Verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"{Path.GetFileName(hexFile)}"); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.WriteLine($"{Path.GetFileName(hexFile)}"); } listOfFiles.AppendLine($"LoadFile {hexFile}"); } // compose JLink command file - var jlinkCmdContent = FlashMultipleFilesCommandTemplate.Replace( + string jlinkCmdContent = FlashMultipleFilesCommandTemplate.Replace( LoadFileListToken, listOfFiles.ToString()); - var jlinkCmdFilePath = Path.Combine( + string jlinkCmdFilePath = Path.Combine( Path.GetTempPath(), $"{Guid.NewGuid()}.jlink"); // create file - var jlinkCmdFile = File.CreateText(jlinkCmdFilePath); + StreamWriter jlinkCmdFile = File.CreateText(jlinkCmdFilePath); jlinkCmdFile.Write(jlinkCmdContent); jlinkCmdFile.Close(); - var cliOutput = RunJLinkCLI(jlinkCmdFilePath); + string cliOutput = RunJLinkCLI(jlinkCmdFilePath); // OK to delete the JLink command file File.Delete(jlinkCmdFilePath); @@ -371,20 +371,20 @@ public ExitCodes ExecuteFlashHexFiles( { warningPromptShown = true; - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; if (Verbosity == VerbosityLevel.Normal) { - Console.WriteLine(); + OutputWriter.WriteLine(); } - Console.WriteLine(""); - Console.WriteLine("******************* WARNING *********************"); - Console.WriteLine("Skip flashing. Contents already match the update."); - Console.WriteLine("*************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************* WARNING *********************"); + OutputWriter.WriteLine("Skip flashing. Contents already match the update."); + OutputWriter.WriteLine("*************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else if (!(cliOutput.Contains("Flash download: Program & Verify") && cliOutput.Contains("O.K."))) @@ -400,17 +400,17 @@ public ExitCodes ExecuteFlashHexFiles( { if (!warningPromptShown) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("Flashing completed..."); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("Flashing completed..."); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -420,17 +420,17 @@ internal void ShowCLIOutput(string cliOutput) // show CLI output, if verbosity is diagnostic if (Verbosity == VerbosityLevel.Diagnostic) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(">>>>>>>>"); - Console.WriteLine(cliOutput); - Console.WriteLine(">>>>>>>>"); - Console.WriteLine(); - Console.WriteLine(); + OutputWriter.WriteLine(); + OutputWriter.WriteLine(); + OutputWriter.WriteLine(">>>>>>>>"); + OutputWriter.WriteLine(cliOutput); + OutputWriter.WriteLine(">>>>>>>>"); + OutputWriter.WriteLine(); + OutputWriter.WriteLine(); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } @@ -508,7 +508,7 @@ private static ExitCodes ProcessFilePaths( foreach (string binFile in files) { // make sure path is absolute - var binFilePath = Utilities.MakePathAbsolute( + string binFilePath = Utilities.MakePathAbsolute( Environment.CurrentDirectory, binFile); @@ -521,7 +521,7 @@ private static ExitCodes ProcessFilePaths( if (!binFilePath.IsNormalized(NormalizationForm.FormD) || binFilePath.Contains(' ')) { - var tempFile = Path.Combine( + string tempFile = Path.Combine( Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Machine), Path.GetFileName(binFilePath)); diff --git a/nanoFirmwareFlasher.Library/JLinkDevice.cs b/nanoFirmwareFlasher.Library/JLinkDevice.cs index 35d26f25..951c3f2f 100644 --- a/nanoFirmwareFlasher.Library/JLinkDevice.cs +++ b/nanoFirmwareFlasher.Library/JLinkDevice.cs @@ -58,7 +58,7 @@ public JLinkDevice(string probeId = null) if (string.IsNullOrEmpty(probeId)) { // no probe id supplied, list available - var jlinkDevices = ListDevices(); + List jlinkDevices = ListDevices(); if (jlinkDevices.Count > 0) { @@ -78,11 +78,11 @@ public JLinkDevice(string probeId = null) } // try to connect to J-Link ID device to check availability - var cliOutput = RunJLinkCLI(Path.Combine(CmdFilesDir, "test_connection.jlink")); + string cliOutput = RunJLinkCLI(Path.Combine(CmdFilesDir, "test_connection.jlink")); if (cliOutput.Contains("Error")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -90,7 +90,7 @@ public JLinkDevice(string probeId = null) } // parse the output to fill in the details - var match = Regex.Match(cliOutput, "(Firmware: )(?.*)$", RegexOptions.Multiline); + Match match = Regex.Match(cliOutput, "(Firmware: )(?.*)$", RegexOptions.Multiline); if (match.Success) { if (match.Groups["firmware"].Captures.Count > 0) @@ -134,7 +134,7 @@ public ExitCodes MassErase() /// A collection of connected Silabs Giant Gecko devices. public static List ListDevices() { - var cliOutput = ExecuteListDevices(); + string cliOutput = ExecuteListDevices(); // (successful) output from the above is // @@ -146,7 +146,7 @@ public static List ListDevices() const string regexPattern = @"(?<=Connection: USB, Serial number:\s)(?\d+)"; var myRegex1 = new Regex(regexPattern, RegexOptions.Multiline); - var jlinkMatches = myRegex1.Matches(cliOutput); + MatchCollection jlinkMatches = myRegex1.Matches(cliOutput); if (jlinkMatches.Count == 0) { diff --git a/nanoFirmwareFlasher.Library/JLinkFirmware.cs b/nanoFirmwareFlasher.Library/JLinkFirmware.cs index eb229a99..9f956d37 100644 --- a/nanoFirmwareFlasher.Library/JLinkFirmware.cs +++ b/nanoFirmwareFlasher.Library/JLinkFirmware.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.IO; @@ -25,10 +23,10 @@ public JLinkFirmware( { } - internal new System.Threading.Tasks.Task DownloadAndExtractAsync() + internal new System.Threading.Tasks.Task DownloadAndExtractAsync(string archiveDirectoryPath) { // perform download and extract - return base.DownloadAndExtractAsync(); + return base.DownloadAndExtractAsync(archiveDirectoryPath); } } } diff --git a/nanoFirmwareFlasher.Library/JLinkOperations.cs b/nanoFirmwareFlasher.Library/JLinkOperations.cs index eba76718..f7c0f270 100644 --- a/nanoFirmwareFlasher.Library/JLinkOperations.cs +++ b/nanoFirmwareFlasher.Library/JLinkOperations.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; @@ -21,6 +19,8 @@ public class JLinkOperations /// Name of the target to update. /// Firmware version to update to. /// Set to to use preview version to update. + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and never be downloaded. /// Set to to force download of firmware package. /// Path to application to update along with the firmware update. /// Flash address to use when deploying an aplication. @@ -32,6 +32,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( string targetName, string fwVersion, bool preview, + string archiveDirectoryPath, bool updateFw, string applicationPath, string deploymentAddress, @@ -60,7 +61,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // need to download update package? if (updateFw) { - operationResult = await firmware.DownloadAndExtractAsync(); + operationResult = await firmware.DownloadAndExtractAsync(archiveDirectoryPath); if (operationResult != ExitCodes.OK) { return operationResult; @@ -101,7 +102,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } } - var connectedSilabsJLinkDevices = JLinkDevice.ListDevices(); + List connectedSilabsJLinkDevices = JLinkDevice.ListDevices(); if (!connectedSilabsJLinkDevices.Any()) { @@ -122,29 +123,29 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"Connected to J-Link device with ID {jlinkDevice.ProbeId}"); - Console.WriteLine(""); - Console.WriteLine($"{jlinkDevice}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.WriteLine($"Connected to J-Link device with ID {jlinkDevice.ProbeId}"); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"{jlinkDevice}"); + OutputWriter.ForegroundColor = ConsoleColor.White; } if (verbosity == VerbosityLevel.Diagnostic) { - Console.WriteLine($"Firmware: {jlinkDevice.Firmare}"); - Console.WriteLine($"Hardware: {jlinkDevice.Hardware}"); + OutputWriter.WriteLine($"Firmware: {jlinkDevice.Firmare}"); + OutputWriter.WriteLine($"Hardware: {jlinkDevice.Hardware}"); } if (fitCheck) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("Image fit check for Silabs devices is not supported at this time."); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("Image fit check for Silabs devices is not supported at this time."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } operationResult = ExitCodes.OK; @@ -190,13 +191,13 @@ public static ExitCodes MassErase( if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine($"Connected to J-Link device with ID {jlinkDevice.ProbeId}"); + OutputWriter.WriteLine($"Connected to J-Link device with ID {jlinkDevice.ProbeId}"); } if (verbosity == VerbosityLevel.Diagnostic) { - Console.WriteLine($"Firmware: {jlinkDevice.Firmare}"); - Console.WriteLine($"Hardware: {jlinkDevice.Hardware}"); + OutputWriter.WriteLine($"Firmware: {jlinkDevice.Firmare}"); + OutputWriter.WriteLine($"Hardware: {jlinkDevice.Hardware}"); } // set verbosity diff --git a/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs b/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs index 95b9d9e2..7fea77ac 100644 --- a/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs +++ b/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs @@ -1,15 +1,13 @@ -//// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -//// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using nanoFramework.Tools.Debugger; using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using nanoFramework.Tools.Debugger; namespace nanoFramework.Tools.FirmwareFlasher { @@ -50,7 +48,7 @@ public ObservableCollection ListDevices(bool getDeviceDetails) if (getDeviceDetails) { // get device details - foreach (var device in _serialDebuggerPort.NanoFrameworkDevices) + foreach (NanoDeviceBase device in _serialDebuggerPort.NanoFrameworkDevices) { _ = device.DebugEngine.Connect( false, @@ -121,12 +119,12 @@ public ExitCodes GetDeviceDetails( // we have to have a valid device info if (nanoDevice.DeviceInfo.Valid) { - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine(""); - Console.WriteLine($"{nanoDevice.DeviceInfo}"); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"{nanoDevice.DeviceInfo}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -142,12 +140,12 @@ public ExitCodes GetDeviceDetails( // we have to have a valid device info if (nanoDevice.DebugEngine.TargetInfo != null) { - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine(""); - Console.WriteLine($"{nanoDevice.DebugEngine.TargetInfo}"); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"{nanoDevice.DebugEngine.TargetInfo}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -170,6 +168,9 @@ public ExitCodes GetDeviceDetails( /// /// Serial port name where the device is connected to. /// Firmware version to update to. + /// Only show which firmware to use; do not deploy anything to the device. + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and never be downloaded. /// Path to CLR file to use for firmware update. /// Set verbosity level of progress and error messages. /// The with the operation result. @@ -187,6 +188,8 @@ public ExitCodes GetDeviceDetails( public async Task UpdateDeviceClrAsync( string serialPort, string fwVersion, + bool showFwOnly, + string archiveDirectoryPath, string clrFile, VerbosityLevel verbosity = VerbosityLevel.Quiet) { @@ -197,8 +200,8 @@ public async Task UpdateDeviceClrAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Getting details from nano device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Getting details from nano device..."); } bool updateCLRfile = !string.IsNullOrEmpty(clrFile); @@ -241,25 +244,33 @@ public async Task UpdateDeviceClrAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + + OutputWriter.WriteLine($"Connected to nano device: {nanoDevice.Description}"); + OutputWriter.WriteLine(""); - Console.WriteLine($"Connected to nano device: {nanoDevice.Description}"); - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.White; + } - Console.ForegroundColor = ConsoleColor.White; + if (showFwOnly) + { + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Connected nanoDevice uses target '{nanoDevice.TargetName}'; platform is {nanoDevice.Platform}."); + OutputWriter.WriteLine(""); + return ExitCodes.OK; } // local file will be flashed straight away @@ -290,10 +301,10 @@ public async Task UpdateDeviceClrAsync( { if (verbosity > VerbosityLevel.Normal) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Launching nanoBooter..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Launching nanoBooter..."); } booterLaunched = nanoDevice.ConnectToNanoBooter(); @@ -304,7 +315,7 @@ public async Task UpdateDeviceClrAsync( if (currentClrVersion != null && nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); throw new NanoDeviceOperationFailedException("The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); } @@ -312,9 +323,9 @@ public async Task UpdateDeviceClrAsync( if (verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } catch @@ -331,14 +342,14 @@ public async Task UpdateDeviceClrAsync( nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) { // get address for CLR block expected by device - var clrAddress = nanoDevice.GetCLRStartAddress(); + int clrAddress = nanoDevice.GetCLRStartAddress(); await Task.Yield(); if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Starting CLR update with local file..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Starting CLR update with local file..."); } try @@ -354,17 +365,17 @@ public async Task UpdateDeviceClrAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } else { if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("FAILED!"); + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("FAILED!"); return ExitCodes.E2002; } @@ -375,17 +386,17 @@ public async Task UpdateDeviceClrAsync( // try to reboot target if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Rebooting..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Rebooting..."); } nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } return ExitCodes.OK; @@ -416,11 +427,11 @@ public async Task UpdateDeviceClrAsync( else { // get firmware package - var fwPackage = FirmwarePackageFactory.GetFirmwarePackage( + FirmwarePackage fwPackage = FirmwarePackageFactory.GetFirmwarePackage( nanoDevice, fwVersion); - var downloadResult = await fwPackage.DownloadAndExtractAsync(); + ExitCodes downloadResult = await fwPackage.DownloadAndExtractAsync(archiveDirectoryPath); if (downloadResult == ExitCodes.OK) { @@ -454,13 +465,13 @@ public async Task UpdateDeviceClrAsync( { if (verbosity > VerbosityLevel.Normal) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write("Launching nanoBooter..."); - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.Write("Launching nanoBooter..."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } attemptToLaunchBooter = nanoDevice.ConnectToNanoBooter(); @@ -471,7 +482,7 @@ public async Task UpdateDeviceClrAsync( if (currentClrVersion != null && nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); throw new NanoDeviceOperationFailedException("The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); } @@ -479,9 +490,9 @@ public async Task UpdateDeviceClrAsync( if (verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } catch @@ -498,7 +509,7 @@ public async Task UpdateDeviceClrAsync( nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) { // get address for CLR block expected by device - var clrAddress = nanoDevice.GetCLRStartAddress(); + int clrAddress = nanoDevice.GetCLRStartAddress(); // compare with address on the fw packages if (clrAddress != @@ -512,8 +523,8 @@ public async Task UpdateDeviceClrAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Starting update to CLR v{fwPackage.Version}..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Starting update to CLR v{fwPackage.Version}..."); } try @@ -529,9 +540,9 @@ public async Task UpdateDeviceClrAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } @@ -540,17 +551,17 @@ public async Task UpdateDeviceClrAsync( // try to reboot target if (verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Rebooting..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Rebooting..."); } nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); if (verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } return ExitCodes.OK; @@ -577,13 +588,13 @@ public async Task UpdateDeviceClrAsync( { if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Nothing to update as device is already running the requested version."); - Console.WriteLine(""); + OutputWriter.WriteLine("Nothing to update as device is already running the requested version."); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } // done here @@ -630,8 +641,8 @@ public ExitCodes DeployApplication( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Getting details from nano device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write($"Getting details from nano device..."); } NanoDeviceBase nanoDevice = null; @@ -651,14 +662,14 @@ public ExitCodes DeployApplication( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } if (nanoDevice.DebugEngine.Connect( @@ -668,7 +679,7 @@ public ExitCodes DeployApplication( { if (verbosity >= VerbosityLevel.Normal) { - Console.Write($"Deploying managed application..."); + OutputWriter.Write($"Deploying managed application..."); } if (!nanoDevice.DeployBinaryFile( @@ -679,8 +690,8 @@ public ExitCodes DeployApplication( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("FAILED!"); + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("FAILED!"); return ExitCodes.E2002; } @@ -689,21 +700,21 @@ public ExitCodes DeployApplication( { if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } // try to reboot target if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Rebooting..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Rebooting..."); } // reboot device @@ -711,9 +722,9 @@ public ExitCodes DeployApplication( if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } @@ -769,7 +780,7 @@ private bool ReadDetailsFromDevice( #if DEBUG catch (Exception ex) { - Console.WriteLine($"Failed to add device: {ex.Message}"); + OutputWriter.WriteLine($"Failed to add device: {ex.Message}"); return false; } @@ -803,7 +814,7 @@ private bool ReadDetailsFromDevice( try { // get device info - var deviceInfo = nanoDevice.GetDeviceInfo(true); + INanoFrameworkDeviceInfo deviceInfo = nanoDevice.GetDeviceInfo(true); // we have to have a valid device info if (deviceInfo.Valid) @@ -828,7 +839,7 @@ private bool ReadDetailsFromDevice( try { // get device info - var deviceInfo = nanoDevice.DebugEngine?.TargetInfo; + Debugger.WireProtocol.TargetInfo deviceInfo = nanoDevice.DebugEngine?.TargetInfo; // we have to have a valid device info if (deviceInfo != null) diff --git a/nanoFirmwareFlasher.Library/OutputWriter.cs b/nanoFirmwareFlasher.Library/OutputWriter.cs new file mode 100644 index 00000000..c6ee665b --- /dev/null +++ b/nanoFirmwareFlasher.Library/OutputWriter.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; + +namespace nanoFramework.Tools.FirmwareFlasher +{ + /// + /// Writes output to the console. In unit tests the output can be directed to + /// a per-test implementation to make the library and tool testable. + /// Parallel running of unit tests is supported in that case. + /// + public static class OutputWriter + { + private static readonly AsyncLocal s_outputWriter = new(); + + #region Properties + /// + /// Get or set the foreground color + /// + public static ConsoleColor ForegroundColor + { + get => s_outputWriter.Value?.ForegroundColor ?? Console.ForegroundColor; + set + { + if (s_outputWriter.Value is null) + { + Console.ForegroundColor = value; + } + else + { + s_outputWriter.Value.ForegroundColor = value; + } + } + } + #endregion + + #region Methods + /// + /// Write a line to the standard output. + /// + /// Text to write + public static void Write(string text) + { + if (s_outputWriter.Value is null) + { + Console.Write(text); + } + else + { + s_outputWriter.Value.Write(text); + } + } + + /// + /// Write a line to the standard output. + /// + /// Text to write + public static void WriteLine(string text = null) + { + if (s_outputWriter.Value is null) + { + if (text is null) + { + Console.WriteLine(); + } + else + { + Console.WriteLine(text); + } + } + else + { + if (text is not null) + { + s_outputWriter.Value.Write(text); + } + s_outputWriter.Value.Write(Environment.NewLine); + } + } + #endregion + + #region Test support + /// + /// Assign an alternative output writer for the current execution + /// context and all async tasks started from this context. + /// + /// Writer to use instead of the console. Pass null + /// to start using the console again. + internal static void SetOutputWriter(IOutputWriter writer) + { + s_outputWriter.Value = writer; + } + + /// + /// Interface to be implemented by test software to capture the output of the library and tool + /// + internal interface IOutputWriter + { + /// + /// Get or set the foreground color. + /// + ConsoleColor ForegroundColor + { + get; set; + } + + /// + /// Write text to the standard output. + /// + /// Text to write; can be null. + void Write(string text); + } + #endregion + } +} diff --git a/nanoFirmwareFlasher.Library/Properties/AssemblyInfo.cs b/nanoFirmwareFlasher.Library/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..20c5b261 --- /dev/null +++ b/nanoFirmwareFlasher.Library/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("nanoFirmwareFlasher.Tests")] diff --git a/nanoFirmwareFlasher.Library/SilinkCli.cs b/nanoFirmwareFlasher.Library/SilinkCli.cs index 3a2c8bda..6aa9ecc6 100644 --- a/nanoFirmwareFlasher.Library/SilinkCli.cs +++ b/nanoFirmwareFlasher.Library/SilinkCli.cs @@ -44,13 +44,13 @@ public static ExitCodes SetVcpBaudRate( if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"Setting VCP baud rate in {OSPlatform.OSX} is not supported."); + OutputWriter.WriteLine($"Setting VCP baud rate in {OSPlatform.OSX} is not supported."); - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); return ExitCodes.E8002; } @@ -58,15 +58,15 @@ public static ExitCodes SetVcpBaudRate( // store baud rate value int targetBaudRate = baudRate == 0 ? DefaultBaudRate : baudRate; - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; // launch silink if (verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine("Launching silink..."); + OutputWriter.WriteLine("Launching silink..."); } - var silinkCli = RunSilinkCLI(Path.Combine(probeId)); + Process silinkCli = RunSilinkCLI(Path.Combine(probeId)); var silinkSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint silinkEndPoint = new(IPAddress.Parse("127.0.0.1"), SilinkAdminPort); @@ -75,7 +75,7 @@ public static ExitCodes SetVcpBaudRate( { if (verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine("Connecting to admin console..."); + OutputWriter.WriteLine("Connecting to admin console..."); } silinkSocket.Connect(silinkEndPoint); @@ -88,7 +88,7 @@ public static ExitCodes SetVcpBaudRate( if (verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine("Querying current config..."); + OutputWriter.WriteLine("Querying current config..."); } Thread.Sleep(250); @@ -96,11 +96,11 @@ public static ExitCodes SetVcpBaudRate( buffer = new byte[1024]; int receiveCount = silinkSocket.Receive(buffer, 0, buffer.Length, 0); - var currentConfig = Encoding.Default.GetString(buffer, 0, receiveCount); + string currentConfig = Encoding.Default.GetString(buffer, 0, receiveCount); if (verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine($"{currentConfig}"); + OutputWriter.WriteLine($"{currentConfig}"); } if (!string.IsNullOrEmpty(currentConfig)) @@ -109,7 +109,7 @@ public static ExitCodes SetVcpBaudRate( const string regexPattern = "(?:Stored port speed : )(?'baudrate'\\d+)"; var myRegex1 = new Regex(regexPattern, RegexOptions.Multiline); - var currentVcomConfig = myRegex1.Match(currentConfig); + Match currentVcomConfig = myRegex1.Match(currentConfig); if (currentVcomConfig.Success) { @@ -118,7 +118,7 @@ public static ExitCodes SetVcpBaudRate( { if (verbosity >= VerbosityLevel.Detailed) { - Console.WriteLine("VCP baud rate it's correct! Nothing to do here."); + OutputWriter.WriteLine("VCP baud rate it's correct! Nothing to do here."); } return ExitCodes.OK; @@ -129,11 +129,11 @@ public static ExitCodes SetVcpBaudRate( if (verbosity == VerbosityLevel.Normal) { - Console.Write("Trying to set VCP baud rate..."); + OutputWriter.Write("Trying to set VCP baud rate..."); } else if (verbosity > VerbosityLevel.Normal) { - Console.WriteLine("Trying to set VCP baud rate..."); + OutputWriter.WriteLine("Trying to set VCP baud rate..."); } Thread.Sleep(250); @@ -147,27 +147,27 @@ public static ExitCodes SetVcpBaudRate( buffer = new byte[1024]; receiveCount = silinkSocket.Receive(buffer, 0, buffer.Length, 0); - var opResult = Encoding.Default.GetString(buffer, 0, receiveCount); + string opResult = Encoding.Default.GetString(buffer, 0, receiveCount); if (verbosity >= VerbosityLevel.Diagnostic) { - Console.WriteLine($"{opResult}"); + OutputWriter.WriteLine($"{opResult}"); } if (opResult.Contains($"Baudrate set to {targetBaudRate} bps")) { if (verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else if (verbosity > VerbosityLevel.Normal) { - Console.WriteLine("Success!"); + OutputWriter.WriteLine("Success!"); } return ExitCodes.OK; @@ -176,22 +176,22 @@ public static ExitCodes SetVcpBaudRate( { if (verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("FAILED!"); + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("FAILED!"); - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.White; - Console.WriteLine($"{opResult.Replace("PK> ", "")}"); + OutputWriter.WriteLine($"{opResult.Replace("PK> ", "")}"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); } else if (verbosity > VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("FAILED!"); - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("FAILED!"); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); } return ExitCodes.E8002; @@ -200,13 +200,13 @@ public static ExitCodes SetVcpBaudRate( } catch (Exception ex) { - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Exception occurred: {ex.Message}"); + OutputWriter.WriteLine($"Exception occurred: {ex.Message}"); - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); return ExitCodes.E8002; } diff --git a/nanoFirmwareFlasher.Library/Stm32Firmware.cs b/nanoFirmwareFlasher.Library/Stm32Firmware.cs index 2f1b80ce..1128cc55 100644 --- a/nanoFirmwareFlasher.Library/Stm32Firmware.cs +++ b/nanoFirmwareFlasher.Library/Stm32Firmware.cs @@ -1,13 +1,11 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using nanoFramework.Tools.Debugger; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using nanoFramework.Tools.Debugger; namespace nanoFramework.Tools.FirmwareFlasher { @@ -47,10 +45,10 @@ public Stm32Firmware(NanoDeviceBase nanoDevice) : base(nanoDevice) } - internal new System.Threading.Tasks.Task DownloadAndExtractAsync() + internal new System.Threading.Tasks.Task DownloadAndExtractAsync(string archiveDirectoryPath) { // perform download and extract - return base.DownloadAndExtractAsync(); + return base.DownloadAndExtractAsync(archiveDirectoryPath); } } } diff --git a/nanoFirmwareFlasher.Library/Stm32Operations.cs b/nanoFirmwareFlasher.Library/Stm32Operations.cs index 32583aee..56cd6ce6 100644 --- a/nanoFirmwareFlasher.Library/Stm32Operations.cs +++ b/nanoFirmwareFlasher.Library/Stm32Operations.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; @@ -23,6 +21,8 @@ public class Stm32Operations /// The name of the target. /// The firmware version to send. /// Whether preview packages should be used. + /// Path to the archive directory where all targets are located. Pass null if there is no archive. + /// If not null, the package will always be retrieved from the archive and never be downloaded. /// Update firmware to latest version. /// Path to the directory where the files are located. /// The start memory address. @@ -36,6 +36,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( string targetName, string fwVersion, bool preview, + string archiveDirectoryPath, bool updateFw, string applicationPath, string deploymentAddress, @@ -66,7 +67,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( // need to download update package? if (updateFw) { - var operationResult = await firmware.DownloadAndExtractAsync(); + ExitCodes operationResult = await firmware.DownloadAndExtractAsync(archiveDirectoryPath); if (operationResult != ExitCodes.OK) { return operationResult; @@ -107,8 +108,8 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( } } - var connectedStDfuDevices = StmDfuDevice.ListDevices(); - var connectedStJtagDevices = StmJtagDevice.ListDevices(); + List<(string serial, string device)> connectedStDfuDevices = StmDfuDevice.ListDevices(); + List connectedStJtagDevices = StmJtagDevice.ListDevices(); if (updateInterface != Interface.None) { @@ -179,23 +180,23 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( if (fitCheck) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("It's not possible to perform image fit check for devices connected with DFU"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("It's not possible to perform image fit check for devices connected with DFU"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } if (verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"Connected to DFU device with ID {dfuDevice.DfuId}"); - Console.WriteLine(""); - Console.WriteLine($"{dfuDevice}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine($"Connected to DFU device with ID {dfuDevice.DfuId}"); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"{dfuDevice}"); + OutputWriter.ForegroundColor = ConsoleColor.White; } // set verbosity @@ -255,12 +256,12 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); - Console.WriteLine(""); - Console.WriteLine($"{jtagDevice}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"{jtagDevice}"); + OutputWriter.ForegroundColor = ConsoleColor.White; } if (fitCheck) @@ -309,16 +310,16 @@ private static void PerformTargetCheck(string target, StmJtagDevice jtagDevice) string.IsNullOrEmpty(jtagDevice.BoardName) || jtagDevice.BoardName == "--") { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("******************************************* WARNING ************************ *************"); - Console.WriteLine("It wasn't possible to validate if the firmware image that's about to be used works on the"); - Console.WriteLine($"target connected. But this doesn't necessarily mean that it won't work."); - Console.WriteLine("******************************************************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************************************* WARNING ************************ *************"); + OutputWriter.WriteLine("It wasn't possible to validate if the firmware image that's about to be used works on the"); + OutputWriter.WriteLine($"target connected. But this doesn't necessarily mean that it won't work."); + OutputWriter.WriteLine("******************************************************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } else { @@ -327,16 +328,16 @@ private static void PerformTargetCheck(string target, StmJtagDevice jtagDevice) if (!targetName.Contains(boardName)) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("******************************************* WARNING ***************************************"); - Console.WriteLine("It seems that the firmware image that's about to be used isn't the appropriate one for the"); - Console.WriteLine($"target connected. But this doesn't necessarily mean that it won't work."); - Console.WriteLine("*******************************************************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("******************************************* WARNING ***************************************"); + OutputWriter.WriteLine("It seems that the firmware image that's about to be used isn't the appropriate one for the"); + OutputWriter.WriteLine($"target connected. But this doesn't necessarily mean that it won't work."); + OutputWriter.WriteLine("*******************************************************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } } @@ -364,7 +365,7 @@ public static ExitCodes ResetMcu( if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); + OutputWriter.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); } // set verbosity @@ -397,7 +398,7 @@ public static ExitCodes MassErase( if (verbosity >= VerbosityLevel.Normal) { - Console.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); + OutputWriter.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); } // set verbosity @@ -417,7 +418,7 @@ public static ExitCodes InstallDfuDrivers(VerbosityLevel verbosityLevel) { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Console.WriteLine("No driver installation needed on MacOS"); + OutputWriter.WriteLine("No driver installation needed on MacOS"); return ExitCodes.OK; } @@ -445,9 +446,9 @@ public static ExitCodes InstallDfuDrivers(VerbosityLevel verbosityLevel) if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } return ExitCodes.OK; @@ -455,11 +456,11 @@ public static ExitCodes InstallDfuDrivers(VerbosityLevel verbosityLevel) if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write("Calling installer for STM32 DFU drivers..."); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.Write("Calling installer for STM32 DFU drivers..."); } - var infPath = Path.Combine(Utilities.ExecutingPath, "stlink\\DFU_Driver\\Driver\\STM32Bootloader.inf"); + string infPath = Path.Combine(Utilities.ExecutingPath, "stlink\\DFU_Driver\\Driver\\STM32Bootloader.inf"); Process installerCli = new Process { @@ -505,9 +506,9 @@ public static ExitCodes InstallDfuDrivers(VerbosityLevel verbosityLevel) if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } // always true as the drivers will be installed depending on user answering yes to elevate prompt @@ -518,8 +519,8 @@ public static ExitCodes InstallDfuDrivers(VerbosityLevel verbosityLevel) { if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("ERROR"); + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("ERROR"); } throw new Exception(ex.Message); @@ -536,23 +537,23 @@ public static ExitCodes InstallJtagDrivers(VerbosityLevel verbosityLevel) { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Console.WriteLine("No driver installation needed on MacOS"); + OutputWriter.WriteLine("No driver installation needed on MacOS"); return ExitCodes.OK; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Driver installation for JTAG no supported on Linux. Please refer to the STM32 website to get the specific drivers."); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine("Driver installation for JTAG no supported on Linux. Please refer to the STM32 website to get the specific drivers."); return ExitCodes.OK; } - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write("Calling installer for STM32 JTAG drivers..."); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.Write("Calling installer for STM32 JTAG drivers..."); + OutputWriter.ForegroundColor = ConsoleColor.White; } try @@ -585,9 +586,9 @@ public static ExitCodes InstallJtagDrivers(VerbosityLevel verbosityLevel) if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("OK"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("OK"); + OutputWriter.ForegroundColor = ConsoleColor.White; } // always true as the drivers will be installed depending on user answering yes to elevate prompt @@ -598,9 +599,9 @@ public static ExitCodes InstallJtagDrivers(VerbosityLevel verbosityLevel) { if (verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("ERROR"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("ERROR"); + OutputWriter.ForegroundColor = ConsoleColor.White; } throw new Exception(ex.Message); diff --git a/nanoFirmwareFlasher.Library/StmDeviceBase.cs b/nanoFirmwareFlasher.Library/StmDeviceBase.cs index 47d3e331..e1f0caef 100644 --- a/nanoFirmwareFlasher.Library/StmDeviceBase.cs +++ b/nanoFirmwareFlasher.Library/StmDeviceBase.cs @@ -53,18 +53,18 @@ public static string RunSTM32ProgrammerCLI(string arguments) { if (!Utilities.ExecutingPath.IsNormalized(NormalizationForm.FormD)) { - Console.ForegroundColor = ConsoleColor.Red; - - Console.WriteLine(""); - Console.WriteLine("**************************** WARNING ****************************"); - Console.WriteLine("nanoff installation path contains diacritic chars!"); - Console.WriteLine("There are know issues executing some commands in this situation."); - Console.WriteLine("Recommend that the tool be installed in a path without those."); - Console.WriteLine("For a detailed explanation please visit https://git.io/JEcpK."); - Console.WriteLine("*****************************************************************"); - Console.WriteLine(""); - - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("**************************** WARNING ****************************"); + OutputWriter.WriteLine("nanoff installation path contains diacritic chars!"); + OutputWriter.WriteLine("There are know issues executing some commands in this situation."); + OutputWriter.WriteLine("Recommend that the tool be installed in a path without those."); + OutputWriter.WriteLine("For a detailed explanation please visit https://git.io/JEcpK."); + OutputWriter.WriteLine("*****************************************************************"); + OutputWriter.WriteLine(""); + + OutputWriter.ForegroundColor = ConsoleColor.White; } // done @@ -130,7 +130,7 @@ public static string GetErrorMessageFromSTM32CLI(string cliOutput) { var regEx = new Regex(@"Error: (?.+).", RegexOptions.IgnoreCase); - var match = regEx.Match(cliOutput); + Match match = regEx.Match(cliOutput); if (match.Success) { @@ -157,20 +157,20 @@ public void ShowCLIOutput(string cliOutput) // show CLI output, if verbosity is diagnostic if (Verbosity == VerbosityLevel.Diagnostic) { - Console.WriteLine(">>>>>>>>"); - Console.WriteLine($"{cliOutput}"); - Console.WriteLine(">>>>>>>>"); + OutputWriter.WriteLine(">>>>>>>>"); + OutputWriter.WriteLine($"{cliOutput}"); + OutputWriter.WriteLine(">>>>>>>>"); } // show error message from CLI, if there is one if (!string.IsNullOrEmpty(_stCLIErrorMessage)) { // show error detail, if available - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(_stCLIErrorMessage); + OutputWriter.WriteLine(_stCLIErrorMessage); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } @@ -192,15 +192,15 @@ public ExitCodes ExecuteMassErase(string connectDetails) { if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Mass erase device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Mass erase device..."); } - var cliOutput = RunSTM32ProgrammerCLI($"-c {connectDetails} mode=UR -e all"); + string cliOutput = RunSTM32ProgrammerCLI($"-c {connectDetails} mode=UR -e all"); if (!cliOutput.Contains("Mass erase successfully achieved")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -209,15 +209,15 @@ public ExitCodes ExecuteMassErase(string connectDetails) if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } else { - Console.WriteLine(""); + OutputWriter.WriteLine(""); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -241,7 +241,7 @@ public ExitCodes ExecuteFlashHexFiles( // erase flash if (DoMassErase) { - var eraseResult = ExecuteMassErase(connectDetails); + ExitCodes eraseResult = ExecuteMassErase(connectDetails); if (eraseResult != ExitCodes.OK) { @@ -254,30 +254,30 @@ public ExitCodes ExecuteFlashHexFiles( if (Verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Flashing device..."); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine("Flashing device..."); } // program HEX file(s) foreach (string hexFile in files) { // make sure path is absolute - var hexFilePath = Utilities.MakePathAbsolute( + string hexFilePath = Utilities.MakePathAbsolute( Environment.CurrentDirectory, hexFile); if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"{Path.GetFileName(hexFile)}"); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine($"{Path.GetFileName(hexFile)}"); } - var cliOutput = RunSTM32ProgrammerCLI($"-c {connectDetails} -w \"{hexFilePath}\""); + string cliOutput = RunSTM32ProgrammerCLI($"-c {connectDetails} -w \"{hexFilePath}\""); if (!cliOutput.Contains("File download complete")) { @@ -289,16 +289,16 @@ public ExitCodes ExecuteFlashHexFiles( if (Verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("Flashing completed..."); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("Flashing completed..."); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -353,7 +353,7 @@ public ExitCodes ExecuteFlashBinFiles( // erase flash if (DoMassErase) { - var eraseResult = ExecuteMassErase(connectDetails); + ExitCodes eraseResult = ExecuteMassErase(connectDetails); if (eraseResult != ExitCodes.OK) { @@ -366,13 +366,13 @@ public ExitCodes ExecuteFlashBinFiles( if (Verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Flashing device..."); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine("Flashing device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine("Flashing device..."); } // program BIN file(s) @@ -380,17 +380,17 @@ public ExitCodes ExecuteFlashBinFiles( foreach (string binFile in files) { // make sure path is absolute - var binFilePath = Utilities.MakePathAbsolute( + string binFilePath = Utilities.MakePathAbsolute( Environment.CurrentDirectory, binFile); if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"{Path.GetFileName(binFilePath)} @ {addresses.ElementAt(index)}"); + OutputWriter.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.WriteLine($"{Path.GetFileName(binFilePath)} @ {addresses.ElementAt(index)}"); } - var cliOutput = RunSTM32ProgrammerCLI($"-c {connectDetails} mode=UR -w \"{binFilePath}\" {addresses.ElementAt(index++)}"); + string cliOutput = RunSTM32ProgrammerCLI($"-c {connectDetails} mode=UR -w \"{binFilePath}\" {addresses.ElementAt(index++)}"); if (!cliOutput.Contains("File download complete")) { @@ -402,16 +402,16 @@ public ExitCodes ExecuteFlashBinFiles( if (Verbosity == VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } else if (Verbosity >= VerbosityLevel.Detailed) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("Flashing completed..."); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine("Flashing completed..."); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } diff --git a/nanoFirmwareFlasher.Library/StmDfuDevice.cs b/nanoFirmwareFlasher.Library/StmDfuDevice.cs index c1a803cb..bcf83675 100644 --- a/nanoFirmwareFlasher.Library/StmDfuDevice.cs +++ b/nanoFirmwareFlasher.Library/StmDfuDevice.cs @@ -49,7 +49,7 @@ public StmDfuDevice(string dfuId = null) if (string.IsNullOrEmpty(dfuId)) { // no DFU id supplied, list available - var jtagDevices = ListDevices(); + List<(string serial, string device)> jtagDevices = ListDevices(); if (jtagDevices.Count > 0) { @@ -69,13 +69,13 @@ public StmDfuDevice(string dfuId = null) // DFU id was supplied // list available to find out the device ID - var jtagDevices = ListDevices(); + List<(string serial, string device)> jtagDevices = ListDevices(); // sanity check if (jtagDevices.Any()) { // find the one we're looking for - var dfuDevice = jtagDevices.FirstOrDefault(d => d.serial == dfuId); + (string serial, string device) dfuDevice = jtagDevices.FirstOrDefault(d => d.serial == dfuId); if (dfuDevice == default) { @@ -98,11 +98,11 @@ public StmDfuDevice(string dfuId = null) // try to connect to JTAG ID device to check availability // connect to device with RESET - var cliOutput = RunSTM32ProgrammerCLI($"-c port={_deviceId}"); + string cliOutput = RunSTM32ProgrammerCLI($"-c port={_deviceId}"); if (cliOutput.Contains("Error")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -110,7 +110,7 @@ public StmDfuDevice(string dfuId = null) } // parse the output to fill in the details - var match = Regex.Match(cliOutput, $"(Device name :)(?.*)(.*?[\r\n]*)*(Device CPU :)(?.*)"); + Match match = Regex.Match(cliOutput, $"(Device name :)(?.*)(.*?[\r\n]*)*(Device CPU :)(?.*)"); if (match.Success) { // grab details @@ -152,16 +152,16 @@ public ExitCodes StartExecution(string startAddress) { if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Starting execution on device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Starting execution on device..."); } // connect to device and perform command - var cliOutput = RunSTM32ProgrammerCLI($"-c port={_deviceId} --start 0x{startAddress}"); + string cliOutput = RunSTM32ProgrammerCLI($"-c port={_deviceId} --start 0x{startAddress}"); if (cliOutput.Contains("Error")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -170,24 +170,24 @@ public ExitCodes StartExecution(string startAddress) if (!cliOutput.Contains("Start operation achieved successfully")) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("ERROR"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("ERROR"); + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E1006; } if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } else { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -198,7 +198,7 @@ public ExitCodes StartExecution(string startAddress) /// A collection of connected STM DFU devices. public static List<(string serial, string device)> ListDevices() { - var cliOutput = ExecuteListDevices(); + string cliOutput = ExecuteListDevices(); // (successful) output from the above is //===== DFU Interface ===== @@ -217,7 +217,7 @@ public ExitCodes StartExecution(string startAddress) const string regexPattern = @"(?>Device Index : )(?\w+)(.*?[\r\n]*)*(?>Serial number : )(?\d+)"; var myRegex1 = new Regex(regexPattern, RegexOptions.Multiline); - var dfuMatches = myRegex1.Matches(cliOutput); + MatchCollection dfuMatches = myRegex1.Matches(cliOutput); if (dfuMatches.Count == 0) { diff --git a/nanoFirmwareFlasher.Library/StmJtagDevice.cs b/nanoFirmwareFlasher.Library/StmJtagDevice.cs index e43bba35..e15ca650 100644 --- a/nanoFirmwareFlasher.Library/StmJtagDevice.cs +++ b/nanoFirmwareFlasher.Library/StmJtagDevice.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; @@ -57,7 +55,7 @@ public StmJtagDevice(string jtagId = null) if (string.IsNullOrEmpty(jtagId)) { // no JTAG id supplied, list available - var jtagDevices = ListDevices(); + List jtagDevices = ListDevices(); if (jtagDevices.Count > 0) { @@ -78,11 +76,11 @@ public StmJtagDevice(string jtagId = null) // try to connect to JTAG ID device to check availability // connect to device with RESET - var cliOutput = RunSTM32ProgrammerCLI($"-c port=SWD sn={JtagId} HOTPLUG"); + string cliOutput = RunSTM32ProgrammerCLI($"-c port=SWD sn={JtagId} HOTPLUG"); if (cliOutput.Contains("Error")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -90,7 +88,7 @@ public StmJtagDevice(string jtagId = null) } // parse the output to fill in the details - var match = Regex.Match(cliOutput, $"(Board :)(?.*)(.*?[\r\n]*)*(Device ID :)(?.*)(.*?[\r\n]*)*(Device name :)(?.*)(.*?[\r\n]*)*(Device CPU :)(?.*)"); + Match match = Regex.Match(cliOutput, $"(Board :)(?.*)(.*?[\r\n]*)*(Device ID :)(?.*)(.*?[\r\n]*)*(Device name :)(?.*)(.*?[\r\n]*)*(Device CPU :)(?.*)"); if (match.Success) { // grab details @@ -141,16 +139,16 @@ public ExitCodes ResetMcu() { if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write("Reset MCU on device..."); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.Write("Reset MCU on device..."); } // try to connect to device with RESET - var cliOutput = RunSTM32ProgrammerCLI($"-c port=SWD sn={JtagId} mode=UR -rst"); + string cliOutput = RunSTM32ProgrammerCLI($"-c port=SWD sn={JtagId} mode=UR -rst"); if (cliOutput.Contains("Error")) { - Console.WriteLine(""); + OutputWriter.WriteLine(""); ShowCLIOutput(cliOutput); @@ -159,24 +157,24 @@ public ExitCodes ResetMcu() if (!cliOutput.Contains("MCU Reset")) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("ERROR"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine("ERROR"); + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.E5010; } if (Verbosity >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(" OK"); + OutputWriter.ForegroundColor = ConsoleColor.Green; + OutputWriter.WriteLine(" OK"); } else { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(""); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine(""); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return ExitCodes.OK; } @@ -187,7 +185,7 @@ public ExitCodes ResetMcu() /// A collection of connected STM JTAG devices. public static List ListDevices() { - var cliOutput = ExecuteListDevices(); + string cliOutput = ExecuteListDevices(); // (successful) output from the above for JTAG devices is // @@ -203,7 +201,7 @@ public static List ListDevices() const string regexPattern = @"(?<=ST-LINK SN :\s)(?.{24})"; var myRegex1 = new Regex(regexPattern, RegexOptions.Multiline); - var jtagMatches = myRegex1.Matches(cliOutput); + MatchCollection jtagMatches = myRegex1.Matches(cliOutput); if (jtagMatches.Count == 0) { diff --git a/nanoFirmwareFlasher.Library/Utilities.cs b/nanoFirmwareFlasher.Library/Utilities.cs index 6f86745b..0bf9734e 100644 --- a/nanoFirmwareFlasher.Library/Utilities.cs +++ b/nanoFirmwareFlasher.Library/Utilities.cs @@ -17,7 +17,7 @@ static Utilities() { // need this to be able to use ProcessStart at the location where the .NET Core CLI tool is running from string codeBase = Assembly.GetExecutingAssembly().Location; - var fullPath = Path.GetFullPath(codeBase); + string fullPath = Path.GetFullPath(codeBase); ExecutingPath = Path.GetDirectoryName(fullPath); } diff --git a/nanoFirmwareFlasher.Tests/CloudsmithApiTests.cs b/nanoFirmwareFlasher.Tests/CloudsmithApiTests.cs index 44ba66de..c087b71c 100644 --- a/nanoFirmwareFlasher.Tests/CloudsmithApiTests.cs +++ b/nanoFirmwareFlasher.Tests/CloudsmithApiTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.VisualStudio.TestTools.UnitTesting; namespace nanoFirmwareFlasher.Tests @@ -8,26 +11,26 @@ public class CloudsmithApiTests [TestMethod] public void CheckApiUriExists() { - Assert.IsTrue(false); + Assert.Inconclusive(); } [TestMethod] public void CheckApiVersionExists() { // And is not deprecated?! - Assert.IsTrue(false); + Assert.Inconclusive(); } [TestMethod] public void DecodePackageDetails() { - Assert.IsTrue(false); + Assert.Inconclusive(); } [TestMethod] public void DecodePackageInformation() { - Assert.IsTrue(false); + Assert.Inconclusive(); } } -} \ No newline at end of file +} diff --git a/nanoFirmwareFlasher.Tests/FirmwareArchiveManagerTests.cs b/nanoFirmwareFlasher.Tests/FirmwareArchiveManagerTests.cs new file mode 100644 index 00000000..0db3d7ee --- /dev/null +++ b/nanoFirmwareFlasher.Tests/FirmwareArchiveManagerTests.cs @@ -0,0 +1,241 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using nanoFirmwareFlasher.Tests.Helpers; +using nanoFramework.Tools.FirmwareFlasher; + +namespace nanoFirmwareFlasher.Tests +{ + [TestClass] + [TestCategory("Firmware archive")] + public sealed class FirmwareArchiveManagerTests + { + public TestContext TestContext { get; set; } = null!; + + [TestMethod] + public void FirmwareArchiveManager_EmptyArchive() + { + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + + var actual = new FirmwareArchiveManager(archiveDirectory); + + List list = actual.GetTargetList(false, null, VerbosityLevel.Diagnostic); + + Assert.IsNotNull(list); + Assert.AreEqual(0, list.Count); + output.AssertAreEqual($"Listing targets from firmware archive '{archiveDirectory}'..."); + } + + [TestMethod] + [TestCategory("CloudSmith")] + [DataRow(true)] + [DataRow(false)] + public void FirmwareArchiveManager_SinglePackage(bool isReferenceTarget) + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + CloudSmithPackageDetail package = GetTargetListHelper.GetTargetList(!isReferenceTarget, false, SupportedPlatform.esp32)[0]; + output.Reset(); + #endregion + + #region Download the package + var actual = new FirmwareArchiveManager(archiveDirectory); + + // Note that the platform is determined by the logic in the nanoff tool if a target is specified. + ExitCodes exitCode = actual.DownloadFirmwareFromRepository(false, SupportedPlatform.esp32, package.Name, package.Version, VerbosityLevel.Detailed) + .GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + Assert.IsTrue(output.Output.Contains($"Added target {package.Name} {package.Version} to the archive")); + #endregion + + #region Assert it is present and can be found via GetTargetList + Assert.IsTrue(File.Exists(Path.Combine(archiveDirectory, $"{package.Name}-{package.Version}.zip"))); + + // List of all packages + output.Reset(); + List list = actual.GetTargetList(false, null, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + $"{package.Name} {package.Version}", + string.Join("\n", from p in list select $"{p.Name} {p.Version}")); + + // List of esp32 packages + output.Reset(); + list = actual.GetTargetList(false, SupportedPlatform.esp32, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + $"{package.Name} {package.Version}", + string.Join("\n", from p in list select $"{p.Name} {p.Version}")); + + // List of stm32 packages - no match + output.Reset(); + list = actual.GetTargetList(false, SupportedPlatform.stm32, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + "", + string.Join("\n", from p in list select $"{p.Name} {p.Version}")); + #endregion + } + + [TestMethod] + [TestCategory("CloudSmith")] + public void FirmwareArchiveManager_nanoCLR() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + string targetName = "WIN_DLL_nanoCLR"; + #endregion + + #region Download the package + var actual = new FirmwareArchiveManager(archiveDirectory); + + ExitCodes exitCode = actual.DownloadFirmwareFromRepository(false, null, targetName, null, VerbosityLevel.Detailed) + .GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + Assert.IsTrue(output.Output.Contains($"Added target {targetName} ")); + #endregion + + #region Assert it is present + var targetDirectory = (from d in Directory.EnumerateDirectories(archiveDirectory) + where Path.GetFileName(d).StartsWith($"{targetName}-") + select d).FirstOrDefault(); + Assert.IsNotNull(targetDirectory); + Assert.AreEqual(1, Directory.GetDirectories(archiveDirectory).Length); + Assert.IsTrue(File.Exists(Path.Combine(targetDirectory, "nanoFramework.nanoCLR.dll"))); + Assert.IsTrue(File.Exists($"{targetDirectory}.json")); + #endregion + } + + [TestMethod] + [TestCategory("CloudSmith")] + public void FirmwareArchiveManager_TargetLatestVersion() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + CloudSmithPackageDetail package = GetTargetListHelper.GetTargetList(false, false, SupportedPlatform.esp32)[0]; + output.Reset(); + #endregion + + #region Download the latest package for the target + var actual = new FirmwareArchiveManager(archiveDirectory); + + ExitCodes exitCode = actual.DownloadFirmwareFromRepository(false, SupportedPlatform.esp32, package.Name, null, VerbosityLevel.Quiet) + .GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + output.AssertAreEqual(""); + #endregion + + #region Assert the package can be found via GetTargetList + // List of all packages + output.Reset(); + List list = actual.GetTargetList(false, null, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + $"{package.Name} {package.Version}\n", + string.Join("\n", from p in list orderby p.Name, p.Version select $"{p.Name} {p.Version}") + '\n' + ); + + // List of esp32 packages + output.Reset(); + list = actual.GetTargetList(false, SupportedPlatform.esp32, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + $"{package.Name} {package.Version}\n", + string.Join("\n", from p in list orderby p.Name, p.Version select $"{p.Name} {p.Version}") + '\n' + ); + + // List of stm32 packages - no match + output.Reset(); + list = actual.GetTargetList(false, SupportedPlatform.stm32, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + "", + string.Join("\n", from p in list select $"{p.Name} {p.Version}")); + #endregion + } + + [TestMethod] + [TestCategory("CloudSmith")] + public void FirmwareArchiveManager_Platform() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + List stablePackages = GetTargetListHelper.GetTargetList(null, false, SupportedPlatform.ti_simplelink); + output.Reset(); + #endregion + + #region Download all packages + var actual = new FirmwareArchiveManager(archiveDirectory); + + ExitCodes exitCode = actual.DownloadFirmwareFromRepository(false, SupportedPlatform.ti_simplelink, null, null, VerbosityLevel.Quiet) + .GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + output.AssertAreEqual(""); + #endregion + + #region Assert the packages can be found via GetTargetList + // List of all packages + output.Reset(); + List list = actual.GetTargetList(false, null, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + string.Join("\n", from p in stablePackages orderby p.Name, p.Version select $"{p.Name} {p.Version}") + '\n', + string.Join("\n", from p in list orderby p.Name, p.Version select $"{p.Name} {p.Version}") + '\n' + ); + + // List of esp32 packages + output.Reset(); + list = actual.GetTargetList(false, SupportedPlatform.ti_simplelink, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + string.Join("\n", from p in stablePackages orderby p.Name, p.Version select $"{p.Name} {p.Version}") + '\n', + string.Join("\n", from p in list orderby p.Name, p.Version select $"{p.Name} {p.Version}") + '\n' + ); + + // List of stm32 packages - no match + output.Reset(); + list = actual.GetTargetList(false, SupportedPlatform.esp32, VerbosityLevel.Quiet); + + output.AssertAreEqual(""); + Assert.IsNotNull(list); + Assert.AreEqual( + "", + string.Join("\n", from p in list select $"{p.Name} {p.Version}")); + #endregion + } + } +} diff --git a/nanoFirmwareFlasher.Tests/FirmwarePackageTests.cs b/nanoFirmwareFlasher.Tests/FirmwarePackageTests.cs index 4d1ef2fc..026ee919 100644 --- a/nanoFirmwareFlasher.Tests/FirmwarePackageTests.cs +++ b/nanoFirmwareFlasher.Tests/FirmwarePackageTests.cs @@ -1,33 +1,268 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using nanoFirmwareFlasher.Tests.Helpers; +using nanoFramework.Tools.FirmwareFlasher; namespace nanoFirmwareFlasher.Tests { [TestClass] public class FirmwarePackageTests { + public TestContext TestContext { get; set; } = null!; [TestMethod] - public void ListReferenceTargets() + [TestCategory("CloudSmith")] + public void FirmwarePackage_ListReferenceTargets() { - Assert.IsTrue(false); + using var output = new OutputWriterHelper(); + + #region Get the stable packages + List stable = FirmwarePackage.GetTargetList(false, false, null, VerbosityLevel.Diagnostic); + Assert.IsNotNull(stable); + Assert.AreNotEqual(0, stable.Count); + output.AssertAreEqual("Listing targets from 'nanoframework-images' repository [STABLE]..."); + #endregion + + #region Get the preview packages + output.Reset(); + List preview = FirmwarePackage.GetTargetList(false, true, null, VerbosityLevel.Quiet); + Assert.IsNotNull(preview); + output.AssertAreEqual(""); + + // Assert that the preview packages are not part of the stable package list + foreach (CloudSmithPackageDetail previewPackage in preview) + { + Assert.IsFalse((from s in stable + where s.Name == previewPackage.Name && s.Version == previewPackage.Version + select s).Any()); + } + #endregion + + #region Get the stable esp32 packages + output.Reset(); + List stableEsp32 = FirmwarePackage.GetTargetList(false, false, SupportedPlatform.esp32, VerbosityLevel.Diagnostic); + Assert.IsNotNull(stableEsp32); + Assert.AreNotEqual(0, stableEsp32.Count); + output.AssertAreEqual("Listing esp32 targets from 'nanoframework-images' repository [STABLE]..."); + + // Assert that there are more stable packages than for the esp32 + Assert.IsTrue(stableEsp32.Count < stable.Count); + + // Assert that all esp32 packages are in the stable list + foreach (CloudSmithPackageDetail esp32Package in stableEsp32) + { + Assert.IsTrue((from s in stable + where s.Name == esp32Package.Name && s.Version == esp32Package.Version + select s).Any()); + } + #endregion } [TestMethod] - public void ListCommunityTargets() + [TestCategory("CloudSmith")] + public void FirmwarePackage_ListCommunityTargets() { - Assert.IsTrue(false); + using var output = new OutputWriterHelper(); + + #region Get the stable packages + List stable = FirmwarePackage.GetTargetList(true, false, null, VerbosityLevel.Diagnostic); + Assert.IsNotNull(stable); + Assert.AreNotEqual(0, stable.Count); + output.AssertAreEqual("Listing targets from 'nanoframework-images-community-targets' repository..."); + #endregion + + #region Get the stable esp32 packages + output.Reset(); + List stableEsp32 = FirmwarePackage.GetTargetList(true, false, SupportedPlatform.esp32, VerbosityLevel.Quiet); + Assert.AreNotEqual(0, stableEsp32.Count); + output.AssertAreEqual(""); + + // Assert that there are more stable packages than for the esp32 + Assert.IsTrue(stableEsp32.Count < stable.Count); + + // Assert that all esp32 packages are in the stable list + foreach (CloudSmithPackageDetail esp32Package in stableEsp32) + { + Assert.IsTrue((from s in stable + where s.Name == esp32Package.Name && s.Version == esp32Package.Version + select s).Any()); + } + #endregion } [TestMethod] - public void DownloadReferenceTarget() + [TestCategory("CloudSmith")] + [DataRow(true)] + [DataRow(false)] + public void FirmwarePackage_Download(bool isReferenceTarget) { - Assert.IsTrue(false); + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string cacheDirectory = Path.Combine(testDirectory, TestDirectoryHelper.LocationPathBase_RelativePath); + List stable = GetTargetListHelper.GetTargetList(!isReferenceTarget, false, SupportedPlatform.esp32, false); + CloudSmithPackageDetail? newerPackage = null; + CloudSmithPackageDetail? package = null; + for (int i = 0; i < stable.Count; i++) + { + for (int j = i + 1; j < stable.Count; j++) + { + if (stable[i].Name == stable[j].Name && stable[i].Version != stable[j].Version) + { + if (new Version(stable[i].Version) > new Version(stable[j].Version)) + { + newerPackage = stable[i]; + package = stable[j]; + } + else + { + newerPackage = stable[j]; + package = stable[i]; + } + break; + } + } + if (newerPackage is not null) + { + break; + } + } + if (newerPackage is null || package is null) + { + Assert.Inconclusive("No ESP32 package available with two versions???"); + } + #endregion + + #region Download older version + var actual = new Esp32Firmware(package!.Name, package.Version, false, null); + ExitCodes exitCode = actual.DownloadAndExtractAsync(null).GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + Assert.IsTrue(Directory.Exists(Path.Combine(cacheDirectory, package.Name))); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, package.Name, $"{package.Name}-{package.Version}.zip"))); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, package.Name, "nanoCLR.bin"))); + #endregion + + #region Download newer version + DateTime modified = File.GetLastWriteTimeUtc(Path.Combine(cacheDirectory, package.Name, "nanoCLR.bin")); + + actual = new Esp32Firmware(package!.Name, newerPackage.Version, false, null); + exitCode = actual.DownloadAndExtractAsync(null).GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + Assert.IsTrue(Directory.Exists(Path.Combine(cacheDirectory, package.Name))); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, package.Name, $"{newerPackage.Name}-{newerPackage.Version}.zip"))); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, package.Name, "nanoCLR.bin"))); + Assert.AreNotEqual(modified, File.GetLastWriteTimeUtc(Path.Combine(cacheDirectory, package.Name, "nanoCLR.bin"))); + #endregion } + [TestMethod] - public void DownloadCommunityTarget() + [TestCategory("CloudSmith")] + public void FirmwarePackage_VirtualDevice_Download() { - Assert.IsTrue(false); + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string cacheDirectory = Path.Combine(testDirectory, TestDirectoryHelper.LocationPathBase_RelativePath); + string targetName = "WIN_DLL_nanoCLR"; + #endregion + + #region Download Virtual Device firmware + var actual = new Esp32Firmware(targetName, null, false, null); + ExitCodes exitCode = actual.DownloadAndExtractAsync(null).GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + Assert.IsTrue(Directory.Exists(Path.Combine(cacheDirectory, targetName))); + string[] directories = (from d in Directory.GetDirectories(Path.Combine(cacheDirectory, targetName)) + select Path.GetFileName(d)).ToArray(); + Assert.AreEqual(1, (from d in directories + where d.StartsWith(targetName + "-") + select d).Count()); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, targetName, directories[0], "nanoFramework.nanoCLR.dll"))); + Assert.AreEqual(1, directories.Length); + #endregion + } + + + [TestMethod] + [TestCategory("CloudSmith")] + public void FirmwarePackage_DebugFirmware_Download() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string cacheDirectory = Path.Combine(testDirectory, TestDirectoryHelper.LocationPathBase_RelativePath); + string targetName = "WIN32_nanoCLR"; + #endregion + + #region Download Virtual Device firmware + var actual = new Esp32Firmware(targetName, null, false, null); + ExitCodes exitCode = actual.DownloadAndExtractAsync(null).GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + Assert.IsTrue(Directory.Exists(Path.Combine(cacheDirectory, targetName))); + string[] directories = (from d in Directory.GetDirectories(Path.Combine(cacheDirectory, targetName)) + select Path.GetFileName(d)).ToArray(); + Assert.AreEqual(1, (from d in directories + where d.StartsWith(targetName + "-") + select d).Count()); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, targetName, directories[0], "nanoFramework.nanoCLR.dll"))); + Assert.AreEqual(1, directories.Length); + #endregion + } + + [TestMethod] + [TestCategory("Firmware archive")] + public void FirmwarePackage_FromArchive() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + Directory.CreateDirectory(archiveDirectory); + string cacheDirectory = Path.Combine(testDirectory, TestDirectoryHelper.LocationPathBase_RelativePath); + CloudSmithPackageDetail package = GetTargetListHelper.GetTargetList(false, false, SupportedPlatform.ti_simplelink)[0]; + var firmware = new Esp32Firmware(package.Name, package.Version, false, null); + ExitCodes exitCode = firmware.DownloadAndExtractAsync(null).GetAwaiter().GetResult(); + if (ExitCodes.OK != exitCode) + { + Assert.Inconclusive("Cannot download the ESP32 package."); + } + string testTargetName = "NO_REAL_TARGET"; + string testVersion = "0.0.0.0"; + File.Copy(Path.Combine(cacheDirectory, package.Name, $"{package.Name}-{package.Version}.zip"), Path.Combine(archiveDirectory, $"{testTargetName}-{testVersion}.zip")); + #endregion + + #region Get package that exist in the archive directory but not in the repository + output.Reset(); + var actual = new Esp32Firmware(testTargetName, testVersion, false, null); + exitCode = actual.DownloadAndExtractAsync(archiveDirectory).GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.OK, exitCode); + output.AssertAreEqual(""); + Assert.IsTrue(Directory.Exists(Path.Combine(cacheDirectory, testTargetName))); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, testTargetName, $"{testTargetName}-{testVersion}.zip"))); + Assert.IsTrue(File.Exists(Path.Combine(cacheDirectory, testTargetName, "nanoCLR.bin"))); + #endregion + + #region Get package that does not exist in the archive directory + testTargetName = "MISSING_TARGET"; + + actual = new Esp32Firmware(testTargetName, testVersion, false, null); + exitCode = actual.DownloadAndExtractAsync(archiveDirectory).GetAwaiter().GetResult(); + + Assert.AreEqual(ExitCodes.E9007, exitCode); + output.AssertAreEqual(""); + Assert.IsFalse(File.Exists(Path.Combine(cacheDirectory, testTargetName, $"{testTargetName}-{testVersion}.zip"))); + #endregion } } } diff --git a/nanoFirmwareFlasher.Tests/Helpers/GetTargetListHelper.cs b/nanoFirmwareFlasher.Tests/Helpers/GetTargetListHelper.cs new file mode 100644 index 00000000..c9134869 --- /dev/null +++ b/nanoFirmwareFlasher.Tests/Helpers/GetTargetListHelper.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using nanoFramework.Tools.FirmwareFlasher; + +namespace nanoFirmwareFlasher.Tests.Helpers +{ + internal static class GetTargetListHelper + { + internal static List GetTargetList(bool? community, bool preview, SupportedPlatform? platform, bool latestVersionOnly = true) + { + var allPackages = new List(); + + if (community != true) + { + allPackages.AddRange(FirmwarePackage.GetTargetList(false, preview, platform, VerbosityLevel.Quiet)); + } + if (community != false && !preview) + { + allPackages.AddRange(FirmwarePackage.GetTargetList(true, preview, platform, VerbosityLevel.Quiet)); + } + if (allPackages.Count == 0) + { + Assert.Inconclusive("No packages available???"); + } + + if (latestVersionOnly) + { + var latestVersion = new Dictionary(); + foreach (var package in allPackages) + { + var thisVersion = new Version(package.Version); + if (!latestVersion.TryGetValue(package.Name, out (CloudSmithPackageDetail package, Version version) version) + || thisVersion > version.version) + { + latestVersion[package.Name] = (package, thisVersion); + } + } + return (from p in latestVersion.Values + select p.package).ToList(); + } + else + { + return allPackages; + } + } + } +} diff --git a/nanoFirmwareFlasher.Tests/Helpers/OutputWriterHelper.cs b/nanoFirmwareFlasher.Tests/Helpers/OutputWriterHelper.cs new file mode 100644 index 00000000..0447bc55 --- /dev/null +++ b/nanoFirmwareFlasher.Tests/Helpers/OutputWriterHelper.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using nanoFramework.Tools.FirmwareFlasher; + +namespace nanoFirmwareFlasher.Tests.Helpers +{ + internal sealed class OutputWriterHelper : IDisposable, OutputWriter.IOutputWriter + { + #region Fields + private ConsoleColor _foregroundColor = ConsoleColor.White; + private readonly StringBuilder _output = new(); + #endregion + + #region Construction/destruction + public OutputWriterHelper() + { + OutputWriter.SetOutputWriter(this); + } + public void Dispose() + { + OutputWriter.SetOutputWriter(null); + } + #endregion + + #region Properties + /// + /// Get the output so far. Changes of foreground color are coded + /// as ~`{ColorName}`~. + /// + public string Output + => _output.ToString(); + #endregion + + #region Test support + /// + /// Reset the writer to its initial state + /// + public void Reset() + { + _foregroundColor = ConsoleColor.White; + _output.Clear(); + } + + /// + /// Assert that the output is equal to . Both the actual + /// and expected values are trimmed before the comparison. + /// + /// + public void AssertAreEqual(string expected) + { + Assert.AreEqual( + expected.Trim().Replace("\r\n", Environment.NewLine).Replace("\n", Environment.NewLine) + '\n', + _output.ToString().Trim() + '\n' // extra \n to make output in test results look better + ); + } + #endregion + + #region OutputWriter.IOutputWriter implementation + ConsoleColor OutputWriter.IOutputWriter.ForegroundColor + { + get => _foregroundColor; + set + { + if (_foregroundColor != value) + { + _foregroundColor = value; + _output.Append($"~`{value}`~"); + Debug.Write($"~`{value}`~"); + } + } + } + + void OutputWriter.IOutputWriter.Write(string text) + { + _output.Append(text); + Debug.Write(text); + } + #endregion + + + } +} diff --git a/nanoFirmwareFlasher.Tests/Helpers/TestDirectoryHelper.cs b/nanoFirmwareFlasher.Tests/Helpers/TestDirectoryHelper.cs new file mode 100644 index 00000000..9ce6b128 --- /dev/null +++ b/nanoFirmwareFlasher.Tests/Helpers/TestDirectoryHelper.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using nanoFramework.Tools.FirmwareFlasher; + +namespace nanoFirmwareFlasher.Tests.Helpers +{ + internal static class TestDirectoryHelper + { + public const string LocationPathBase_RelativePath = "fw_cache"; + + /// + /// Get a test directory for the test. Also sets the . + /// + /// + /// + public static string GetTestDirectory(TestContext context) + { + lock (typeof(TestDirectoryHelper)) + { + s_lastIndex++; + string path = Path.Combine(context.ResultsDirectory!, "nanoff", s_lastIndex.ToString()); + Debug.WriteLine($"Test directory: {path}"); + Directory.CreateDirectory(path); + + FirmwarePackage.LocationPathBase = Path.Combine(path, LocationPathBase_RelativePath); + + return path; + } + } + private static int s_lastIndex; + } +} diff --git a/nanoFirmwareFlasher.Tests/MSTest.cs b/nanoFirmwareFlasher.Tests/MSTest.cs new file mode 100644 index 00000000..ea9e914f --- /dev/null +++ b/nanoFirmwareFlasher.Tests/MSTest.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/nanoFirmwareFlasher.Tests/ToolFirmwareArchiveTests.cs b/nanoFirmwareFlasher.Tests/ToolFirmwareArchiveTests.cs new file mode 100644 index 00000000..3ac52d92 --- /dev/null +++ b/nanoFirmwareFlasher.Tests/ToolFirmwareArchiveTests.cs @@ -0,0 +1,155 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using nanoFirmwareFlasher.Tests.Helpers; +using nanoFramework.Tools.FirmwareFlasher; + +namespace nanoFirmwareFlasher.Tests +{ + /// + /// Verify that the options to nanoff are passed correctly to the low-level classes. + /// This cannot be done for the update of firmware, at least not for ESP32, as that + /// requires a connection to a real device. + /// + [TestClass] + [TestCategory("Firmware archive")] + [DoNotParallelize] // because of static variables in the programs + public sealed class ToolFirmwareArchiveTests + { + public TestContext TestContext { get; set; } = null!; + + [TestMethod] + public void FirmwareArchive_Platform_UpdateArchive_ListTargets() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + List expectedPackages = GetTargetListHelper.GetTargetList(null, false, SupportedPlatform.ti_simplelink); + #endregion + + #region List empty archive + int actual = Program.Main(["--listtargets", "--platform", $"{SupportedPlatform.ti_simplelink}", "--fromfwarchive", "--fwarchivepath", archiveDirectory]) + .GetAwaiter().GetResult(); + Assert.AreEqual((int)ExitCodes.OK, actual); + #endregion + + #region Update archive + output.Reset(); + actual = Program.Main(["--updatefwarchive", "--platform", $"{SupportedPlatform.ti_simplelink}", "--fwarchivepath", archiveDirectory]) + .GetAwaiter().GetResult(); + Assert.AreEqual((int)ExitCodes.OK, actual); + #endregion + + #region List filled archive + output.Reset(); + actual = Program.Main(["--listtargets", "--platform", $"{SupportedPlatform.ti_simplelink}", "--fromfwarchive", "--fwarchivepath", archiveDirectory]) + .GetAwaiter().GetResult(); + Assert.AreEqual((int)ExitCodes.OK, actual); + + foreach (var package in expectedPackages) + { + Assert.IsTrue(output.Output.Contains($"{package.Name}{Environment.NewLine} {package.Version}"), $"{package.Name} - {package.Version}"); + } + #endregion + } + + [TestMethod] + public void FirmwareArchive_Target_UpdateArchive_ListTargets() + { + #region Setup + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + List allPackages = GetTargetListHelper.GetTargetList(null, false, SupportedPlatform.ti_simplelink); + #endregion + + #region Update archive + output.Reset(); + int actual = Program.Main(["--updatefwarchive", "--target", $"{allPackages[0].Name}", "--fwarchivepath", archiveDirectory]) + .GetAwaiter().GetResult(); + Assert.AreEqual((int)ExitCodes.OK, actual); + #endregion + + #region List filled archive + output.Reset(); + actual = Program.Main(["--listtargets", "--platform", $"{SupportedPlatform.ti_simplelink}", "--fromfwarchive", "--fwarchivepath", archiveDirectory]) + .GetAwaiter().GetResult(); + Assert.AreEqual((int)ExitCodes.OK, actual); + + foreach (var package in allPackages) + { + var expectPresent = package == allPackages[0]; + Assert.AreEqual(expectPresent, output.Output.Contains($"{package.Name}{Environment.NewLine} {package.Version}"), $"{package.Name} - {package.Version}"); + } + #endregion + } + + [TestMethod] + public void FirmwareArchive_ListTargets_InvalidArguments() + { + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + + int actual = Program.Main(["--listtargets", "--platform", $"{SupportedPlatform.ti_simplelink}", "--fromfwarchive", "--verbosity", "diagnostic"]) + .GetAwaiter().GetResult(); + + Assert.AreEqual((int)ExitCodes.E9000, actual); + Assert.IsTrue(output.Output.Contains("--fwarchivepath is required when --fromfwarchive is specified.")); + } + + [TestMethod] + public void FirmwareArchive_UpdateArchive_InvalidArguments() + { + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + + int actual = Program.Main(["--updatefwarchive", "--platform", $"{SupportedPlatform.ti_simplelink}", "--fromfwarchive", "--verbosity", "diagnostic"]) + .GetAwaiter().GetResult(); + + Assert.AreEqual((int)ExitCodes.E9000, actual); + Assert.IsTrue(output.Output.Contains("Incompatible option --fromfwarchive combined with --updatefwarchive.")); + + output.Reset(); + actual = Program.Main(["--updatefwarchive", "--platform", $"{SupportedPlatform.ti_simplelink}", "--verbosity", "diagnostic"]) + .GetAwaiter().GetResult(); + + Assert.AreEqual((int)ExitCodes.E9000, actual); + Assert.IsTrue(output.Output.Contains("--fwarchivepath is required when --updatefwarchive is specified.")); + + output.Reset(); + actual = Program.Main(["--updatefwarchive", "--fwarchivepath", $"{SupportedPlatform.ti_simplelink}", "--verbosity", "diagnostic"]) + .GetAwaiter().GetResult(); + + Assert.AreEqual((int)ExitCodes.E9000, actual); + Assert.IsTrue(output.Output.Contains("--platform or --target is required when --updatefwarchive is specified.")); + } + + [TestMethod] + public void FirmwareArchive_UpdateFirmware_InvalidArguments() + { + using var output = new OutputWriterHelper(); + string testDirectory = TestDirectoryHelper.GetTestDirectory(TestContext); + string archiveDirectory = Path.Combine(testDirectory, "archive"); + + int actual = Program.Main(["--serialport", "COM3", "--target", "SOME_TARGET", "--fromfwarchive", "--verbosity", "diagnostic"]) + .GetAwaiter().GetResult(); + + Assert.AreEqual((int)ExitCodes.E9000, actual); + Assert.IsTrue(output.Output.Contains("--fwarchivepath is required when --fromfwarchive is specified.")); + + output.Reset(); + actual = Program.Main(["--serialport", "COM3", "--target", "SOME_TARGET", "--fwarchivepath", archiveDirectory, "--verbosity", "diagnostic"]) + .GetAwaiter().GetResult(); + + Assert.AreEqual((int)ExitCodes.E9000, actual); + Assert.IsTrue(output.Output.Contains("--fromfwarchive is required when --fwarchivepath is specified.")); + } + } +} diff --git a/nanoFirmwareFlasher.Tests/nanoFirmwareFlasher.Tests.csproj b/nanoFirmwareFlasher.Tests/nanoFirmwareFlasher.Tests.csproj index b372995e..4e84fbf1 100644 --- a/nanoFirmwareFlasher.Tests/nanoFirmwareFlasher.Tests.csproj +++ b/nanoFirmwareFlasher.Tests/nanoFirmwareFlasher.Tests.csproj @@ -1,20 +1,25 @@ - net6.0 + net8.0 enable false - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/nanoFirmwareFlasher.Tool/Esp32Manager.cs b/nanoFirmwareFlasher.Tool/Esp32Manager.cs index 6d3d2f44..de9480b7 100644 --- a/nanoFirmwareFlasher.Tool/Esp32Manager.cs +++ b/nanoFirmwareFlasher.Tool/Esp32Manager.cs @@ -1,11 +1,10 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Linq; using System.Threading.Tasks; +using nanoFramework.Tools.Debugger.NFDevice; namespace nanoFramework.Tools.FirmwareFlasher { @@ -16,6 +15,7 @@ public class Esp32Manager : IManager { private readonly Options _options; private readonly VerbosityLevel _verbosityLevel; + private const int AccessSerialPortTimeout = 3000; public Esp32Manager(Options options, VerbosityLevel verbosityLevel) { @@ -42,6 +42,25 @@ public async Task ProcessAsync() return ExitCodes.E6001; } + ExitCodes result = ExitCodes.E6002; + + using (var access = GlobalExclusiveDeviceAccess.TryGet(_options.SerialPort, AccessSerialPortTimeout)) + { + if (access is null) + { + result = ExitCodes.E6002; + } + else + { + result = await DoProcessAsync(); + } + } + + return result; + } + + private async Task DoProcessAsync() + { EspTool espTool; try @@ -78,13 +97,13 @@ public async Task ProcessAsync() if (_verbosityLevel >= VerbosityLevel.Normal) { - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine(""); - Console.WriteLine($"Connected to:"); - Console.WriteLine($"{esp32Device}"); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine($"Connected to:"); + OutputWriter.WriteLine($"{esp32Device}"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; // if this is a PICO and baud rate is not 115200 or 1M5, operations will most likely fail // warn user about this @@ -93,16 +112,16 @@ public async Task ProcessAsync() && (_options.BaudRate != 115200 && _options.BaudRate != 1500000)) { - Console.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(""); - Console.WriteLine("****************************** WARNING ******************************"); - Console.WriteLine("The connected device it's an ESP32 PICO which can be picky about the "); - Console.WriteLine("baud rate used. Recommendation is to use --baud 115200 "); - Console.WriteLine("*********************************************************************"); - Console.WriteLine(""); + OutputWriter.WriteLine(""); + OutputWriter.WriteLine("****************************** WARNING ******************************"); + OutputWriter.WriteLine("The connected device it's an ESP32 PICO which can be picky about the "); + OutputWriter.WriteLine("baud rate used. Recommendation is to use --baud 115200 "); + OutputWriter.WriteLine("*********************************************************************"); + OutputWriter.WriteLine(""); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } @@ -135,7 +154,7 @@ public async Task ProcessAsync() bool updateAndDeploy = false; // update operation requested? - if (_options.Update) + if (_options.Update || _options.IdentifyFirmware) { // write flash var exitCode = await Esp32Operations.UpdateFirmwareAsync( @@ -145,6 +164,8 @@ public async Task ProcessAsync() true, _options.FwVersion, _options.Preview, + _options.IdentifyFirmware, + _options.FromFwArchive ? _options.FwArchivePath : null, _options.DeploymentImage, null, _options.ClrFile, @@ -153,7 +174,7 @@ public async Task ProcessAsync() _verbosityLevel, _options.Esp32PartitionTableSize); - if (exitCode != ExitCodes.OK) + if (exitCode != ExitCodes.OK || _options.IdentifyFirmware) { // done here return exitCode; diff --git a/nanoFirmwareFlasher.Tool/Friends.cs b/nanoFirmwareFlasher.Tool/Friends.cs new file mode 100644 index 00000000..20c5b261 --- /dev/null +++ b/nanoFirmwareFlasher.Tool/Friends.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("nanoFirmwareFlasher.Tests")] diff --git a/nanoFirmwareFlasher.Tool/NanoDeviceManager.cs b/nanoFirmwareFlasher.Tool/NanoDeviceManager.cs index ed23b99b..63d1b8bd 100644 --- a/nanoFirmwareFlasher.Tool/NanoDeviceManager.cs +++ b/nanoFirmwareFlasher.Tool/NanoDeviceManager.cs @@ -1,9 +1,10 @@ -using nanoFramework.Tools.Debugger; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.NFDevice; namespace nanoFramework.Tools.FirmwareFlasher { @@ -14,6 +15,7 @@ public class NanoDeviceManager : IManager { private readonly Options _options; private readonly VerbosityLevel _verbosityLevel; + private const int AccessSerialPortTimeout = 3000; public NanoDeviceManager(Options options, VerbosityLevel verbosityLevel) { @@ -40,44 +42,54 @@ public async Task ProcessAsync() NanoDeviceOperations _nanoDeviceOperations = new NanoDeviceOperations(); - if (_options.DeviceDetails) - { - NanoDeviceBase nanoDevice = null; - return _nanoDeviceOperations.GetDeviceDetails( - _options.SerialPort, - ref nanoDevice); - } - else if (_options.Update) + using (var access = GlobalExclusiveDeviceAccess.TryGet(_options.SerialPort, AccessSerialPortTimeout)) { - exitCode = await _nanoDeviceOperations.UpdateDeviceClrAsync( - _options.SerialPort, - _options.FwVersion, - _options.ClrFile, - _verbosityLevel); + if (access is null) + { + return ExitCodes.E6002; + } - if (exitCode != ExitCodes.OK) + if (_options.DeviceDetails) { - return exitCode; + NanoDeviceBase nanoDevice = null; + return _nanoDeviceOperations.GetDeviceDetails( + _options.SerialPort, + ref nanoDevice); } + else if (_options.Update) + { + exitCode = await _nanoDeviceOperations.UpdateDeviceClrAsync( + _options.SerialPort, + _options.FwVersion, + _options.IdentifyFirmware, + _options.FromFwArchive ? _options.FwArchivePath : null, + _options.ClrFile, + _verbosityLevel); - // flag operation as done - failedToDoSomething = false; - } + if (exitCode != ExitCodes.OK) + { + return exitCode; + } - if (_options.Deploy) - { - exitCode = _nanoDeviceOperations.DeployApplication( - _options.SerialPort, - _options.DeploymentImage, - _verbosityLevel); + // flag operation as done + failedToDoSomething = false; + } - if (exitCode != ExitCodes.OK) + if (_options.Deploy) { - return exitCode; - } + exitCode = _nanoDeviceOperations.DeployApplication( + _options.SerialPort, + _options.DeploymentImage, + _verbosityLevel); + + if (exitCode != ExitCodes.OK) + { + return exitCode; + } - // flag operation as done - failedToDoSomething = false; + // flag operation as done + failedToDoSomething = false; + } } if (failedToDoSomething) diff --git a/nanoFirmwareFlasher.Tool/Options.cs b/nanoFirmwareFlasher.Tool/Options.cs index 02bdb0c2..e6e650f5 100644 --- a/nanoFirmwareFlasher.Tool/Options.cs +++ b/nanoFirmwareFlasher.Tool/Options.cs @@ -1,11 +1,9 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using CommandLine.Text; -using System.Collections.Generic; namespace nanoFramework.Tools.FirmwareFlasher { @@ -341,6 +339,13 @@ public class Options HelpText = "Reads details from connected device.")] public bool DeviceDetails { get; set; } + [Option( + "identifyfirmware", + Required = false, + Default = false, + HelpText = "Show which firmware to use for a device without deploying anything.")] + public bool IdentifyFirmware { get; set; } + [Option( "filedeployment", Required = false, @@ -348,10 +353,37 @@ public class Options HelpText = "JSON file containing file deployment settings.")] public string FileDeployment { get; set; } + [Option( + "archivepath", + Required = false, + Default = null, + HelpText = "Path of the directory where the firmware is archived.")] + public string FwArchivePath { get; set; } + + [Option( + "updatearchive", + Required = false, + Default = false, + HelpText = "Copy the firmware from the online repository to the firmware archive directory; do not update the firmware on a connected device.")] + public bool UpdateFwArchive { get; set; } + + [Option( + "fromarchive", + Required = false, + Default = false, + HelpText = "Get the firmware from the firmware archive rather than from the online repository.")] + public bool FromFwArchive { get; set; } + + [Option( + "suppressnanoffversioncheck", + Required = false, + Default = false, + HelpText = $"Do not check whether a new version of {_APPLICATIONALIAS} is available.")] + public bool SuppressNanoFFVersionCheck { get; set; } #endregion - [Usage(ApplicationAlias = "nanoff")] + [Usage(ApplicationAlias = _APPLICATIONALIAS)] public static IEnumerable Examples => [ new("- Update ESP32 WROVER Kit device with latest available firmware", new Options { TargetName = "ESP_WROVER_KIT", Update = true }), @@ -364,5 +396,6 @@ public class Options new("- List all available STM32 targets", new Options { ListTargets = true, Platform = SupportedPlatform.stm32 }), new("- List all available COM ports", new Options { ListComPorts = true }), ]; + private const string _APPLICATIONALIAS = "nanoff"; } } diff --git a/nanoFirmwareFlasher.Tool/Program.cs b/nanoFirmwareFlasher.Tool/Program.cs index ed131e37..16a15cab 100644 --- a/nanoFirmwareFlasher.Tool/Program.cs +++ b/nanoFirmwareFlasher.Tool/Program.cs @@ -1,14 +1,6 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using CommandLine; -using CommandLine.Text; -using Microsoft.Extensions.Configuration; -using nanoFramework.Tools.FirmwareFlasher.Extensions; -using nanoFramework.Tools.FirmwareFlasher.FileDeployment; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -19,6 +11,12 @@ using System.Net.Http.Headers; using System.Reflection; using System.Threading.Tasks; +using CommandLine; +using CommandLine.Text; +using Microsoft.Extensions.Configuration; +using nanoFramework.Tools.FirmwareFlasher.Extensions; +using nanoFramework.Tools.FirmwareFlasher.FileDeployment; +using Newtonsoft.Json; namespace nanoFramework.Tools.FirmwareFlasher { @@ -36,7 +34,8 @@ public static async Task Main(string[] args) { // take care of static fields _informationalVersionAttribute = Attribute.GetCustomAttribute( - Assembly.GetEntryAssembly()!, + // Cannot be Assembly.GetEntryAssembly()! as that fails in tests + typeof(Program).Assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute; @@ -44,6 +43,11 @@ public static async Task Main(string[] args) _copyrightInfo = new CopyrightInfo(true, $".NET Foundation and nanoFramework project contributors", 2019); + // for tests + _exitCode = ExitCodes.OK; + _extraMessage = null; + _verbosityLevel = VerbosityLevel.Quiet; + // need this to be able to use ProcessStart at the location where the .NET Core CLI tool is running from string codeBase = Assembly.GetExecutingAssembly().Location; var fullPath = Path.GetFullPath(codeBase); @@ -82,12 +86,12 @@ public static async Task Main(string[] args) .AddPreOptionsLine(HelpText.RenderUsageText(result)) .AddPreOptionsLine(""); - Console.WriteLine(helpText.ToString()); + OutputWriter.WriteLine(helpText.ToString()); #if !VS_CODE_EXTENSION_BUILD // perform version check CheckVersion(); - Console.WriteLine(); + OutputWriter.WriteLine(); #endif return (int)ExitCodes.OK; @@ -142,10 +146,10 @@ private static void CheckVersion() } catch (Exception) { - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine("** Can't check the version! **"); - Console.WriteLine("** Continuing anyway. **"); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.DarkYellow; + OutputWriter.WriteLine("** Can't check the version! **"); + OutputWriter.WriteLine("** Continuing anyway. **"); + OutputWriter.ForegroundColor = ConsoleColor.White; } } @@ -203,28 +207,31 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) #endregion - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; - Console.WriteLine(_headerInfo); - Console.WriteLine(_copyrightInfo); - Console.WriteLine(); + OutputWriter.WriteLine(_headerInfo); + OutputWriter.WriteLine(_copyrightInfo); + OutputWriter.WriteLine(); #if !VS_CODE_EXTENSION_BUILD - // perform version check - CheckVersion(); - Console.WriteLine(); + if (!o.SuppressNanoFFVersionCheck) + { + // perform version check + CheckVersion(); + OutputWriter.WriteLine(); + } #endif - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; if (o.ClearCache) { - Console.WriteLine(); + OutputWriter.WriteLine(); if (Directory.Exists(FirmwarePackage.LocationPathBase)) { - Console.WriteLine("Clearing firmware cache location."); + OutputWriter.WriteLine("Clearing firmware cache location."); try { @@ -238,7 +245,7 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) } else { - Console.WriteLine("Firmware cache location does not exist. Nothing to do."); + OutputWriter.WriteLine("Firmware cache location does not exist. Nothing to do."); } return; @@ -249,22 +256,22 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) var ports = SerialPort.GetPortNames(); if (ports.Any()) { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine("Available COM ports:"); + OutputWriter.ForegroundColor = ConsoleColor.White; + OutputWriter.WriteLine("Available COM ports:"); foreach (var p in ports) { - Console.WriteLine($" {p}"); + OutputWriter.WriteLine($" {p}"); } } else { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("No available COM port."); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine("No available COM port."); } - Console.WriteLine(); + OutputWriter.WriteLine(); - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; return; } @@ -273,22 +280,40 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) // First check if we are asked for the list of available targets if (o.ListTargets) { - // get list from REFERENCE targets - var targets = FirmwarePackage.GetTargetList( - false, - o.Preview, - o.Platform, - _verbosityLevel); + List targets; + if (o.FromFwArchive) + { + if (string.IsNullOrEmpty(o.FwArchivePath)) + { + _exitCode = ExitCodes.E9000; + _extraMessage = "--fwarchivepath is required when --fromfwarchive is specified."; + return; + } - // append list from COMMUNITY targets - targets = targets.Concat( - FirmwarePackage.GetTargetList( - true, - o.Preview, - o.Platform, - _verbosityLevel)).ToList(); + // get the list from the archive + targets = new FirmwareArchiveManager(o.FwArchivePath).GetTargetList( + o.Preview, + o.Platform, + _verbosityLevel); + } + else + { + // get list from REFERENCE targets + targets = FirmwarePackage.GetTargetList( + false, + o.Preview, + o.Platform, + _verbosityLevel); - Console.WriteLine("Available targets:"); + // append list from COMMUNITY targets + targets = targets.Concat( + FirmwarePackage.GetTargetList( + true, + o.Preview, + o.Platform, + _verbosityLevel)).ToList(); + } + OutputWriter.WriteLine("Available targets:"); DisplayBoardDetails(targets); @@ -309,16 +334,16 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) if (!connectedDevices.Any()) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("No devices found"); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine("No devices found"); } else { - Console.WriteLine("-- Connected .NET nanoFramework devices --"); + OutputWriter.WriteLine("-- Connected .NET nanoFramework devices --"); foreach (var nanoDevice in connectedDevices) { - Console.WriteLine($"{nanoDevice.Description}"); + OutputWriter.WriteLine($"{nanoDevice.Description}"); if (_verbosityLevel >= VerbosityLevel.Normal) { @@ -328,11 +353,11 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) // we have to have a valid device info if (nanoDevice.DeviceInfo.Valid) { - Console.WriteLine($" Target: {nanoDevice.DeviceInfo.TargetName?.ToString()}"); - Console.WriteLine($" Platform: {nanoDevice.DeviceInfo.Platform?.ToString()}"); - Console.WriteLine($" Date: {nanoDevice.DebugEngine.Capabilities.SoftwareVersion.BuildDate ?? "unknown"}"); - Console.WriteLine($" Type: {nanoDevice.DebugEngine.Capabilities.SolutionReleaseInfo.VendorInfo ?? "unknown"}"); - Console.WriteLine($" CLR Version: {nanoDevice.DeviceInfo.SolutionBuildVersion}"); + OutputWriter.WriteLine($" Target: {nanoDevice.DeviceInfo.TargetName?.ToString()}"); + OutputWriter.WriteLine($" Platform: {nanoDevice.DeviceInfo.Platform?.ToString()}"); + OutputWriter.WriteLine($" Date: {nanoDevice.DebugEngine.Capabilities.SoftwareVersion.BuildDate ?? "unknown"}"); + OutputWriter.WriteLine($" Type: {nanoDevice.DebugEngine.Capabilities.SolutionReleaseInfo.VendorInfo ?? "unknown"}"); + OutputWriter.WriteLine($" CLR Version: {nanoDevice.DeviceInfo.SolutionBuildVersion}"); } } else @@ -341,22 +366,22 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) // we have to have a valid device info if (nanoDevice.DebugEngine.TargetInfo != null) { - Console.WriteLine($" Target: {nanoDevice.DebugEngine.TargetInfo.TargetName}"); - Console.WriteLine($" Platform: {nanoDevice.DebugEngine.TargetInfo.PlatformName}"); - Console.WriteLine($" Type: {nanoDevice.DebugEngine.TargetInfo.PlatformInfo}"); - Console.WriteLine($" CLR Version: {nanoDevice.DebugEngine.TargetInfo.CLRVersion}"); - Console.WriteLine($" Booter Version: {nanoDevice.DebugEngine.TargetInfo.CLRVersion}"); + OutputWriter.WriteLine($" Target: {nanoDevice.DebugEngine.TargetInfo.TargetName}"); + OutputWriter.WriteLine($" Platform: {nanoDevice.DebugEngine.TargetInfo.PlatformName}"); + OutputWriter.WriteLine($" Type: {nanoDevice.DebugEngine.TargetInfo.PlatformInfo}"); + OutputWriter.WriteLine($" CLR Version: {nanoDevice.DebugEngine.TargetInfo.CLRVersion}"); + OutputWriter.WriteLine($" Booter Version: {nanoDevice.DebugEngine.TargetInfo.CLRVersion}"); } } - Console.WriteLine(""); + OutputWriter.WriteLine(""); } } - Console.WriteLine("------------------------------------------"); + OutputWriter.WriteLine("------------------------------------------"); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } catch (Exception ex) { @@ -418,54 +443,55 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) #region target processing // if a target name was specified, try to be smart and set the platform accordingly (in case it wasn't specified) - if (o.Platform == null - && !string.IsNullOrEmpty(o.TargetName)) + if (!string.IsNullOrEmpty(o.TargetName)) { // check for invalid options passed with platform option - if (o.NanoDevice) + if (o.NanoDevice || o.IdentifyFirmware) { _exitCode = ExitCodes.E9000; - _extraMessage = "Incompatible options combined with --platform."; + _extraMessage = "Incompatible options combined with --targetname."; return; } - - // easiest one: ESP32 - if (o.TargetName.StartsWith("ESP") - || o.TargetName.StartsWith("M5") - || o.TargetName.StartsWith("FEATHER") - || o.TargetName.StartsWith("ESPKALUGA")) - { - o.Platform = SupportedPlatform.esp32; - } - else if ( - o.TargetName.StartsWith("ST") - || o.TargetName.StartsWith("MBN_QUAIL") - || o.TargetName.StartsWith("NETDUINO3") - || o.TargetName.StartsWith("GHI") - || o.TargetName.StartsWith("IngenuityMicro") - || o.TargetName.StartsWith("WeAct") - || o.TargetName.StartsWith("ORGPAL") - || o.TargetName.StartsWith("Pyb") - || o.TargetName.StartsWith("NESHTEC_NESHNODE_V") - ) - { - // candidates for STM32 - o.Platform = SupportedPlatform.stm32; - } - else if (o.TargetName.StartsWith("TI")) - { - // candidates for TI CC13x2 - o.Platform = SupportedPlatform.ti_simplelink; - } - else if (o.TargetName.StartsWith("SL")) - { - // candidates for Silabs GG11 - o.Platform = SupportedPlatform.gg11; - } - else + if (o.Platform == null) { - // other supported platforms will go here - // in case a wacky target is entered by the user, the package name will be checked against Cloudsmith repo + // easiest one: ESP32 + if (o.TargetName.StartsWith("ESP") + || o.TargetName.StartsWith("M5") + || o.TargetName.StartsWith("FEATHER") + || o.TargetName.StartsWith("ESPKALUGA")) + { + o.Platform = SupportedPlatform.esp32; + } + else if ( + o.TargetName.StartsWith("ST") + || o.TargetName.StartsWith("MBN_QUAIL") + || o.TargetName.StartsWith("NETDUINO3") + || o.TargetName.StartsWith("GHI") + || o.TargetName.StartsWith("IngenuityMicro") + || o.TargetName.StartsWith("WeAct") + || o.TargetName.StartsWith("ORGPAL") + || o.TargetName.StartsWith("Pyb") + || o.TargetName.StartsWith("NESHTEC_NESHNODE_V") + ) + { + // candidates for STM32 + o.Platform = SupportedPlatform.stm32; + } + else if (o.TargetName.StartsWith("TI")) + { + // candidates for TI CC13x2 + o.Platform = SupportedPlatform.ti_simplelink; + } + else if (o.TargetName.StartsWith("SL")) + { + // candidates for Silabs GG11 + o.Platform = SupportedPlatform.gg11; + } + else + { + // other supported platforms will go here + // in case a wacky target is entered by the user, the package name will be checked against Cloudsmith repo + } } } @@ -522,6 +548,58 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) #endregion + #region firmware archive update if no device is required + if (o.UpdateFwArchive) + { + // check for invalid options passed with platform option + if (o.FromFwArchive) + { + _exitCode = ExitCodes.E9000; + _extraMessage = "Incompatible option --fromfwarchive combined with --updatefwarchive."; + return; + } + if (string.IsNullOrEmpty(o.FwArchivePath)) + { + _exitCode = ExitCodes.E9000; + _extraMessage = $"--fwarchivepath is required when --updatefwarchive is specified."; + return; + } + + if (o.Platform is null && string.IsNullOrEmpty(o.TargetName)) + { + _exitCode = ExitCodes.E9000; + _extraMessage = $"--platform or --target is required when --updatefwarchive is specified."; + return; + } + + // The packages can be downloaded without device connection + _exitCode = await new FirmwareArchiveManager(o.FwArchivePath).DownloadFirmwareFromRepository( + o.Preview, + o.Platform, + o.TargetName, + o.FwVersion, + _verbosityLevel); + return; + } + + // From now on the archive can only be used as a source of firmware + if (string.IsNullOrWhiteSpace(o.FwArchivePath)) + { + if (o.FromFwArchive) + { + _exitCode = ExitCodes.E9000; + _extraMessage = $"--fwarchivepath is required when --fromfwarchive is specified."; + return; + } + } + else if (!o.FromFwArchive) + { + _exitCode = ExitCodes.E9000; + _extraMessage = $"--fromfwarchive is required when --fwarchivepath is specified."; + return; + } + #endregion + #region ESP32 platform options if (o.Platform == SupportedPlatform.esp32) @@ -702,18 +780,18 @@ private static void DisplayNoOperationMessage() .AddPreOptionsLine("") .AddOptions(result); - Console.WriteLine(helpText.ToString()); + OutputWriter.WriteLine(helpText.ToString()); } private static void DisplayBoardDetails(List boards) { foreach (var boardName in boards.Select(m => m.Name).Distinct()) { - Console.WriteLine($" {boardName}"); + OutputWriter.WriteLine($" {boardName}"); foreach (var board in boards.Where(m => m.Name == boardName).OrderBy(m => m.Name).Take(3)) { - Console.WriteLine($" {board.Version}"); + OutputWriter.WriteLine($" {board.Version}"); } } } @@ -725,35 +803,35 @@ private static void OutputError(ExitCodes errorCode, bool outputMessage, string return; } - Console.ForegroundColor = ConsoleColor.Red; + OutputWriter.ForegroundColor = ConsoleColor.Red; if (outputMessage) { - Console.Write($"Error {errorCode}"); + OutputWriter.Write($"Error {errorCode}"); var exitCodeDisplayName = errorCode.GetAttribute(); if (!string.IsNullOrEmpty(exitCodeDisplayName.Name)) { - Console.Write($": {exitCodeDisplayName.Name}"); + OutputWriter.Write($": {exitCodeDisplayName.Name}"); } if (string.IsNullOrEmpty(extraMessage)) { - Console.WriteLine(); + OutputWriter.WriteLine(); } else { - Console.WriteLine($" ({extraMessage})"); + OutputWriter.WriteLine($" ({extraMessage})"); } } else { - Console.Write($"{errorCode}"); - Console.WriteLine(); + OutputWriter.Write($"{errorCode}"); + OutputWriter.WriteLine(); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; } } } diff --git a/nanoFirmwareFlasher.Tool/SilabsManager.cs b/nanoFirmwareFlasher.Tool/SilabsManager.cs index 260fd2d3..2b908cf6 100644 --- a/nanoFirmwareFlasher.Tool/SilabsManager.cs +++ b/nanoFirmwareFlasher.Tool/SilabsManager.cs @@ -1,10 +1,7 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -37,29 +34,39 @@ public SilabsManager(Options options, VerbosityLevel verbosityLevel) /// public async Task ProcessAsync() { + if (_options.IdentifyFirmware) + { + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine(); + OutputWriter.WriteLine($"Cannot determine the best matching target for a {SupportedPlatform.gg11} device."); + OutputWriter.WriteLine(); + OutputWriter.ForegroundColor = ConsoleColor.White; + return ExitCodes.OK; + } + if (_options.ListJLinkDevices) { var connecteDevices = JLinkDevice.ListDevices(); - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; if (connecteDevices.Count == 0) { - Console.WriteLine("No J-Link devices found"); + OutputWriter.WriteLine("No J-Link devices found"); } else { - Console.WriteLine("-- Connected USB J-Link devices --"); + OutputWriter.WriteLine("-- Connected USB J-Link devices --"); foreach (string deviceId in connecteDevices) { - Console.WriteLine(deviceId); + OutputWriter.WriteLine(deviceId); } - Console.WriteLine("----------------------------------"); + OutputWriter.WriteLine("----------------------------------"); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; // done here, this command has no further processing return ExitCodes.OK; @@ -86,13 +93,13 @@ public async Task ProcessAsync() if (_verbosityLevel >= VerbosityLevel.Normal) { - Console.WriteLine($"Connected to J-Link device with ID {jlinkDevice.ProbeId}"); + OutputWriter.WriteLine($"Connected to J-Link device with ID {jlinkDevice.ProbeId}"); } if (_verbosityLevel == VerbosityLevel.Diagnostic) { - Console.WriteLine($"Firmware: {jlinkDevice.Firmare}"); - Console.WriteLine($"Hardware: {jlinkDevice.Hardware}"); + OutputWriter.WriteLine($"Firmware: {jlinkDevice.Firmare}"); + OutputWriter.WriteLine($"Hardware: {jlinkDevice.Hardware}"); } // set VCP baud rate (if requested) @@ -164,6 +171,7 @@ public async Task ProcessAsync() _options.TargetName, _options.FwVersion, _options.Preview, + _options.FromFwArchive ? _options.FwArchivePath : null, true, _options.DeploymentImage, appFlashAddress, @@ -211,6 +219,7 @@ public async Task ProcessAsync() _options.TargetName, null, false, + null, false, _options.DeploymentImage, appFlashAddress, diff --git a/nanoFirmwareFlasher.Tool/Stm32Manager.cs b/nanoFirmwareFlasher.Tool/Stm32Manager.cs index 120222cf..28695bbd 100644 --- a/nanoFirmwareFlasher.Tool/Stm32Manager.cs +++ b/nanoFirmwareFlasher.Tool/Stm32Manager.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Linq; @@ -36,6 +34,15 @@ public Stm32Manager(Options options, VerbosityLevel verbosityLevel) /// public async Task ProcessAsync() { + if (_options.IdentifyFirmware) + { + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine(); + OutputWriter.WriteLine($"Cannot determine the best matching target for a {SupportedPlatform.stm32} device."); + OutputWriter.WriteLine(); + OutputWriter.ForegroundColor = ConsoleColor.White; + return ExitCodes.OK; + } if (_options.InstallDfuDrivers) { return Stm32Operations.InstallDfuDrivers(_verbosityLevel); @@ -50,26 +57,26 @@ public async Task ProcessAsync() { var connecteDevices = StmDfuDevice.ListDevices(); - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; if (connecteDevices.Count() == 0) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("No DFU devices found"); + OutputWriter.ForegroundColor = ConsoleColor.Yellow; + OutputWriter.WriteLine("No DFU devices found"); } else { - Console.WriteLine("-- Connected DFU devices --"); + OutputWriter.WriteLine("-- Connected DFU devices --"); foreach ((string serial, string device) device in connecteDevices) { - Console.WriteLine($"{device.serial} @ {device.device}"); + OutputWriter.WriteLine($"{device.serial} @ {device.device}"); } - Console.WriteLine("---------------------------"); + OutputWriter.WriteLine("---------------------------"); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; // done here, this command has no further processing return ExitCodes.OK; @@ -79,25 +86,25 @@ public async Task ProcessAsync() { var connecteDevices = StmJtagDevice.ListDevices(); - Console.ForegroundColor = ConsoleColor.Cyan; + OutputWriter.ForegroundColor = ConsoleColor.Cyan; if (connecteDevices.Count == 0) { - Console.WriteLine("No JTAG devices found"); + OutputWriter.WriteLine("No JTAG devices found"); } else { - Console.WriteLine("-- Connected JTAG devices --"); + OutputWriter.WriteLine("-- Connected JTAG devices --"); foreach (string deviceId in connecteDevices) { - Console.WriteLine(deviceId); + OutputWriter.WriteLine(deviceId); } - Console.WriteLine("---------------------------"); + OutputWriter.WriteLine("---------------------------"); } - Console.ForegroundColor = ConsoleColor.White; + OutputWriter.ForegroundColor = ConsoleColor.White; // done here, this command has no further processing return ExitCodes.OK; @@ -126,7 +133,7 @@ public async Task ProcessAsync() if (_verbosityLevel >= VerbosityLevel.Normal) { - Console.WriteLine($"Connected to JTAG device with ID {dfuDevice.DfuId}"); + OutputWriter.WriteLine($"Connected to JTAG device with ID {dfuDevice.DfuId}"); } // set verbosity @@ -168,7 +175,7 @@ public async Task ProcessAsync() if (_verbosityLevel >= VerbosityLevel.Normal) { - Console.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); + OutputWriter.WriteLine($"Connected to JTAG device with ID {jtagDevice.JtagId}"); } // set verbosity @@ -225,6 +232,7 @@ public async Task ProcessAsync() _options.TargetName, _options.FwVersion, _options.Preview, + _options.FromFwArchive ? _options.FwArchivePath : null, true, _options.DeploymentImage, appFlashAddress, @@ -281,6 +289,7 @@ public async Task ProcessAsync() _options.TargetName, null, false, + null, false, _options.DeploymentImage, appFlashAddress, diff --git a/nanoFirmwareFlasher.Tool/TIManager.cs b/nanoFirmwareFlasher.Tool/TIManager.cs index ed1d150b..2102a637 100644 --- a/nanoFirmwareFlasher.Tool/TIManager.cs +++ b/nanoFirmwareFlasher.Tool/TIManager.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Linq; @@ -36,6 +34,16 @@ public TIManager(Options options, VerbosityLevel verbosityLevel) /// public async Task ProcessAsync() { + if (_options.IdentifyFirmware) + { + OutputWriter.ForegroundColor = ConsoleColor.Red; + OutputWriter.WriteLine(); + OutputWriter.WriteLine($"Cannot determine the best matching target for a {SupportedPlatform.ti_simplelink} device."); + OutputWriter.WriteLine(); + OutputWriter.ForegroundColor = ConsoleColor.White; + return ExitCodes.OK; + } + if (_options.TIInstallXdsDrivers) { return CC13x26x2Operations.InstallXds110Drivers(_verbosityLevel); @@ -63,6 +71,7 @@ public async Task ProcessAsync() _options.TargetName, _options.FwVersion, _options.Preview, + _options.FromFwArchive ? _options.FwArchivePath : null, true, _options.DeploymentImage, appFlashAddress, @@ -99,6 +108,7 @@ public async Task ProcessAsync() _options.TargetName, null, false, + null, false, _options.DeploymentImage, appFlashAddress, diff --git a/nanoFirmwareFlasher.sln b/nanoFirmwareFlasher.sln index 0619e94a..4f22c22d 100644 --- a/nanoFirmwareFlasher.sln +++ b/nanoFirmwareFlasher.sln @@ -11,8 +11,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.json = version.json EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nanoFirmwareFlasher.Tests", "nanoFirmwareFlasher.Tests\nanoFirmwareFlasher.Tests.csproj", "{CF76CB85-81C8-40AF-B252-84A8B43F13BA}" -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nanoFirmwareFlasher", "nanoFirmwareFlasher.Library\nanoFirmwareFlasher.Library.csproj", "{FE33E79E-5392-4469-B2BC-D0083C608808}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nanoFirmwareFlasher.Tests", "nanoFirmwareFlasher.Tests\nanoFirmwareFlasher.Tests.csproj", "{CF76CB85-81C8-40AF-B252-84A8B43F13BA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nanoFirmwareFlasher.Library", "nanoFirmwareFlasher.Library\nanoFirmwareFlasher.Library.csproj", "{FE33E79E-5392-4469-B2BC-D0083C608808}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -28,14 +29,14 @@ Global {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Debug|x64.ActiveCfg = Debug|x64 {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Debug|x64.Build.0 = Debug|x64 - {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Debug|x86.ActiveCfg = Debug|x64 {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Debug|x86.ActiveCfg = Debug|x86 {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Debug|x86.Build.0 = Debug|x86 {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|Any CPU.Build.0 = Release|Any CPU {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x64.ActiveCfg = Release|x64 {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x64.Build.0 = Release|x64 - {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x86.ActiveCfg = Release|x64 + {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x86.ActiveCfg = Release|x86 + {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x86.Build.0 = Release|x86 {CF76CB85-81C8-40AF-B252-84A8B43F13BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF76CB85-81C8-40AF-B252-84A8B43F13BA}.Debug|Any CPU.Build.0 = Debug|Any CPU {CF76CB85-81C8-40AF-B252-84A8B43F13BA}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -48,8 +49,6 @@ Global {CF76CB85-81C8-40AF-B252-84A8B43F13BA}.Release|x64.Build.0 = Release|Any CPU {CF76CB85-81C8-40AF-B252-84A8B43F13BA}.Release|x86.ActiveCfg = Release|Any CPU {CF76CB85-81C8-40AF-B252-84A8B43F13BA}.Release|x86.Build.0 = Release|Any CPU - {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x86.ActiveCfg = Release|x86 - {762BA2A1-B3E9-4E26-9491-AE11D1F1C1EA}.Release|x86.Build.0 = Release|x86 {FE33E79E-5392-4469-B2BC-D0083C608808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE33E79E-5392-4469-B2BC-D0083C608808}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE33E79E-5392-4469-B2BC-D0083C608808}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -67,7 +66,7 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {AF6C2B32-984F-41C2-B0F7-B895E45A1BB5} SolutionGuid = {56E99FF7-1E34-4FB8-8E3C-F98890E741D0} + SolutionGuid = {AF6C2B32-984F-41C2-B0F7-B895E45A1BB5} EndGlobalSection EndGlobal diff --git a/spelling_exclusion.dic b/spelling_exclusion.dic new file mode 100644 index 00000000..187194a4 --- /dev/null +++ b/spelling_exclusion.dic @@ -0,0 +1,14 @@ +nano +color +nano +psram +nanoff +cloudsmith +cleanup +booter +listtargets +Espressif +esptool +Uniflash +Silabs +Orgpal