diff --git a/cli/arguments/arguments.go b/cli/arguments/arguments.go new file mode 100644 index 00000000..7d44d7b4 --- /dev/null +++ b/cli/arguments/arguments.go @@ -0,0 +1,19 @@ +package arguments + +import ( + "github.com/spf13/cobra" +) + +// Flags contains various common flags. +// This is useful so all flags used by commands that need +// this information are consistent with each other. +type Flags struct { + Address string + Fqbn string +} + +// AddToCommand adds the flags used to set address and fqbn to the specified Command +func (f *Flags) AddToCommand(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:samd:mkr1000, arduino:mbed_nano:nanorp2040connect") + cmd.Flags().StringVarP(&f.Address, "address", "a", "", "Upload port, e.g.: COM10, /dev/ttyACM0") +} diff --git a/cli/certificates/flash.go b/cli/certificates/flash.go index e091e8b0..05789575 100644 --- a/cli/certificates/flash.go +++ b/cli/certificates/flash.go @@ -23,31 +23,27 @@ import ( "bytes" "fmt" "os" - "path/filepath" "strings" "time" - "github.com/arduino/arduino-cli/arduino/serialutils" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-fwuploader/cli/arguments" + "github.com/arduino/arduino-fwuploader/cli/common" "github.com/arduino/arduino-fwuploader/flasher" - "github.com/arduino/arduino-fwuploader/indexes" "github.com/arduino/arduino-fwuploader/indexes/download" - programmer "github.com/arduino/arduino-fwuploader/programmers" "github.com/arduino/go-paths-helper" - "github.com/arduino/go-properties-orderedmap" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( - fqbn string - address string + commonFlags arguments.Flags certificateURLs []string certificatePaths []string ) -// NewCommand created a new `version` command +// NewFlashCommand creates a new `flash` command func NewFlashCommand() *cobra.Command { command := &cobra.Command{ Use: "flash", @@ -58,121 +54,59 @@ func NewFlashCommand() *cobra.Command { " " + os.Args[0] + " certificates flash -b arduino:samd:mkr1000 -a COM10 -u arduino.cc:443 -u google.cc:443\n" + " " + os.Args[0] + " certificates flash -b arduino:samd:mkr1000 -a COM10 -f /home/me/VeriSign.cer -f /home/me/Digicert.cer\n", Args: cobra.NoArgs, - Run: run, + Run: runFlash, } - - command.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:samd:mkr1000, arduino:mbed_nano:nanorp2040connect") - command.Flags().StringVarP(&address, "address", "a", "", "Upload port, e.g.: COM10, /dev/ttyACM0") + commonFlags.AddToCommand(command) command.Flags().StringSliceVarP(&certificateURLs, "url", "u", []string{}, "List of urls to download root certificates, e.g.: arduino.cc:443") command.Flags().StringSliceVarP(&certificatePaths, "file", "f", []string{}, "List of paths to certificate file, e.g.: /home/me/Digicert.cer") return command } -func run(cmd *cobra.Command, args []string) { - packageIndex, err := indexes.GetPackageIndex() - if err != nil { - feedback.Errorf("Can't load package index: %s", err) - os.Exit(errorcodes.ErrGeneric) - } +func runFlash(cmd *cobra.Command, args []string) { - firmwareIndex, err := indexes.GetFirmwareIndex() - if err != nil { - feedback.Errorf("Can't load firmware index: %s", err) - os.Exit(errorcodes.ErrGeneric) - } - - if fqbn == "" { - feedback.Errorf("Error during certificates flashing: missing board fqbn") - os.Exit(errorcodes.ErrBadArgument) - } - - if address == "" { - feedback.Errorf("Error during certificates flashing: missing board address") - os.Exit(errorcodes.ErrBadArgument) - } + packageIndex, firmwareIndex := common.InitIndexes() + common.CheckFlags(commonFlags.Fqbn, commonFlags.Address) + board := common.GetBoard(firmwareIndex, commonFlags.Fqbn) + uploadToolDir := common.GetUploadToolDir(packageIndex, board) if len(certificateURLs) == 0 && len(certificatePaths) == 0 { feedback.Errorf("Error during certificates flashing: no certificates provided") os.Exit(errorcodes.ErrBadArgument) } - board := firmwareIndex.GetBoard(fqbn) - if board == nil { - feedback.Errorf("Can't find board with %s fqbn", fqbn) - os.Exit(errorcodes.ErrBadArgument) - } - - toolRelease := indexes.GetToolRelease(packageIndex, board.Uploader) - if toolRelease == nil { - feedback.Errorf("Error getting upload tool %s for board %s", board.Uploader, board.Fqbn) - os.Exit(errorcodes.ErrGeneric) - } - uploadToolDir, err := download.DownloadTool(toolRelease) - if err != nil { - feedback.Errorf("Error downloading tool %s: %s", board.Uploader, err) - os.Exit(errorcodes.ErrGeneric) - } - - loaderSketchPath, err := download.DownloadLoaderSketch(board.LoaderSketch) + loaderSketchPath, err := download.DownloadSketch(board.LoaderSketch) if err != nil { feedback.Errorf("Error downloading loader sketch from %s: %s", board.LoaderSketch.URL, err) os.Exit(errorcodes.ErrGeneric) } + logrus.Debugf("loader sketch downloaded in %s", loaderSketchPath.String()) loaderSketch := strings.ReplaceAll(loaderSketchPath.String(), loaderSketchPath.Ext(), "") - // Check if board needs a 1200bps touch for upload - bootloaderPort := address - if board.UploadTouch { - logrus.Info("Putting board into bootloader mode") - newUploadPort, err := serialutils.Reset(address, board.UploadWait, nil) - if err != nil { - feedback.Errorf("Error during certificates flashing: missing board address") - os.Exit(errorcodes.ErrGeneric) - } - if newUploadPort != "" { - logrus.Infof("Found port to upload Loader: %s", newUploadPort) - bootloaderPort = newUploadPort - } - } - - uploaderCommand := board.GetUploaderCommand() - uploaderCommand = strings.ReplaceAll(uploaderCommand, "{tool_dir}", filepath.FromSlash(uploadToolDir.String())) - uploaderCommand = strings.ReplaceAll(uploaderCommand, "{serial.port.file}", bootloaderPort) - uploaderCommand = strings.ReplaceAll(uploaderCommand, "{loader.sketch}", loaderSketch) - - commandLine, err := properties.SplitQuotedString(uploaderCommand, "\"", false) + programmerOut, programmerErr, err := common.FlashSketch(board, loaderSketch, uploadToolDir, commonFlags.Address) if err != nil { - feedback.Errorf(`Error splitting command line "%s": %s`, uploaderCommand, err) - os.Exit(errorcodes.ErrGeneric) - } - - // Flash loader Sketch - programmerOut := new(bytes.Buffer) - programmerErr := new(bytes.Buffer) - if feedback.GetFormat() == feedback.JSON { - err = programmer.Flash(commandLine, programmerOut, programmerErr) - } else { - err = programmer.Flash(commandLine, os.Stdout, os.Stderr) - } - if err != nil { - feedback.Errorf("Error during certificates flashing: %s", err) + feedback.Error(err) os.Exit(errorcodes.ErrGeneric) } // Wait a bit after flashing the loader sketch for the board to become // available again. + logrus.Debug("sleeping for 3 sec") time.Sleep(3 * time.Second) // Get flasher depending on which module to use var f flasher.Flasher moduleName := board.Module + + // This matches the baudrate used in the FirmwareUpdater.ino sketch + // https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/Tools/FirmwareUpdater/FirmwareUpdater.ino + const baudRate = 1000000 switch moduleName { case "NINA": // we use address and not bootloaderPort because the board should not be in bootloader mode - f, err = flasher.NewNinaFlasher(address) + f, err = flasher.NewNinaFlasher(commonFlags.Address, baudRate, 30) case "WINC1500": - f, err = flasher.NewWincFlasher(address) + f, err = flasher.NewWincFlasher(commonFlags.Address, baudRate, 30) default: err = fmt.Errorf("unknown module: %s", moduleName) } diff --git a/cli/common/common.go b/cli/common/common.go new file mode 100644 index 00000000..795d9699 --- /dev/null +++ b/cli/common/common.go @@ -0,0 +1,134 @@ +package common + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/arduino/arduino-cli/arduino/cores/packageindex" + "github.com/arduino/arduino-cli/arduino/serialutils" + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-fwuploader/indexes" + "github.com/arduino/arduino-fwuploader/indexes/download" + "github.com/arduino/arduino-fwuploader/indexes/firmwareindex" + programmer "github.com/arduino/arduino-fwuploader/programmers" + "github.com/arduino/go-paths-helper" + "github.com/arduino/go-properties-orderedmap" + "github.com/sirupsen/logrus" +) + +// InitIndexes does exactly what the name implies +func InitIndexes() (*packageindex.Index, *firmwareindex.Index) { + packageIndex, err := indexes.GetPackageIndex() + if err != nil { + feedback.Errorf("Can't load package index: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + + firmwareIndex, err := indexes.GetFirmwareIndex() + if err != nil { + feedback.Errorf("Can't load firmware index: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + return packageIndex, firmwareIndex +} + +// CheckFlags runs a basic check, errors if the flags are not defined +func CheckFlags(fqbn, address string) { + if fqbn == "" { + feedback.Errorf("Error during firmware flashing: missing board fqbn") + os.Exit(errorcodes.ErrBadArgument) + } + + if address == "" { + feedback.Errorf("Error during firmware flashing: missing board address") + os.Exit(errorcodes.ErrBadArgument) + } + logrus.Debugf("fqbn: %s, address: %s", fqbn, address) +} + +// GetBoard is an helper function useful to get the IndexBoard, +// the struct that contains all the infos to make all the operations possible +func GetBoard(firmwareIndex *firmwareindex.Index, fqbn string) *firmwareindex.IndexBoard { + board := firmwareIndex.GetBoard(fqbn) + if board == nil { + feedback.Errorf("Can't find board with %s fqbn", fqbn) + os.Exit(errorcodes.ErrBadArgument) + } + logrus.Debugf("got board: %s", board.Fqbn) + return board +} + +// GetUploadToolDir is an helper function that downloads the correct tool to flash a board, +// it returns the path of the downloaded tool +func GetUploadToolDir(packageIndex *packageindex.Index, board *firmwareindex.IndexBoard) *paths.Path { + toolRelease := indexes.GetToolRelease(packageIndex, board.Uploader) + if toolRelease == nil { + feedback.Errorf("Error getting upload tool %s for board %s", board.Uploader, board.Fqbn) + os.Exit(errorcodes.ErrGeneric) + } + uploadToolDir, err := download.DownloadTool(toolRelease) + if err != nil { + feedback.Errorf("Error downloading tool %s: %s", board.Uploader, err) + os.Exit(errorcodes.ErrGeneric) + } + logrus.Debugf("upload tool downloaded in %s", uploadToolDir.String()) + return uploadToolDir +} + +// FlashSketch is the business logic that handles the flashing procedure, +// it returns using a buffer the stdout and the stderr of the programmer +func FlashSketch(board *firmwareindex.IndexBoard, sketch string, uploadToolDir *paths.Path, address string) (programmerOut, programmerErr *bytes.Buffer, err error) { + bootloaderPort, err := GetNewAddress(board, address) + if err != nil { + return nil, nil, err + } + + uploaderCommand := board.GetUploaderCommand() + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{tool_dir}", filepath.FromSlash(uploadToolDir.String())) + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{serial.port.file}", bootloaderPort) + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{loader.sketch}", sketch) // we leave that name here because it's only a template, + + logrus.Debugf("uploading with command: %s", uploaderCommand) + commandLine, err := properties.SplitQuotedString(uploaderCommand, "\"", false) + if err != nil { + feedback.Errorf(`Error splitting command line "%s": %s`, uploaderCommand, err) + os.Exit(errorcodes.ErrGeneric) + } + + // Flash the actual sketch + programmerOut = new(bytes.Buffer) + programmerErr = new(bytes.Buffer) + if feedback.GetFormat() == feedback.JSON { + err = programmer.Flash(commandLine, programmerOut, programmerErr) + } else { + err = programmer.Flash(commandLine, os.Stdout, os.Stderr) + } + if err != nil { + return nil, nil, fmt.Errorf("error during sketch flashing: %s", err) + } + return programmerOut, programmerErr, err +} + +// GetNewAddress is a function used to reset a board and put it in bootloader mode +// it could happen that the board is assigned to a different serial port, after the reset, +// this fuction handles also this possibility +func GetNewAddress(board *firmwareindex.IndexBoard, oldAddress string) (string, error) { + // Check if board needs a 1200bps touch for upload + bootloaderPort := oldAddress + if board.UploadTouch { + logrus.Info("Putting board into bootloader mode") + newUploadPort, err := serialutils.Reset(oldAddress, board.UploadWait, nil) + if err != nil { + return "", fmt.Errorf("error during sketch flashing: missing board address. %s", err) + } + if newUploadPort != "" { + logrus.Infof("Found port to upload: %s", newUploadPort) + bootloaderPort = newUploadPort + } + } + return bootloaderPort, nil +} diff --git a/cli/firmware/firmware.go b/cli/firmware/firmware.go index d21f4929..51903edc 100644 --- a/cli/firmware/firmware.go +++ b/cli/firmware/firmware.go @@ -35,5 +35,6 @@ func NewCommand() *cobra.Command { firmwareCmd.AddCommand(NewFlashCommand()) firmwareCmd.AddCommand(newListCommand()) + firmwareCmd.AddCommand(NewGetVersionCommand()) return firmwareCmd } diff --git a/cli/firmware/flash.go b/cli/firmware/flash.go index c1b96df5..d34bf419 100644 --- a/cli/firmware/flash.go +++ b/cli/firmware/flash.go @@ -23,32 +23,28 @@ import ( "bytes" "fmt" "os" - "path/filepath" "strings" "time" - "github.com/arduino/arduino-cli/arduino/serialutils" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-fwuploader/cli/arguments" + "github.com/arduino/arduino-fwuploader/cli/common" "github.com/arduino/arduino-fwuploader/flasher" - "github.com/arduino/arduino-fwuploader/indexes" "github.com/arduino/arduino-fwuploader/indexes/download" "github.com/arduino/arduino-fwuploader/indexes/firmwareindex" - programmer "github.com/arduino/arduino-fwuploader/programmers" "github.com/arduino/go-paths-helper" - "github.com/arduino/go-properties-orderedmap" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( - fqbn string - address string - module string - retries uint8 + commonFlags arguments.Flags // contains fqbn and address + module string + retries uint8 ) -// NewCommand created a new `version` command +// NewFlashCommand creates a new `flash` command func NewFlashCommand() *cobra.Command { command := &cobra.Command{ Use: "flash", @@ -59,44 +55,20 @@ func NewFlashCommand() *cobra.Command { " " + os.Args[0] + " firmware flash -b arduino:samd:mkr1000 -a COM10 -m WINC15000\n" + " " + os.Args[0] + " firmware flash -b arduino:samd:mkr1000 -a COM10\n", Args: cobra.NoArgs, - Run: run, + Run: runFlash, } - - command.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:samd:mkr1000, arduino:mbed_nano:nanorp2040connect") - command.Flags().StringVarP(&address, "address", "a", "", "Upload port, e.g.: COM10, /dev/ttyACM0") + commonFlags.AddToCommand(command) command.Flags().StringVarP(&module, "module", "m", "", "Firmware module ID, e.g.: WINC1500, NINA") command.Flags().Uint8Var(&retries, "retries", 9, "Number of retries in case of upload failure (default 9)") return command } -func run(cmd *cobra.Command, args []string) { - packageIndex, err := indexes.GetPackageIndex() - if err != nil { - feedback.Errorf("Can't load package index: %s", err) - os.Exit(errorcodes.ErrGeneric) - } - - firmwareIndex, err := indexes.GetFirmwareIndex() - if err != nil { - feedback.Errorf("Can't load firmware index: %s", err) - os.Exit(errorcodes.ErrGeneric) - } +func runFlash(cmd *cobra.Command, args []string) { - if fqbn == "" { - feedback.Errorf("Error during firmware flashing: missing board fqbn") - os.Exit(errorcodes.ErrBadArgument) - } - - if address == "" { - feedback.Errorf("Error during firmware flashing: missing board address") - os.Exit(errorcodes.ErrBadArgument) - } - - board := firmwareIndex.GetBoard(fqbn) - if board == nil { - feedback.Errorf("Can't find board with %s fqbn", fqbn) - os.Exit(errorcodes.ErrBadArgument) - } + packageIndex, firmwareIndex := common.InitIndexes() + common.CheckFlags(commonFlags.Fqbn, commonFlags.Address) + board := common.GetBoard(firmwareIndex, commonFlags.Fqbn) + uploadToolDir := common.GetUploadToolDir(packageIndex, board) // Get module name if not specified moduleName := "" @@ -121,8 +93,9 @@ func run(cmd *cobra.Command, args []string) { } else { firmware = board.GetFirmware(moduleVersion) } + logrus.Debugf("module name: %s, firmware version: %s", firmware.Module, firmware.Version.String()) if firmware == nil { - feedback.Errorf("Error getting firmware for board: %s", fqbn) + feedback.Errorf("Error getting firmware for board: %s", commonFlags.Fqbn) os.Exit(errorcodes.ErrGeneric) } @@ -131,23 +104,14 @@ func run(cmd *cobra.Command, args []string) { feedback.Errorf("Error downloading firmware from %s: %s", firmware.URL, err) os.Exit(errorcodes.ErrGeneric) } + logrus.Debugf("firmware file downloaded in %s", firmwareFile.String()) - toolRelease := indexes.GetToolRelease(packageIndex, board.Uploader) - if toolRelease == nil { - feedback.Errorf("Error getting upload tool %s for board %s", board.Uploader, board.Fqbn) - os.Exit(errorcodes.ErrGeneric) - } - uploadToolDir, err := download.DownloadTool(toolRelease) - if err != nil { - feedback.Errorf("Error downloading tool %s: %s", board.Uploader, err) - os.Exit(errorcodes.ErrGeneric) - } - - loaderSketchPath, err := download.DownloadLoaderSketch(board.LoaderSketch) + loaderSketchPath, err := download.DownloadSketch(board.LoaderSketch) if err != nil { feedback.Errorf("Error downloading loader sketch from %s: %s", board.LoaderSketch.URL, err) os.Exit(errorcodes.ErrGeneric) } + logrus.Debugf("loader sketch downloaded in %s", loaderSketchPath.String()) loaderSketch := strings.ReplaceAll(loaderSketchPath.String(), loaderSketchPath.Ext(), "") @@ -168,56 +132,27 @@ func run(cmd *cobra.Command, args []string) { } func updateFirmware(board *firmwareindex.IndexBoard, loaderSketch, moduleName string, uploadToolDir, firmwareFile *paths.Path) error { - var err error - // Check if board needs a 1200bps touch for upload - bootloaderPort := address - if board.UploadTouch { - logrus.Info("Putting board into bootloader mode") - newUploadPort, err := serialutils.Reset(address, board.UploadWait, nil) - if err != nil { - return fmt.Errorf("error during firmware flashing: missing board address. %s", err) - } - if newUploadPort != "" { - logrus.Infof("Found port to upload Loader: %s", newUploadPort) - bootloaderPort = newUploadPort - } - } - - uploaderCommand := board.GetUploaderCommand() - uploaderCommand = strings.ReplaceAll(uploaderCommand, "{tool_dir}", filepath.FromSlash(uploadToolDir.String())) - uploaderCommand = strings.ReplaceAll(uploaderCommand, "{serial.port.file}", bootloaderPort) - uploaderCommand = strings.ReplaceAll(uploaderCommand, "{loader.sketch}", loaderSketch) - - commandLine, err := properties.SplitQuotedString(uploaderCommand, "\"", false) - if err != nil { - feedback.Errorf(`Error splitting command line "%s": %s`, uploaderCommand, err) - os.Exit(errorcodes.ErrGeneric) - } - - // Flash loader Sketch - programmerOut := new(bytes.Buffer) - programmerErr := new(bytes.Buffer) - if feedback.GetFormat() == feedback.JSON { - err = programmer.Flash(commandLine, programmerOut, programmerErr) - } else { - err = programmer.Flash(commandLine, os.Stdout, os.Stderr) - } + programmerOut, programmerErr, err := common.FlashSketch(board, loaderSketch, uploadToolDir, commonFlags.Address) if err != nil { - return fmt.Errorf("error during loader sketch flashing: %s", err) + return err } - // Wait a bit after flashing the loader sketch for the board to become // available again. + logrus.Debug("sleeping for 3 sec") time.Sleep(3 * time.Second) // Get flasher depending on which module to use var f flasher.Flasher + + // This matches the baudrate used in the FirmwareUpdater.ino sketch + // https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/Tools/FirmwareUpdater/FirmwareUpdater.ino + const baudRate = 1000000 switch moduleName { case "NINA": // we use address and not bootloaderPort because the board should not be in bootloader mode - f, err = flasher.NewNinaFlasher(address) + f, err = flasher.NewNinaFlasher(commonFlags.Address, baudRate, 30) case "WINC1500": - f, err = flasher.NewWincFlasher(address) + f, err = flasher.NewWincFlasher(commonFlags.Address, baudRate, 30) default: err = fmt.Errorf("unknown module: %s", moduleName) feedback.Errorf("Error during firmware flashing: %s", err) @@ -240,6 +175,7 @@ func updateFirmware(board *firmwareindex.IndexBoard, loaderSketch, moduleName st } if err != nil { flasherErr.Write([]byte(fmt.Sprintf("Error during firmware flashing: %s", err))) + return err } // Print the results @@ -253,12 +189,10 @@ func updateFirmware(board *firmwareindex.IndexBoard, loaderSketch, moduleName st Stderr: flasherErr.String(), }), }) - if err != nil { - return fmt.Errorf("error during firmware flashing: %s", err) - } return nil } +// callback used to print the progress func printProgress(progress int) { fmt.Printf("Flashing progress: %d%%\r", progress) } diff --git a/cli/firmware/getversion.go b/cli/firmware/getversion.go new file mode 100644 index 00000000..1826ecef --- /dev/null +++ b/cli/firmware/getversion.go @@ -0,0 +1,136 @@ +/* + arduino-fwuploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package firmware + +import ( + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-fwuploader/cli/common" + "github.com/arduino/arduino-fwuploader/flasher" + "github.com/arduino/arduino-fwuploader/indexes/download" + "github.com/arduino/arduino-fwuploader/indexes/firmwareindex" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + semver "go.bug.st/relaxed-semver" +) + +// NewGetVersionCommand creates a new `get-version` command +func NewGetVersionCommand() *cobra.Command { + + command := &cobra.Command{ + Use: "get-version", + Short: "Gets the version of the firmware the board is using.", + Long: "Flashes a sketch to a board to obtain the firmware version used by the board", + Example: "" + + " " + os.Args[0] + " firmware get-version --fqbn arduino:samd:mkr1000 --address COM10\n" + + " " + os.Args[0] + " firmware get-version -b arduino:samd:mkr1000 -a COM10\n", + Args: cobra.NoArgs, + Run: runGetVersion, + } + commonFlags.AddToCommand(command) + return command +} + +func runGetVersion(cmd *cobra.Command, args []string) { + + packageIndex, firmwareIndex := common.InitIndexes() + common.CheckFlags(commonFlags.Fqbn, commonFlags.Address) + board := common.GetBoard(firmwareIndex, commonFlags.Fqbn) + uploadToolDir := common.GetUploadToolDir(packageIndex, board) + + versionSketchPath, err := download.DownloadSketch(board.VersionSketch) + if err != nil { + feedback.Errorf("Error downloading loader sketch from %s: %s", board.LoaderSketch.URL, err) + os.Exit(errorcodes.ErrGeneric) + } + logrus.Debugf("version sketch downloaded in %s", versionSketchPath.String()) + + versionSketch := strings.ReplaceAll(versionSketchPath.String(), versionSketchPath.Ext(), "") + + programmerOut, programmerErr, err := common.FlashSketch(board, versionSketch, uploadToolDir, commonFlags.Address) + if err != nil { + feedback.Error(err) + os.Exit(errorcodes.ErrGeneric) + } + + // Wait a bit after flashing the sketch for the board to become available again. + logrus.Debug("sleeping for 3 sec") + time.Sleep(3 * time.Second) + + currentVersion, err := getVersion(board) + if err != nil { + feedback.Error(err) + os.Exit(1) + } + if feedback.GetFormat() == feedback.Text { + feedback.Printf("Firmware version installed: %s", currentVersion) + } + // Print the results + feedback.PrintResult(&flasher.FlashResult{ + Programmer: (&flasher.ExecOutput{ + Stdout: programmerOut.String(), + Stderr: programmerErr.String(), + }), + Version: currentVersion, + }) +} + +func getVersion(board *firmwareindex.IndexBoard) (fwVersion string, err error) { + + // 9600 is the baudrate used in the CheckVersion sketch + port, err := flasher.OpenSerial(commonFlags.Address, 9600, 2) + if err != nil { + feedback.Error(err) + os.Exit(errorcodes.ErrGeneric) + } + + buff := make([]byte, 200) + serialResult := make([]byte, 0) + for { + n, err := port.Read(buff) + if err != nil { + log.Fatal(err) + break + } + serialResult = append(serialResult, buff[:n]...) + if n == 0 { // exit when done reading from serial + break + } + logrus.Info(string(buff[:n])) + } + lines := strings.Split(string(serialResult), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Firmware version installed: ") { + version := strings.TrimSpace(strings.Replace(line, "Firmware version installed: ", "", 1)) + semver := semver.ParseRelaxed(version) + return semver.String(), nil + } + if strings.HasPrefix(line, "Communication with WiFi module failed!") { + return "", fmt.Errorf("communication with WiFi module failed") + } + } + return "", fmt.Errorf("could not find the version string to parse") +} diff --git a/docs/usage.md b/docs/usage.md index 59cd7bc3..497f8b8a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -40,6 +40,25 @@ but you can also filter the results by specifying the `-b` or `--fqbn` flag The tool offers the ability to print output in JSON, with the `--format json` flag +### Get Version + +You can also obtain the version of the firmware the board is currently running with: + +``` +./arduino-fwuploader firmware get-version -b arduino:samd:mkrwifi1010 -a /dev/ttyACM0 +``` + +The `get-version` subcommand flashes a special sketch in order to be able to read that information using the serial +connection: + +``` +... + +Firmware version installed: 1.4.8 +``` + +You can also use the `--format json` flag to parse the output with more ease. + ### Certificates The tool offers also the ability to flash SSL certificates to a module: diff --git a/flasher/flasher.go b/flasher/flasher.go index 92b4e680..f65bf8f0 100644 --- a/flasher/flasher.go +++ b/flasher/flasher.go @@ -62,11 +62,8 @@ type Flasher interface { sendCommand(data CommandData) error } -// This matches the baudrate used in the FirmwareUpdater.ino sketch -// https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/Tools/FirmwareUpdater/FirmwareUpdater.ino -const baudRate = 1000000 - -func openSerial(portAddress string) (serial.Port, error) { +// OpenSerial opens a new serial connection with the specified portAddress +func OpenSerial(portAddress string, baudRate int, readTimeout int) (serial.Port, error) { port, err := serial.Open(portAddress, &serial.Mode{BaudRate: baudRate}) if err != nil { @@ -74,7 +71,7 @@ func openSerial(portAddress string) (serial.Port, error) { } logrus.Infof("Opened port %s at %d", portAddress, baudRate) - if err := port.SetReadTimeout(30 * time.Second); err != nil { + if err := port.SetReadTimeout(time.Duration(readTimeout) * time.Second); err != nil { err = fmt.Errorf("could not set timeout on serial port: %s", err) logrus.Error(err) return nil, err @@ -82,11 +79,14 @@ func openSerial(portAddress string) (serial.Port, error) { return port, nil } +// FlashResult contains the result of the flashing procedure type FlashResult struct { Programmer *ExecOutput `json:"programmer"` - Flasher *ExecOutput `json:"flasher"` + Flasher *ExecOutput `json:"flasher,omitempty"` + Version string `json:"version,omitempty"` } +// ExecOutput contains the stdout and stderr output, they are used to store the output of the flashing and upload type ExecOutput struct { Stdout string `json:"stdout"` Stderr string `json:"stderr"` diff --git a/flasher/nina.go b/flasher/nina.go index 5e12cd18..c218ab6c 100644 --- a/flasher/nina.go +++ b/flasher/nina.go @@ -36,8 +36,8 @@ import ( ) // NewNinaFlasher creates an new instance of NinaFlasher -func NewNinaFlasher(portAddress string) (*NinaFlasher, error) { - port, err := openSerial(portAddress) +func NewNinaFlasher(portAddress string, baudRate, readTimeout int) (*NinaFlasher, error) { + port, err := OpenSerial(portAddress, baudRate, readTimeout) if err != nil { logrus.Error(err) return nil, err diff --git a/flasher/winc.go b/flasher/winc.go index 105722b1..c57d0861 100644 --- a/flasher/winc.go +++ b/flasher/winc.go @@ -35,8 +35,8 @@ import ( "go.bug.st/serial" ) -func NewWincFlasher(portAddress string) (*WincFlasher, error) { - port, err := openSerial(portAddress) +func NewWincFlasher(portAddress string, baudRate, readTimeout int) (*WincFlasher, error) { + port, err := OpenSerial(portAddress, baudRate, readTimeout) if err != nil { logrus.Error(err) return nil, err diff --git a/indexes/download/download.go b/indexes/download/download.go index fd24603e..cd004b89 100644 --- a/indexes/download/download.go +++ b/indexes/download/download.go @@ -43,6 +43,7 @@ import ( "go.bug.st/downloader/v2" ) +// DownloadTool downloads and returns the path on the local filesystem of a tool func DownloadTool(toolRelease *cores.ToolRelease) (*paths.Path, error) { resource := toolRelease.GetCompatibleFlavour() installDir := globals.FwUploaderPath.Join( @@ -73,6 +74,7 @@ func DownloadTool(toolRelease *cores.ToolRelease) (*paths.Path, error) { return installDir, nil } +// DownloadFirmware downloads and returns the path on the local filesystem of a firmware func DownloadFirmware(firmware *firmwareindex.IndexFirmware) (*paths.Path, error) { firmwarePath := globals.FwUploaderPath.Join( "firmwares", @@ -105,16 +107,17 @@ func DownloadFirmware(firmware *firmwareindex.IndexFirmware) (*paths.Path, error return firmwarePath, nil } -func DownloadLoaderSketch(loader *firmwareindex.IndexLoaderSketch) (*paths.Path, error) { - loaderPath := globals.FwUploaderPath.Join( - "loader", +// DownloadSketch downloads and returns the path on the local filesystem of a sketch +func DownloadSketch(loader *firmwareindex.IndexSketch) (*paths.Path, error) { + sketchPath := globals.FwUploaderPath.Join( + "sketch", path.Base(loader.URL)) - loaderPath.Parent().MkdirAll() - if err := loaderPath.WriteFile(nil); err != nil { + sketchPath.Parent().MkdirAll() + if err := sketchPath.WriteFile(nil); err != nil { logrus.Error(err) return nil, err } - d, err := downloader.Download(loaderPath.String(), loader.URL) + d, err := downloader.Download(sketchPath.String(), loader.URL) if err != nil { logrus.Error(err) return nil, err @@ -123,16 +126,16 @@ func DownloadLoaderSketch(loader *firmwareindex.IndexLoaderSketch) (*paths.Path, logrus.Error(err) return nil, err } - if err := VerifyFileChecksum(loader.Checksum, loaderPath); err != nil { + if err := VerifyFileChecksum(loader.Checksum, sketchPath); err != nil { logrus.Error(err) return nil, err } size, _ := loader.Size.Int64() - if err := VerifyFileSize(size, loaderPath); err != nil { + if err := VerifyFileSize(size, sketchPath); err != nil { logrus.Error(err) return nil, err } - return loaderPath, nil + return sketchPath, nil } // Download will take a downloader.Downloader as parameter. It will Download the file specified in the downloader @@ -151,7 +154,7 @@ func Download(d *downloader.Downloader) error { return nil } -// taken and adapted from https://github.com/arduino/arduino-cli/blob/59b6277a4d6731a1c1579d43aef6df2a46a771d5/arduino/resources/checksums.go +// VerifyFileChecksum is taken and adapted from https://github.com/arduino/arduino-cli/blob/59b6277a4d6731a1c1579d43aef6df2a46a771d5/arduino/resources/checksums.go func VerifyFileChecksum(checksum string, filePath *paths.Path) error { if checksum == "" { return fmt.Errorf("missing checksum for: %s", filePath) @@ -193,7 +196,7 @@ func VerifyFileChecksum(checksum string, filePath *paths.Path) error { return nil } -// taken and adapted from https://github.com/arduino/arduino-cli/blob/59b6277a4d6731a1c1579d43aef6df2a46a771d5/arduino/resources/checksums.go +// VerifyFileSize is taken and adapted from https://github.com/arduino/arduino-cli/blob/59b6277a4d6731a1c1579d43aef6df2a46a771d5/arduino/resources/checksums.go func VerifyFileSize(size int64, filePath *paths.Path) error { info, err := filePath.Stat() if err != nil { @@ -290,6 +293,7 @@ func verifyIndex(indexPath *paths.Path, URL *url.URL) error { return nil } +// verifyPackageIndex verify if the signature is valid for the provided package index func verifyPackageIndex(indexPath, signaturePath *paths.Path) (bool, error) { valid, _, err := security.VerifyArduinoDetachedSignature(indexPath, signaturePath) if err != nil { @@ -302,6 +306,7 @@ func verifyPackageIndex(indexPath, signaturePath *paths.Path) (bool, error) { return valid, nil } +// verifyModuleFirmwareIndex verify if the signature is valid for the provided module firmware index func verifyModuleFirmwareIndex(indexPath, signaturePath *paths.Path) (bool, error) { keysBox, err := rice.FindBox("gpg_keys") if err != nil { diff --git a/indexes/download/download_test.go b/indexes/download/download_test.go index b157bf58..efc87813 100644 --- a/indexes/download/download_test.go +++ b/indexes/download/download_test.go @@ -149,7 +149,7 @@ func TestDownloadLoaderSketch(t *testing.T) { index, e := firmwareindex.LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - loaderPath, err := DownloadLoaderSketch(index.Boards[0].LoaderSketch) + loaderPath, err := DownloadSketch(index.Boards[0].LoaderSketch) require.NoError(t, err) require.NotEmpty(t, loaderPath) require.FileExists(t, loaderPath.String()) diff --git a/indexes/firmwareindex/firmwareindex.go b/indexes/firmwareindex/firmwareindex.go index 1cbb52cb..3a2b3b34 100644 --- a/indexes/firmwareindex/firmwareindex.go +++ b/indexes/firmwareindex/firmwareindex.go @@ -36,11 +36,12 @@ type Index struct { IsTrusted bool } -// indexPackage represents a single entry from module_firmware_index.json file. +// IndexBoard represents a single entry from module_firmware_index.json file. type IndexBoard struct { Fqbn string `json:"fqbn,required"` Firmwares []*IndexFirmware `json:"firmware,required"` - LoaderSketch *IndexLoaderSketch `json:"loader_sketch,required"` + LoaderSketch *IndexSketch `json:"loader_sketch,required"` + VersionSketch *IndexSketch `json:"version_sketch"` Module string `json:"module,required"` Name string `json:"name,required"` Uploader string `json:"uploader,required"` @@ -50,6 +51,7 @@ type IndexBoard struct { LatestFirmware *IndexFirmware `json:"-"` } +// IndexUploaderCommand represents the command-line to use for different OS type IndexUploaderCommand struct { Linux string `json:"linux,required"` Windows string `json:"windows"` @@ -65,8 +67,8 @@ type IndexFirmware struct { Module string `json:"module,required"` } -// IndexLoaderSketch represents the sketch used to upload the new firmware on a board. -type IndexLoaderSketch struct { +// IndexSketch represents a sketch used to manage firmware on a board. +type IndexSketch struct { URL string `json:"url,required"` Checksum string `json:"checksum,required"` Size json.Number `json:"size,required"` @@ -140,7 +142,7 @@ func (i *Index) GetBoard(fqbn string) *IndexBoard { return nil } -// GetLatestFirmware returns the specified IndexFirmware version for this board. +// GetFirmware returns the specified IndexFirmware version for this board. // Returns nil if version is not found. func (b *IndexBoard) GetFirmware(version string) *IndexFirmware { v := semver.ParseRelaxed(version) @@ -152,6 +154,7 @@ func (b *IndexBoard) GetFirmware(version string) *IndexFirmware { return nil } +// GetUploaderCommand returns the command to use for the upload func (b *IndexBoard) GetUploaderCommand() string { if runtime.GOOS == "windows" && b.UploaderCommand.Windows != "" { return b.UploaderCommand.Linux diff --git a/version/version.go b/version/version.go index 867f07ea..bd8cf575 100644 --- a/version/version.go +++ b/version/version.go @@ -26,7 +26,8 @@ var ( versionString = "" commit = "" date = "" - VersionInfo *info + // VersionInfo contains info regarding the version + VersionInfo *info ) type info struct {