Skip to content

Commit

Permalink
Added external programmer support (#720)
Browse files Browse the repository at this point in the history
* Added scaffolding for external programmer support

* Added programmers extraction in arduino/cores module

* Implemented programmers list command

* Print upload command line in verbose mode

* Added programmer option to compile command

* External programmer implementation

* Factored function runTool in upload

This will turn out useful for burn-bootloader that requires to run
two actions in a row ("erase" and "bootloader").

* Implemented burn-bootloader

* Increased tracing log

* Test fix

* Added BurnBootloder action

* Make the upload port parameter mandatory only when really needed

* Fixed nil pointer exception when burning-bootloader

* Added sanity check on upload parameters
  • Loading branch information
cmaglie committed Jun 15, 2020
1 parent 15d35de commit 5373961
Show file tree
Hide file tree
Showing 20 changed files with 1,198 additions and 268 deletions.
18 changes: 9 additions & 9 deletions arduino/cores/cores.go
Expand Up @@ -40,14 +40,14 @@ type PlatformRelease struct {
Resource *resources.DownloadResource
Version *semver.Version
BoardsManifest []*BoardManifest
Dependencies ToolDependencies // The Dependency entries to load tools.
Platform *Platform `json:"-"`
Properties *properties.Map `json:"-"`
Boards map[string]*Board `json:"-"`
Programmers map[string]*properties.Map `json:"-"`
Menus *properties.Map `json:"-"`
InstallDir *paths.Path `json:"-"`
IsIDEBundled bool `json:"-"`
Dependencies ToolDependencies // The Dependency entries to load tools.
Platform *Platform `json:"-"`
Properties *properties.Map `json:"-"`
Boards map[string]*Board `json:"-"`
Programmers map[string]*Programmer `json:"-"`
Menus *properties.Map `json:"-"`
InstallDir *paths.Path `json:"-"`
IsIDEBundled bool `json:"-"`
}

// BoardManifest contains information about a board. These metadata are usually
Expand Down Expand Up @@ -117,7 +117,7 @@ func (platform *Platform) GetOrCreateRelease(version *semver.Version) (*Platform
Version: version,
Boards: map[string]*Board{},
Properties: properties.NewMap(),
Programmers: map[string]*properties.Map{},
Programmers: map[string]*Programmer{},
Platform: platform,
}
platform.Releases[tag] = release
Expand Down
15 changes: 11 additions & 4 deletions arduino/cores/packagemanager/loader.go
Expand Up @@ -284,10 +284,10 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p

// Create programmers properties
if programmersProperties, err := properties.SafeLoad(programmersTxtPath.String()); err == nil {
platform.Programmers = properties.MergeMapsOfProperties(
map[string]*properties.Map{},
platform.Programmers, // TODO: Very weird, why not an empty one?
programmersProperties.FirstLevelOf())
for programmerID, programmerProperties := range programmersProperties.FirstLevelOf() {
platform.Programmers[programmerID] = pm.loadProgrammer(programmerProperties)
platform.Programmers[programmerID].PlatformRelease = platform
}
} else {
return err
}
Expand All @@ -299,6 +299,13 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
return nil
}

func (pm *PackageManager) loadProgrammer(programmerProperties *properties.Map) *cores.Programmer {
return &cores.Programmer{
Name: programmerProperties.Get("name"),
Properties: programmerProperties,
}
}

