From d0b9fbe01f95c8375e76bff8ece159620bb4fa7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 22 Aug 2022 19:04:48 +0100 Subject: [PATCH] Add support for direct operations to nano devices (#151) --- .../CC13x26x2Firmware.cs | 13 +- .../CC13x26x2Operations.cs | 2 +- .../CantConnectToNanoDeviceException.cs | 33 + .../NanoDeviceOperationFailedException.cs | 33 + nanoFirmwareFlasher.Library/ExitCodes.cs | 23 +- .../FirmwarePackage.cs | 166 ++++- .../FirmwarePackageFactory.cs | 68 ++ nanoFirmwareFlasher.Library/JLinkFirmware.cs | 65 +- .../NanoDeviceOperations.cs | 619 ++++++++++++++++++ nanoFirmwareFlasher.Library/SilinkCli.cs | 3 +- nanoFirmwareFlasher.Library/Stm32Firmware.cs | 80 +-- .../Stm32Operations.cs | 2 +- .../nanoFirmwareFlasher.Library.csproj | 5 +- nanoFirmwareFlasher.Tool/Options.cs | 31 +- nanoFirmwareFlasher.Tool/Program.cs | 141 +++- .../nanoFirmwareFlasher.Tool.csproj | 5 +- version.json | 2 +- 17 files changed, 1131 insertions(+), 160 deletions(-) create mode 100644 nanoFirmwareFlasher.Library/Exceptions/CantConnectToNanoDeviceException.cs create mode 100644 nanoFirmwareFlasher.Library/Exceptions/NanoDeviceOperationFailedException.cs create mode 100644 nanoFirmwareFlasher.Library/FirmwarePackageFactory.cs create mode 100644 nanoFirmwareFlasher.Library/NanoDeviceOperations.cs diff --git a/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs b/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs index 6e104322..37b173fd 100644 --- a/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs +++ b/nanoFirmwareFlasher.Library/CC13x26x2Firmware.cs @@ -13,8 +13,6 @@ namespace nanoFramework.Tools.FirmwareFlasher /// internal class CC13x26x2Firmware : FirmwarePackage { - public string nanoCLRFile { get; private set; } - public CC13x26x2Firmware( string targetName, string fwVersion, @@ -26,17 +24,10 @@ public CC13x26x2Firmware( { } - internal new async System.Threading.Tasks.Task DownloadAndExtractAsync() + internal new System.Threading.Tasks.Task DownloadAndExtractAsync() { // perform download and extract - var executionResult = await base.DownloadAndExtractAsync(); - - if (executionResult == ExitCodes.OK) - { - nanoCLRFile = Directory.EnumerateFiles(LocationPath, "nanoCLR.hex").FirstOrDefault(); - } - - return executionResult; + return base.DownloadAndExtractAsync(); } } } diff --git a/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs b/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs index 5abf5886..af200876 100644 --- a/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs +++ b/nanoFirmwareFlasher.Library/CC13x26x2Operations.cs @@ -68,7 +68,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( if (updateFw) { - filesToFlash.Add(firmware.nanoCLRFile); + filesToFlash.Add(firmware.NanoClrFile); } // need to include application file? diff --git a/nanoFirmwareFlasher.Library/Exceptions/CantConnectToNanoDeviceException.cs b/nanoFirmwareFlasher.Library/Exceptions/CantConnectToNanoDeviceException.cs new file mode 100644 index 00000000..b13c7fa7 --- /dev/null +++ b/nanoFirmwareFlasher.Library/Exceptions/CantConnectToNanoDeviceException.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Runtime.Serialization; + +namespace nanoFramework.Tools.FirmwareFlasher +{ + /// + /// Couldn't open the specified .NET nanoFramework device. + /// + [Serializable] + public class CantConnectToNanoDeviceException : Exception + { + public CantConnectToNanoDeviceException() + { + } + + public CantConnectToNanoDeviceException(string message) : base(message) + { + } + + public CantConnectToNanoDeviceException(string message, Exception innerException) : base(message, innerException) + { + } + + protected CantConnectToNanoDeviceException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/nanoFirmwareFlasher.Library/Exceptions/NanoDeviceOperationFailedException.cs b/nanoFirmwareFlasher.Library/Exceptions/NanoDeviceOperationFailedException.cs new file mode 100644 index 00000000..6b117b9e --- /dev/null +++ b/nanoFirmwareFlasher.Library/Exceptions/NanoDeviceOperationFailedException.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Runtime.Serialization; + +namespace nanoFramework.Tools.FirmwareFlasher +{ + /// + /// Verification of DFU write failed. + /// + [Serializable] + public class NanoDeviceOperationFailedException : Exception + { + public NanoDeviceOperationFailedException() + { + } + + public NanoDeviceOperationFailedException(string message) : base(message) + { + } + + public NanoDeviceOperationFailedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected NanoDeviceOperationFailedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/nanoFirmwareFlasher.Library/ExitCodes.cs b/nanoFirmwareFlasher.Library/ExitCodes.cs index f993746b..83577c9f 100644 --- a/nanoFirmwareFlasher.Library/ExitCodes.cs +++ b/nanoFirmwareFlasher.Library/ExitCodes.cs @@ -56,6 +56,28 @@ public enum ExitCodes [Display(Name = "Failed to start execition on the connected device.")] E1006 = 1006, + /////////////////////// + // nanoDevice Errors // + /////////////////////// + + /// + /// Error connecting to nano device. + /// + [Display(Name = "Error connecting to nano device.")] + E2000 = 2000, + + /// + /// Error connecting to nano device. + /// + [Display(Name = "Error occurred with listing nano devices.")] + E2001 = 2001, + + /// + /// Error executing operation with nano device. + /// + [Display(Name = "Error executing operation with nano device.")] + E2002 = 2002, + //////////////////////// // ESP32 tools Errors // //////////////////////// @@ -301,6 +323,5 @@ public enum ExitCodes /// [Display(Name = "Error occured when clearing the firmware cache location.")] E9014 = 9014, - } } diff --git a/nanoFirmwareFlasher.Library/FirmwarePackage.cs b/nanoFirmwareFlasher.Library/FirmwarePackage.cs index 64bee5a3..16ded79b 100644 --- a/nanoFirmwareFlasher.Library/FirmwarePackage.cs +++ b/nanoFirmwareFlasher.Library/FirmwarePackage.cs @@ -3,6 +3,8 @@ // See LICENSE file in the project root for full license information. // +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.FirmwareFlasher; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -31,7 +33,7 @@ public abstract class FirmwarePackage : IDisposable private readonly bool _preview; private const string _readmeContent = "This folder contains nanoFramework firmware files. Can safely be removed."; - + /// /// Path with the base location for firmware packages. /// @@ -58,6 +60,40 @@ public static string LocationPathBase public VerbosityLevel Verbosity { get; internal set; } + /// + /// Path to nanoBooter file. Hex format. + /// + /// For the binary file please use the property. + public string NanoBooterFile { get; internal set; } + + /// + /// Path to nanoBooter file. Binary format. + /// + /// For the HEX format file please use the property. + public string NanoBooterFileBinary => NanoBooterFile.Replace(".hex", ".bin"); + + /// + /// Path to nanoCLR file. Hex format. + /// + /// For the binary file please use the property. + public string NanoClrFile { get; internal set; } + + /// + /// Path to nanoCLR file. Binary format. + /// + /// For the HEX format file please use the property. + public string NanoClrFileBinary => NanoClrFile.Replace(".hex", ".bin"); + + /// + /// The address of nanoCLR in flash. + /// + public uint ClrStartAddress { get; internal set; } + + /// + /// The address of nanoBooter in flash. + /// + public object BooterStartAddress { get; internal set; } + static FirmwarePackage() { _cloudsmithClient = new HttpClient(); @@ -65,6 +101,17 @@ static FirmwarePackage() _cloudsmithClient.DefaultRequestHeaders.Add("Accept", "*/*"); } + /// + /// + /// + /// + protected FirmwarePackage(NanoDeviceBase nanoDevice) + { + _targetName = nanoDevice.TargetName; + Version = ""; + _preview = false; + } + /// /// Constructor /// @@ -148,7 +195,7 @@ 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) - protected async Task DownloadAndExtractAsync() + internal async Task DownloadAndExtractAsync() { string fwFileName = null; @@ -223,7 +270,7 @@ protected async Task DownloadAndExtractAsync() } downloadUrl = downloadResult.Url; - + // update with version from package about to be downloaded Version = downloadResult.Version; @@ -380,6 +427,8 @@ protected async Task DownloadAndExtractAsync() .ToList() .ForEach(f => f.Delete()); + PostProcessDownloadAndExtract(); + if (Verbosity >= VerbosityLevel.Normal) { Console.ForegroundColor = ConsoleColor.Yellow; @@ -627,6 +676,117 @@ void IDisposable.Dispose() internal record struct DownloadUrlResult(string Url, string Version, ExitCodes Outcome) { } + + private void FindBooterStartAddress() + { + uint address; + + // find out what's the CLR block start + + // do this by reading the HEX format file... + var textLines = File.ReadAllLines(NanoBooterFile); + + // ... and decoding the start address + var addressRecord = textLines.FirstOrDefault(); + + // 1st line is an Extended Segment Address Records (HEX86) + // format ":02000004FFFFFC" + + // perform sanity checks + if (addressRecord == null || + addressRecord.Length != 15 || + addressRecord.Substring(0, 9) != ":02000004") + { + // wrong format + throw new FormatException("Wrong data in nanoBooter file"); + } + + // looking good, grab the upper 16bits + address = (uint)int.Parse(addressRecord.Substring(9, 4), System.Globalization.NumberStyles.HexNumber); + address <<= 16; + + // now the 2nd line to get the lower 16 bits of the address + addressRecord = textLines.Skip(1).FirstOrDefault(); + + // 2nd line is a Data Record + // format ":10246200464C5549442050524F46494C4500464C33" + + // perform sanity checks + if (addressRecord == null || + addressRecord.Substring(0, 1) != ":" || + addressRecord.Length < 7) + { + // wrong format + throw new FormatException("Wrong data in nanoBooter file"); + } + + // looking good, grab the lower 16bits + address += (uint)int.Parse(addressRecord.Substring(3, 4), System.Globalization.NumberStyles.HexNumber); + + BooterStartAddress = address; + } + + private void FindClrStartAddress() + { + uint address; + + // find out what's the CLR block start + + // do this by reading the HEX format file... + var textLines = File.ReadAllLines(NanoClrFile); + + // ... and decoding the start address + var addressRecord = textLines.FirstOrDefault(); + + // 1st line is an Extended Segment Address Records (HEX86) + // format ":02000004FFFFFC" + + // perform sanity checks + if (addressRecord == null || + addressRecord.Length != 15 || + addressRecord.Substring(0, 9) != ":02000004") + { + // wrong format + throw new FormatException("Wrong data in nanoClr file"); + } + + // looking good, grab the upper 16bits + address = (uint)int.Parse(addressRecord.Substring(9, 4), System.Globalization.NumberStyles.HexNumber); + address <<= 16; + + // now the 2nd line to get the lower 16 bits of the address + addressRecord = textLines.Skip(1).FirstOrDefault(); + + // 2nd line is a Data Record + // format ":10246200464C5549442050524F46494C4500464C33" + + // perform sanity checks + if (addressRecord == null || + addressRecord.Substring(0, 1) != ":" || + addressRecord.Length < 7) + { + // wrong format + throw new FormatException("Wrong data in nanoClr file"); + } + + // looking good, grab the lower 16bits + address += (uint)int.Parse(addressRecord.Substring(3, 4), System.Globalization.NumberStyles.HexNumber); + + ClrStartAddress = address; + } + + internal void PostProcessDownloadAndExtract() + { + NanoBooterFile = Directory.EnumerateFiles(LocationPath, "nanoBooter.hex").FirstOrDefault(); + NanoClrFile = Directory.EnumerateFiles(LocationPath, "nanoCLR.hex").FirstOrDefault(); + + if (NanoBooterFile != null) + { + FindBooterStartAddress(); + } + + FindClrStartAddress(); + } } } diff --git a/nanoFirmwareFlasher.Library/FirmwarePackageFactory.cs b/nanoFirmwareFlasher.Library/FirmwarePackageFactory.cs new file mode 100644 index 00000000..4506419e --- /dev/null +++ b/nanoFirmwareFlasher.Library/FirmwarePackageFactory.cs @@ -0,0 +1,68 @@ +using nanoFramework.Tools.Debugger; +using System; + +namespace nanoFramework.Tools.FirmwareFlasher +{ + //public class FirmwarePackage : FirmwarePackageBase, IDisposable where T : new() + //{ + // public Stm32Firmware DeviceFirmware { get; } + + // public FirmwarePackage(NanoDeviceBase nanoDevice) : base(nanoDevice) + // { + // if (nanoDevice is null) + // { + // throw new ArgumentNullException(nameof(nanoDevice)); + // } + + // if (nanoDevice.Platform.StartsWith("STM32")) + // { + // DeviceFirmware = new Stm32Firmware( + // nanoDevice.TargetName, + // "", + // false); + // } + // else if (nanoDevice.Platform.StartsWith("STM32")) + // { + // DeviceFirmware = new JLinkFirmware( + // nanoDevice.TargetName, + // "", + // false); + + // } + // } + + // public FirmwarePackage(string targetName, string fwVersion, bool preview) : base(targetName, fwVersion, preview) + // { + // } + + //} + public class FirmwarePackageFactory + { + public static FirmwarePackage GetFirmwarePackage(NanoDeviceBase nanoDevice) + { + if (nanoDevice is null) + { + throw new ArgumentNullException(nameof(nanoDevice)); + } + + if (nanoDevice.Platform.StartsWith("STM32")) + { + return new Stm32Firmware( + nanoDevice.TargetName, + "", + false); + } + else if (nanoDevice.Platform.StartsWith("GGECKO_S1")) + { + return new JLinkFirmware( + nanoDevice.TargetName, + "", + false); + } + else + { + throw new NotSupportedException($"FirmwarePackageFactory doesn't support generating packages for {nanoDevice.Platform} platform"); + } + } + } +} diff --git a/nanoFirmwareFlasher.Library/JLinkFirmware.cs b/nanoFirmwareFlasher.Library/JLinkFirmware.cs index 23e0d6e5..eb229a99 100644 --- a/nanoFirmwareFlasher.Library/JLinkFirmware.cs +++ b/nanoFirmwareFlasher.Library/JLinkFirmware.cs @@ -14,10 +14,6 @@ namespace nanoFramework.Tools.FirmwareFlasher /// internal class JLinkFirmware : FirmwarePackage { - public string NanoBooterFile { get; private set; } - - public string NanoClrFile { get; private set; } - public JLinkFirmware( string targetName, string fwVersion, @@ -29,67 +25,10 @@ public JLinkFirmware( { } - internal new async System.Threading.Tasks.Task DownloadAndExtractAsync() + internal new System.Threading.Tasks.Task DownloadAndExtractAsync() { // perform download and extract - var executionResult = await base.DownloadAndExtractAsync(); - - if (executionResult == ExitCodes.OK) - { - NanoBooterFile = Directory.EnumerateFiles(LocationPath, "nanoBooter.hex").FirstOrDefault(); - NanoClrFile = Directory.EnumerateFiles(LocationPath, "nanoCLR.hex").FirstOrDefault(); - } - - return executionResult; - } - - public uint GetdBooterStartAddress() - { - uint address; - - // find out what's the CLR block start - - // do this by reading the HEX format file... - var textLines = File.ReadAllLines(NanoBooterFile); - - // ... and decoding the start address - var addressRecord = textLines.FirstOrDefault(); - - // 1st line is an Extended Segment Address Records (HEX86) - // format ":02000004FFFFFC" - - // perform sanity checks - if (addressRecord == null || - addressRecord.Length != 15 || - addressRecord.Substring(0, 9) != ":02000004") - { - // wrong format - throw new FormatException("Wrong data in nanoBooter file"); - } - - // looking good, grab the upper 16bits - address = (uint)int.Parse(addressRecord.Substring(9, 4), System.Globalization.NumberStyles.HexNumber); - address <<= 16; - - // now the 2nd line to get the lower 16 bits of the address - addressRecord = textLines.Skip(1).FirstOrDefault(); - - // 2nd line is a Data Record - // format ":10246200464C5549442050524F46494C4500464C33" - - // perform sanity checks - if (addressRecord == null || - addressRecord.Substring(0, 1) != ":" || - addressRecord.Length < 7) - { - // wrong format - throw new FormatException("Wrong data in nanoBooter file"); - } - - // looking good, grab the lower 16bits - address += (uint)int.Parse(addressRecord.Substring(3, 4), System.Globalization.NumberStyles.HexNumber); - - return address; + return base.DownloadAndExtractAsync(); } } } diff --git a/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs b/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs new file mode 100644 index 00000000..d27cc11e --- /dev/null +++ b/nanoFirmwareFlasher.Library/NanoDeviceOperations.cs @@ -0,0 +1,619 @@ +//// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +//// + +using nanoFramework.Tools.Debugger; +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace nanoFramework.Tools.FirmwareFlasher +{ + /// + /// Class with operations available for .NET nanoFramework devices. + /// + public class NanoDeviceOperations : IDisposable + { + private bool _disposedValue; + private NanoDeviceBase _nanoDevice; + private readonly PortBase _serialDebuggerPort; + + /// + /// Class with operations to perform with .NET nanoFramework devices. + /// + public NanoDeviceOperations() + { + _serialDebuggerPort = PortBase.CreateInstanceForSerial(); + } + + /// + /// List connected .NET nanoFramework devices. + /// + /// An observable collection of . devices + public ObservableCollection ListDevices() + { + while (!_serialDebuggerPort.IsDevicesEnumerationComplete) + { + Thread.Sleep(100); + } + + return _serialDebuggerPort.NanoFrameworkDevices; + } + + /// + /// Gets device details of the requested .NET nanoFramework device. + /// + /// Serial port name where the device is connected to. + /// The object for the requested device. if there is no .NET nanoFramework device in the specified . + /// + /// + /// Couldn't connect to specified nano device. + /// + /// + /// --OR-- + /// + /// + /// Couldn't retrieve device details from the nano device. + /// + /// + public ExitCodes GetDeviceDetails(string serialPort) + { + NanoDeviceBase nanoDevice = null; + + if (ReadDetailsFromDevice(serialPort, ref nanoDevice)) + { + // check that we are in CLR + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) + { + // we have to have a valid device info + if (nanoDevice.DeviceInfo.Valid) + { + Console.ForegroundColor = ConsoleColor.Cyan; + + Console.WriteLine(""); + Console.WriteLine($"{nanoDevice.DeviceInfo}"); + + Console.ForegroundColor = ConsoleColor.White; + + return ExitCodes.OK; + } + else + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't retrieve device details from nano device."); + } + } + else + { + // we are in booter, can only get TargetInfo + // we have to have a valid device info + if (nanoDevice.DebugEngine.TargetInfo != null) + { + Console.ForegroundColor = ConsoleColor.Cyan; + + Console.WriteLine(""); + Console.WriteLine($"{nanoDevice.DebugEngine.TargetInfo}"); + + Console.ForegroundColor = ConsoleColor.White; + + return ExitCodes.OK; + } + else + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't retrieve device details from nano device."); + } + } + } + else + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't connect to specified nano device."); + } + + return ExitCodes.E2000; + } + + /// + /// Update CLR on specified nano device. + /// + /// Serial port name where the device is connected to. + /// Set verbosity level of progress and error messages. + /// The with the operation result. + /// + /// + /// Couldn't connect to specified nano device. + /// + /// + /// --OR-- + /// + /// + /// Couldn't retrieve device details from the nano device. + /// + /// + public async Task UpdateDeviceClrAsync( + string serialPort, + VerbosityLevel verbosity = VerbosityLevel.Quiet) + { + if (serialPort is null) + { + throw new ArgumentNullException(nameof(serialPort)); + } + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Getting details from nano device..."); + } + + NanoDeviceBase nanoDevice = null; + ReadDetailsFromDevice(serialPort, ref nanoDevice); + + // sanity checks + if (nanoDevice == null + || nanoDevice.TargetName == null + || nanoDevice.Platform == null) + { + // can't update this device + throw new NanoDeviceOperationFailedException($"Missing details from {nanoDevice?.TargetName} to perform update operation."); + } + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("OK"); + + Console.ForegroundColor = ConsoleColor.White; + } + else + { + Console.WriteLine(""); + } + + if (verbosity >= VerbosityLevel.Normal) + { + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.Cyan; + + Console.WriteLine($"Connected to nano device: {nanoDevice.Description}"); + Console.WriteLine($"{nanoDevice.DeviceInfo.ClrBuildVersion}"); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.White; + } + + // get firmware package + var fwPackage = FirmwarePackageFactory.GetFirmwarePackage(nanoDevice); + var downloadResult = await fwPackage.DownloadAndExtractAsync(); + + if (downloadResult == ExitCodes.OK) + { + if (nanoDevice.DebugEngine.Connect( + 1000, + true, + true)) + { + Version currentClrVersion = null; + + // try to store CLR version + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) + { + if (nanoDevice.DeviceInfo.Valid) + { + currentClrVersion = nanoDevice.DeviceInfo.SolutionBuildVersion; + } + } + + // update conditions: + // 1. Running CLR _and_ the new version is higher + // 2. Running nanoBooter and there is no version information on the CLR (presumably because there is no CLR installed) + if (Version.Parse(fwPackage.Version) > nanoDevice.CLRVersion) + { + bool attemptToLaunchBooter = false; + + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) + { + // any update has to be handled by nanoBooter, so let's have it running + try + { + if (verbosity > VerbosityLevel.Normal) + { + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write("Launching nanoBooter..."); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.White; + } + + attemptToLaunchBooter = nanoDevice.ConnectToNanoBooter(); + + if (!attemptToLaunchBooter) + { + // check for version where the software reboot to nanoBooter was made available + if (currentClrVersion != null && + nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) + { + Console.WriteLine(""); + + throw new NanoDeviceOperationFailedException("The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); + } + } + + if (verbosity > VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("OK"); + Console.ForegroundColor = ConsoleColor.White; + } + } + catch + { + // this reboot step can go wrong and there's no big deal with that + } + } + else + { + attemptToLaunchBooter = true; + } + + if (attemptToLaunchBooter && + nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) + { + // get address for CLR block expected by device + var clrAddress = nanoDevice.GetCLRStartAddress(); + + // compare with address on the fw packages + if (clrAddress != + fwPackage.ClrStartAddress) + { + // CLR addresses don't match, can't proceed with update + throw new NanoDeviceOperationFailedException("Can't update device. CLR addresses are different. Please update nanoBooter manually."); + } + + await Task.Yield(); + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Starting update to CLR v{fwPackage.Version}..."); + } + + try + { + await Task.Yield(); + + if (nanoDevice.DeployBinaryFile( + fwPackage.NanoClrFileBinary, + fwPackage.ClrStartAddress, + null)) + { + await Task.Yield(); + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("OK"); + Console.ForegroundColor = ConsoleColor.White; + } + } + + if (attemptToLaunchBooter) + { + // try to reboot target + if (verbosity > VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write("Rebooting..."); + } + + nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); + + if (verbosity > VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("OK"); + Console.ForegroundColor = ConsoleColor.White; + } + + return ExitCodes.OK; + } + } + catch (Exception ex) + { + throw new NanoDeviceOperationFailedException($"Exception occurred when performing update ({ex.Message})."); + } + } + else + { + if (attemptToLaunchBooter) + { + // only report this as an error if the launch was successful + throw new NanoDeviceOperationFailedException("Failed to launch nanoBooter. Quitting update."); + } + } + } + else + { + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR + && (fwPackage.Version == nanoDevice.DeviceInfo.ClrBuildVersion.ToString())) + { + if (verbosity >= VerbosityLevel.Normal) + { + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.Yellow; + + Console.WriteLine("Nothing to update as device is already running the requested version."); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.White; + } + + // done here + return ExitCodes.OK; + } + } + } + else + { + throw new NanoDeviceOperationFailedException("Can't connect to device. Quitting update."); + } + } + + return downloadResult; + } + + /// + /// Deploy application on specified nano device. + /// + /// Serial port name where the device is connected to. + /// Path to the binary file to deploy in the connected nano device. + /// Set verbosity level of progress and error messages. + /// The with the operation result. + public ExitCodes DeployApplication( + string serialPort, + string deploymentPackage, + VerbosityLevel verbosity = VerbosityLevel.Quiet) + { + if (serialPort is null) + { + return ExitCodes.E2001; + } + + if (deploymentPackage is null) + { + throw new ArgumentNullException("No deployment package provided."); + } + + if (!File.Exists(deploymentPackage)) + { + throw new FileNotFoundException("Couldn't find the deployment file at the specified path."); + } + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Getting details from nano device..."); + } + + NanoDeviceBase nanoDevice = null; + ReadDetailsFromDevice(serialPort, ref nanoDevice); + + // sanity checks + if (nanoDevice == null + || nanoDevice.TargetName == null + || nanoDevice.Platform == null) + { + // can't update this device + throw new NanoDeviceOperationFailedException($"Missing details from {nanoDevice?.TargetName} to perform deployment operation."); + } + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("OK"); + + Console.ForegroundColor = ConsoleColor.White; + } + else + { + Console.WriteLine(""); + } + + if (nanoDevice.DebugEngine.Connect( + 1000, + true, + true)) + { + if (verbosity >= VerbosityLevel.Normal) + { + Console.Write($"Erasing deployment block storage..."); + } + + var retryCount = 0; + + retryErase: + + var eraseResult = nanoDevice.Erase( + EraseOptions.Deployment, + null, + null); + + if (!eraseResult) + { + if (retryCount < 3) + { + // Give it a bit of time + Thread.Sleep(400); + + retryCount++; + + goto retryErase; + } + else + { + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Too many retries erasing nano device."); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.White; + + return ExitCodes.E2002; + } + } + + // needed for slow devices + Thread.Sleep(200); + + if (verbosity >= VerbosityLevel.Normal) + { + Console.Write($"Erasing deployment block storage..."); + } + + if (!nanoDevice.DeployBinaryFile( + deploymentPackage, + (uint)nanoDevice.GetDeploymentStartAddress(), + null)) + { + + if (verbosity >= VerbosityLevel.Normal) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("FAILED!"); + + return ExitCodes.E2002; + } + } + + // all good here + return ExitCodes.OK; + } + else + { + throw new NanoDeviceOperationFailedException("Can't connect to device. Quitting update."); + } + } + + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // clean-up debugger port instances and devices + _serialDebuggerPort?.StopDeviceWatchers(); + } + + _nanoDevice?.Disconnect(true); + _nanoDevice = null; + + _disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private bool ReadDetailsFromDevice(string serialPort, ref NanoDeviceBase nanoDevice) + { + while (!_serialDebuggerPort.IsDevicesEnumerationComplete) + { + Thread.Sleep(100); + } + + if (!_serialDebuggerPort.NanoFrameworkDevices.Any()) + { + return false; + } + + nanoDevice = _serialDebuggerPort.NanoFrameworkDevices.Where(m => m.ConnectionId == serialPort).FirstOrDefault(); + + if (nanoDevice != null) + { + // check if debugger engine exists + if (nanoDevice.DebugEngine == null) + { + nanoDevice.CreateDebugEngine(); + } + + // connect to the device + if (nanoDevice.DebugEngine.Connect( + false, + true)) + { + // check that we are in CLR + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) + { + try + { + // get device info + var deviceInfo = nanoDevice.GetDeviceInfo(true); + + // we have to have a valid device info + if (deviceInfo.Valid) + { + return true; + } + else + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't retrieve device details from nano device."); + } + } + catch + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't retrieve device details from nano device."); + } + } + else + { + // we are in booter, can only get TargetInfo + try + { + // get device info + var deviceInfo = nanoDevice.DebugEngine?.TargetInfo; + + // we have to have a valid device info + if (deviceInfo != null) + { + return true; + } + else + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't retrieve device details from nano device."); + } + } + catch + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't retrieve device details from nano device."); + } + } + } + else + { + // report issue + throw new CantConnectToNanoDeviceException("Couldn't connect to specified nano device."); + } + } + + // default to false + return false; + } + } +} diff --git a/nanoFirmwareFlasher.Library/SilinkCli.cs b/nanoFirmwareFlasher.Library/SilinkCli.cs index b3b6e0b0..1f93f6cc 100644 --- a/nanoFirmwareFlasher.Library/SilinkCli.cs +++ b/nanoFirmwareFlasher.Library/SilinkCli.cs @@ -177,11 +177,10 @@ public static ExitCodes SetVcpBaudRate( Console.WriteLine("FAILED!"); Console.WriteLine(""); - Console.ForegroundColor = ConsoleColor.Red; + Console.ForegroundColor = ConsoleColor.White; Console.WriteLine($"{opResult.Replace("PK> ", "")}"); - Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(""); } else if (verbosity > VerbosityLevel.Normal) diff --git a/nanoFirmwareFlasher.Library/Stm32Firmware.cs b/nanoFirmwareFlasher.Library/Stm32Firmware.cs index c3c60cde..530ca441 100644 --- a/nanoFirmwareFlasher.Library/Stm32Firmware.cs +++ b/nanoFirmwareFlasher.Library/Stm32Firmware.cs @@ -3,6 +3,7 @@ // See LICENSE file in the project root for full license information. // +using nanoFramework.Tools.Debugger; using System; using System.Collections.Generic; using System.IO; @@ -18,12 +19,14 @@ internal class Stm32Firmware : FirmwarePackage [Obsolete("This property is discontinued and it will be removed in a future version.")] public bool HasDfuPackage => !string.IsNullOrEmpty(DfuPackage); - public string NanoBooterFile { get; private set; } - - public string NanoClrFile { get; private set; } - public string DfuPackage { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// Name of the target for this object. + /// Firmware version that will be used for this object. + /// to use preview version. to use stable version. public Stm32Firmware( string targetName, string fwVersion, @@ -35,72 +38,19 @@ public Stm32Firmware( { } - internal new async System.Threading.Tasks.Task DownloadAndExtractAsync() + /// + /// Initializes a new instance of the class. + /// + /// that will provide details when instantiating the object. + public Stm32Firmware(NanoDeviceBase nanoDevice) : base(nanoDevice) { - // perform download and extract - var executionResult = await base.DownloadAndExtractAsync(); - - if (executionResult == ExitCodes.OK) - { - var dfuFile = Directory.EnumerateFiles(LocationPath, "*.dfu").ToArray(); - if (dfuFile.Any()) - { - DfuPackage = dfuFile.First(); - } - NanoBooterFile = Directory.EnumerateFiles(LocationPath, "nanoBooter.hex").FirstOrDefault(); - NanoClrFile = Directory.EnumerateFiles(LocationPath, "nanoCLR.hex").FirstOrDefault(); - } - return executionResult; } - public uint GetdBooterStartAddress() + internal new System.Threading.Tasks.Task DownloadAndExtractAsync() { - uint address; - - // find out what's the CLR block start - - // do this by reading the HEX format file... - var textLines = File.ReadAllLines(NanoBooterFile); - - // ... and decoding the start address - var addressRecord = textLines.FirstOrDefault(); - - // 1st line is an Extended Segment Address Records (HEX86) - // format ":02000004FFFFFC" - - // perform sanity checks - if (addressRecord == null || - addressRecord.Length != 15 || - addressRecord.Substring(0, 9) != ":02000004") - { - // wrong format - throw new FormatException("Wrong data in nanoBooter file"); - } - - // looking good, grab the upper 16bits - address = (uint)int.Parse(addressRecord.Substring(9, 4), System.Globalization.NumberStyles.HexNumber); - address <<= 16; - - // now the 2nd line to get the lower 16 bits of the address - addressRecord = textLines.Skip(1).FirstOrDefault(); - - // 2nd line is a Data Record - // format ":10246200464C5549442050524F46494C4500464C33" - - // perform sanity checks - if (addressRecord == null || - addressRecord.Substring(0, 1) != ":" || - addressRecord.Length < 7) - { - // wrong format - throw new FormatException("Wrong data in nanoBooter file"); - } - - // looking good, grab the lower 16bits - address += (uint)int.Parse(addressRecord.Substring(3, 4), System.Globalization.NumberStyles.HexNumber); - - return address; + // perform download and extract + return base.DownloadAndExtractAsync(); } } } diff --git a/nanoFirmwareFlasher.Library/Stm32Operations.cs b/nanoFirmwareFlasher.Library/Stm32Operations.cs index 07e50eba..712cd944 100644 --- a/nanoFirmwareFlasher.Library/Stm32Operations.cs +++ b/nanoFirmwareFlasher.Library/Stm32Operations.cs @@ -193,7 +193,7 @@ public static async System.Threading.Tasks.Task UpdateFirmwareAsync( && operationResult == ExitCodes.OK) { // start execution on MCU from with bootloader address - dfuDevice.StartExecution($"{firmware.GetdBooterStartAddress():X8}"); + dfuDevice.StartExecution($"{firmware.BooterStartAddress:X8}"); } return operationResult; diff --git a/nanoFirmwareFlasher.Library/nanoFirmwareFlasher.Library.csproj b/nanoFirmwareFlasher.Library/nanoFirmwareFlasher.Library.csproj index 55fd9311..2d4b24f6 100644 --- a/nanoFirmwareFlasher.Library/nanoFirmwareFlasher.Library.csproj +++ b/nanoFirmwareFlasher.Library/nanoFirmwareFlasher.Library.csproj @@ -31,9 +31,9 @@ - + - + @@ -56,6 +56,7 @@ + diff --git a/nanoFirmwareFlasher.Tool/Options.cs b/nanoFirmwareFlasher.Tool/Options.cs index c66fdcab..2c4a9679 100644 --- a/nanoFirmwareFlasher.Tool/Options.cs +++ b/nanoFirmwareFlasher.Tool/Options.cs @@ -140,13 +140,6 @@ public class Options HelpText = "Path to file with CLR image. Partitions table and bootloader file will be automatically flashed to the device.")] public string Esp32ClrFile { get; set; } - [Option( - "devicedetails", - Required = false, - Default = false, - HelpText = "Reads details from ESP32 device.")] - public bool DeviceDetails { get; set; } - #endregion @@ -187,6 +180,16 @@ public class Options #endregion + #region nano device options + + [Option( + "nanodevice", + Required = false, + Default = false, + HelpText = "Operations are to be performed to a nanoFramework device.")] + public bool NanoDevice { get; set; } + + #endregion #region common options @@ -324,6 +327,20 @@ public class Options HelpText = "Clear the cache folder with firmware images.")] public bool ClearCache { get; set; } + [Option( + "listdevices", + Required = false, + Default = false, + HelpText = "List the .NET nanoFramework devices connected to the machine.")] + public bool ListDevices { get; set; } + + [Option( + "devicedetails", + Required = false, + Default = false, + HelpText = "Reads details from connected device.")] + public bool DeviceDetails { get; set; } + #endregion diff --git a/nanoFirmwareFlasher.Tool/Program.cs b/nanoFirmwareFlasher.Tool/Program.cs index 77669136..ca31056f 100644 --- a/nanoFirmwareFlasher.Tool/Program.cs +++ b/nanoFirmwareFlasher.Tool/Program.cs @@ -5,6 +5,7 @@ using CommandLine; using CommandLine.Text; +using nanoFramework.Tools.Debugger; using nanoFramework.Tools.FirmwareFlasher.Extensions; using Newtonsoft.Json; using System; @@ -16,6 +17,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; +using System.Threading; using System.Threading.Tasks; namespace nanoFramework.Tools.FirmwareFlasher @@ -28,6 +30,7 @@ internal class Program private static AssemblyInformationalVersionAttribute _informationalVersionAttribute; private static string _headerInfo; private static CopyrightInfo _copyrightInfo; + private static NanoDeviceOperations _nanoDebuggerOperations; internal static string ExecutingPath; @@ -93,6 +96,9 @@ await parsedArguments OutputError(_exitCode, _verbosityLevel > VerbosityLevel.Normal, _extraMessage); } + // force clean-up + _nanoDebuggerOperations?.Dispose(); + return (int)_exitCode; } @@ -294,11 +300,144 @@ static async Task RunOptionsAndReturnExitCodeAsync(Options o) #endregion + #region nano device management + + if (o.ListDevices) + { + _nanoDebuggerOperations = new NanoDeviceOperations(); + + try + { + var connectedDevices = _nanoDebuggerOperations.ListDevices(); + + if (connectedDevices.Count() == 0) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("No devices found"); + } + else + { + Console.WriteLine("-- Connected .NET nanoFramework devices --"); + + foreach (var nanoDevice in connectedDevices) + { + Console.WriteLine($"{nanoDevice.Description}"); + } + + Console.WriteLine("------------------------------------------"); + } + + Console.ForegroundColor = ConsoleColor.White; + } + catch (Exception ex) + { + _exitCode = ExitCodes.E2001; + _extraMessage = ex.Message; + } + + // done here, this command has no further processing + _exitCode = ExitCodes.OK; + + return; + } + + if (o.NanoDevice) + { + // COM port is mandatory for nano device operations + if (string.IsNullOrEmpty(o.SerialPort)) + { + _exitCode = ExitCodes.E6001; + return; + } + + _nanoDebuggerOperations = new NanoDeviceOperations(); + + if (o.DeviceDetails) + { + try + { + _exitCode = _nanoDebuggerOperations.GetDeviceDetails(o.SerialPort); + + // done here + return; + } + catch (CantConnectToNanoDeviceException ex) + { + _exitCode = ExitCodes.E2000; + _extraMessage = ex.Message; + + return; + } + } + else if (o.Update) + { + try + { + _exitCode = await _nanoDebuggerOperations.UpdateDeviceClrAsync( + o.SerialPort, + _verbosityLevel); + + if (_exitCode != ExitCodes.OK) + { + return; + } + } + catch (CantConnectToNanoDeviceException ex) + { + _exitCode = ExitCodes.E2001; + _extraMessage = ex.Message; + + return; + } + catch (Exception ex) + { + _exitCode = ExitCodes.E2002; + _extraMessage = ex.Message; + + return; + } + } + + if (o.Deploy) + { + try + { + _exitCode = _nanoDebuggerOperations.DeployApplication( + o.SerialPort, + o.DeploymentImage, + _verbosityLevel); + + if (_exitCode != ExitCodes.OK) + { + return; + } + } + catch (CantConnectToNanoDeviceException ex) + { + _exitCode = ExitCodes.E2001; + _extraMessage = ex.Message; + + return; + } + catch (Exception ex) + { + _exitCode = ExitCodes.E2002; + _extraMessage = ex.Message; + + return; + } + } + + return; + } + + #endregion + #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)) + && !string.IsNullOrEmpty(o.TargetName)) { // easiest one: ESP32 if (o.TargetName.StartsWith("ESP") diff --git a/nanoFirmwareFlasher.Tool/nanoFirmwareFlasher.Tool.csproj b/nanoFirmwareFlasher.Tool/nanoFirmwareFlasher.Tool.csproj index ac8287c2..bba3bdb1 100644 --- a/nanoFirmwareFlasher.Tool/nanoFirmwareFlasher.Tool.csproj +++ b/nanoFirmwareFlasher.Tool/nanoFirmwareFlasher.Tool.csproj @@ -39,6 +39,7 @@ + @@ -63,9 +64,9 @@ - + - + diff --git a/version.json b/version.json index 290aa278..29418c47 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.1", + "version": "2.2", "assemblyVersion": { "precision": "minor" },