func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
if platform.InstallDir == nil {
return fmt.Errorf("platform not installed")
Expand Down
25 changes: 25 additions & 0 deletions arduino/cores/programmers.go
@@ -0,0 +1,25 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package cores

import "github.com/arduino/go-properties-orderedmap"

// Programmer represents an external programmer
type Programmer struct {
Name string
Properties *properties.Map
PlatformRelease *PlatformRelease
}
129 changes: 129 additions & 0 deletions cli/burnbootloader/burnbootloader.go
@@ -0,0 +1,129 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package burnbootloader

import (
"context"
"os"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands/upload"
rpc "github.com/arduino/arduino-cli/rpc/commands"
"github.com/arduino/arduino-cli/table"
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var (
fqbn string
port string
verbose bool
verify bool
importDir string
programmer string
burnBootloader bool
)

// NewCommand created a new `burn-bootloader` command
func NewCommand() *cobra.Command {
burnBootloaderCommand := &cobra.Command{
Use: "burn-bootloader",
Short: "Upload the bootloader.",
Long: "Upload the bootloader on the board using an external programmer.",
Example: " " + os.Args[0] + " burn-bootloader -b arduino:avr:uno -P atmel-ice",
Args: cobra.MaximumNArgs(1),
Run: run,
}

burnBootloaderCommand.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:avr:uno")
burnBootloaderCommand.Flags().StringVarP(&port, "port", "p", "", "Upload port, e.g.: COM10 or /dev/ttyACM0")
burnBootloaderCommand.Flags().BoolVarP(&verify, "verify", "t", false, "Verify uploaded binary after the upload.")
burnBootloaderCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, "Turns on verbose mode.")
burnBootloaderCommand.Flags().StringVarP(&programmer, "programmer", "P", "", "Use the specified programmer to upload or 'list' to list supported programmers.")

return burnBootloaderCommand
}

func run(command *cobra.Command, args []string) {
instance, err := instance.CreateInstance()
if err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}

if programmer == "list" {
resp, err := upload.ListProgrammersAvailableForUpload(context.Background(), &rpc.ListProgrammersAvailableForUploadReq{
Instance: instance,
Fqbn: fqbn,
})
if err != nil {
feedback.Errorf("Error listing programmers: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
feedback.PrintResult(&programmersList{
Programmers: resp.GetProgrammers(),
})
os.Exit(0)
}

if _, err := upload.BurnBootloader(context.Background(), &rpc.BurnBootloaderReq{
Instance: instance,
Fqbn: fqbn,
Port: port,
Verbose: verbose,
Verify: verify,
Programmer: programmer,
}, os.Stdout, os.Stderr); err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
os.Exit(0)
}

// initSketchPath returns the current working directory
func initSketchPath(sketchPath *paths.Path) *paths.Path {
if sketchPath != nil {
return sketchPath
}

wd, err := paths.Getwd()
if err != nil {
feedback.Errorf("Couldn't get current working directory: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
logrus.Infof("Reading sketch from dir: %s", wd)
return wd
}

type programmersList struct {
Programmers []*rpc.Programmer
}

func (p *programmersList) Data() interface{} {
return p.Programmers
}

func (p *programmersList) String() string {
t := table.New()
t.SetHeader("ID", "Programmer Name", "Platform")
for _, prog := range p.Programmers {
t.AddRow(prog.GetId(), prog.GetName(), prog.GetPlatform())
}
return t.Render()
}
2 changes: 2 additions & 0 deletions cli/cli.go
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"github.com/arduino/arduino-cli/cli/board"
"github.com/arduino/arduino-cli/cli/burnbootloader"
"github.com/arduino/arduino-cli/cli/cache"
"github.com/arduino/arduino-cli/cli/compile"
"github.com/arduino/arduino-cli/cli/completion"
Expand Down Expand Up @@ -87,6 +88,7 @@ func createCliCommandTree(cmd *cobra.Command) {
cmd.AddCommand(sketch.NewCommand())
cmd.AddCommand(upload.NewCommand())
cmd.AddCommand(debug.NewCommand())
cmd.AddCommand(burnbootloader.NewCommand())
cmd.AddCommand(version.NewCommand())

cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print the logs on the standard output.")
Expand Down
3 changes: 3 additions & 0 deletions cli/compile/compile.go
Expand Up @@ -50,6 +50,7 @@ var (
dryRun bool // Use this flag to now write the output file
libraries []string // List of custom libraries paths separated by commas. Or can be used multiple times for multiple libraries paths.
optimizeForDebug bool // Optimize compile output for debug, not for release
programmer string // Use the specified programmer to upload
)

// NewCommand created a new `compile` command
Expand Down Expand Up @@ -84,6 +85,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringSliceVar(&libraries, "libraries", []string{},
"List of custom libraries paths separated by commas. Or can be used multiple times for multiple libraries paths.")
command.Flags().BoolVar(&optimizeForDebug, "optimize-for-debug", false, "Optional, optimize compile output for debug, not for release.")
command.Flags().StringVarP(&programmer, "programmer", "P", "", "Optional, use the specified programmer to upload.")

return command
}
Expand Down Expand Up @@ -135,6 +137,7 @@ func run(cmd *cobra.Command, args []string) {
Verbose: verbose,
Verify: verify,
ImportDir: exportDir,
Programmer: programmer,
}, os.Stdout, os.Stderr)

if err != nil {
Expand Down
64 changes: 59 additions & 5 deletions cli/upload/upload.go
Expand Up @@ -24,17 +24,20 @@ import (
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands/upload"
rpc "github.com/arduino/arduino-cli/rpc/commands"
"github.com/arduino/arduino-cli/table"
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var (
fqbn string
port string
verbose bool
verify bool
importDir string
fqbn string
port string
verbose bool
verify bool
importDir string
programmer string
burnBootloader bool
)

// NewCommand created a new `upload` command
Expand All @@ -53,6 +56,7 @@ func NewCommand() *cobra.Command {
uploadCommand.Flags().StringVarP(&importDir, "input-dir", "", "", "Direcory containing binaries to upload.")
uploadCommand.Flags().BoolVarP(&verify, "verify", "t", false, "Verify uploaded binary after the upload.")
uploadCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, "Optional, turns on verbose mode.")
uploadCommand.Flags().StringVarP(&programmer, "programmer", "P", "", "Optional, use the specified programmer to upload or 'list' to list supported programmers.")

return uploadCommand
}
Expand All @@ -64,12 +68,44 @@ func run(command *cobra.Command, args []string) {
os.Exit(errorcodes.ErrGeneric)
}

if programmer == "list" {
resp, err := upload.ListProgrammersAvailableForUpload(context.Background(), &rpc.ListProgrammersAvailableForUploadReq{
Instance: instance,
Fqbn: fqbn,
})
if err != nil {
feedback.Errorf("Error listing programmers: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
feedback.PrintResult(&programmersList{
Programmers: resp.GetProgrammers(),
})
os.Exit(0)
}

var path *paths.Path
if len(args) > 0 {
path = paths.New(args[0])
}
sketchPath := initSketchPath(path)

if burnBootloader {
if _, err := upload.Upload(context.Background(), &rpc.UploadReq{
Instance: instance,
Fqbn: fqbn,
SketchPath: sketchPath.String(),
Port: port,
Verbose: verbose,
Verify: verify,
ImportDir: importDir,
Programmer: programmer,
}, os.Stdout, os.Stderr); err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
os.Exit(0)
}

if _, err := upload.Upload(context.Background(), &rpc.UploadReq{
Instance: instance,
Fqbn: fqbn,
Expand All @@ -78,6 +114,7 @@ func run(command *cobra.Command, args []string) {
Verbose: verbose,
Verify: verify,
ImportDir: importDir,
Programmer: programmer,
}, os.Stdout, os.Stderr); err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
Expand All @@ -98,3 +135,20 @@ func initSketchPath(sketchPath *paths.Path) *paths.Path {
logrus.Infof("Reading sketch from dir: %s", wd)
return wd
}

type programmersList struct {
Programmers []*rpc.Programmer
}

func (p *programmersList) Data() interface{} {
return p.Programmers
}

func (p *programmersList) String() string {
t := table.New()
t.SetHeader("ID", "Programmer Name", "Platform")
for _, prog := range p.Programmers {
t.AddRow(prog.GetId(), prog.GetName(), prog.GetPlatform())
}
return t.Render()
}
18 changes: 18 additions & 0 deletions commands/daemon/daemon.go
Expand Up @@ -216,6 +216,24 @@ func (s *ArduinoCoreServerImpl) Upload(req *rpc.UploadReq, stream rpc.ArduinoCor
return stream.Send(resp)
}

// BurnBootloader FIXMEDOC
func (s *ArduinoCoreServerImpl) BurnBootloader(req *rpc.BurnBootloaderReq, stream rpc.ArduinoCore_BurnBootloaderServer) error {
resp, err := upload.BurnBootloader(
stream.Context(), req,
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.BurnBootloaderResp{OutStream: data}) }),
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.BurnBootloaderResp{ErrStream: data}) }),
)
if err != nil {
return err
}
return stream.Send(resp)
}

// ListProgrammersAvailableForUpload FIXMEDOC
func (s *ArduinoCoreServerImpl) ListProgrammersAvailableForUpload(ctx context.Context, req *rpc.ListProgrammersAvailableForUploadReq) (*rpc.ListProgrammersAvailableForUploadResp, error) {
return upload.ListProgrammersAvailableForUpload(ctx, req)
}

// LibraryDownload FIXMEDOC
func (s *ArduinoCoreServerImpl) LibraryDownload(req *rpc.LibraryDownloadReq, stream rpc.ArduinoCore_LibraryDownloadServer) error {
resp, err := lib.LibraryDownload(
Expand Down

0 comments on commit 5373961

Please sign in to comment.