Skip to content

Commit

Permalink
Add board autodetection on upload (#1581)
Browse files Browse the repository at this point in the history
  • Loading branch information
silvanocerza committed Dec 7, 2021
1 parent c5fe48d commit f106863
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 58 deletions.
33 changes: 33 additions & 0 deletions arduino/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package arduino
import (
"fmt"

"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -123,6 +124,26 @@ func (e *InvalidVersionError) Unwrap() error {
return e.Cause
}

// MultipleBoardsDetectedError is returned when trying to detect
// the FQBN of a board connected to a port fails because that
// are multiple possible boards detected.
type MultipleBoardsDetectedError struct {
Port *discovery.Port
}

func (e *MultipleBoardsDetectedError) Error() string {
return tr(
"Please specify an FQBN. Multiple possible ports detected on port %s with protocol %s",
e.Port.Address,
e.Port.Protocol,
)
}

// ToRPCStatus converts the error into a *status.Status
func (e *MultipleBoardsDetectedError) ToRPCStatus() *status.Status {
return status.New(codes.InvalidArgument, e.Error())
}

// MissingFQBNError is returned when the FQBN is mandatory and not specified
type MissingFQBNError struct{}

Expand Down Expand Up @@ -153,6 +174,18 @@ func (e *UnknownFQBNError) ToRPCStatus() *status.Status {
return status.New(codes.NotFound, e.Error())
}

// MissingPortAddressError is returned when the port protocol is mandatory and not specified
type MissingPortAddressError struct{}

func (e *MissingPortAddressError) Error() string {
return tr("Missing port protocol")
}

// ToRPCStatus converts the error into a *status.Status
func (e *MissingPortAddressError) ToRPCStatus() *status.Status {
return status.New(codes.InvalidArgument, e.Error())
}

// MissingPortProtocolError is returned when the port protocol is mandatory and not specified
type MissingPortProtocolError struct{}

Expand Down
35 changes: 31 additions & 4 deletions cli/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ import (
"encoding/json"
"os"

"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/arduino/sketch"
"github.com/arduino/arduino-cli/cli/arguments"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/output"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/configuration"
"github.com/arduino/arduino-cli/i18n"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -150,9 +153,28 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
overrides = o.Overrides
}

detectedFqbn := fqbn.String()
var sk *sketch.Sketch
var discoveryPort *discovery.Port
// If the user didn't provide an FQBN it might either mean
// that she forgot or that is trying to compile and upload
// using board autodetection.
if detectedFqbn == "" && uploadAfterCompile {
sk = arguments.NewSketch(sketchPath)
discoveryPort = port.GetDiscoveryPort(inst, sk)
rpcPort := discoveryPort.ToRPC()
var err error
pm := commands.GetPackageManager(inst.Id)
detectedFqbn, err = upload.DetectConnectedBoard(pm, rpcPort.Address, rpcPort.Protocol)
if err != nil {
feedback.Errorf(tr("Error during FQBN detection: %v", err))
os.Exit(errorcodes.ErrGeneric)
}
}

compileRequest := &rpc.CompileRequest{
Instance: inst,
Fqbn: fqbn.String(),
Fqbn: detectedFqbn,
SketchPath: sketchPath.String(),
ShowProperties: showProperties,
Preprocess: preprocess,
Expand Down Expand Up @@ -183,12 +205,17 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
}

if compileError == nil && uploadAfterCompile {
sk := arguments.NewSketch(sketchPath)
discoveryPort := port.GetDiscoveryPort(inst, sk)
if sk == nil {
sk = arguments.NewSketch(sketchPath)
}
if discoveryPort == nil {
discoveryPort = port.GetDiscoveryPort(inst, sk)
}

userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
Instance: inst,
Fqbn: fqbn.String(),
Address: discoveryPort.Address,
Protocol: discoveryPort.Protocol,
})
if err != nil {
Expand All @@ -204,7 +231,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {

uploadRequest := &rpc.UploadRequest{
Instance: inst,
Fqbn: fqbn.String(),
Fqbn: detectedFqbn,
SketchPath: sketchPath.String(),
Port: discoveryPort.ToRPC(),
Verbose: verbose,
Expand Down
1 change: 1 addition & 0 deletions cli/upload/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func runUploadCommand(command *cobra.Command, args []string) {
userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
Instance: instance,
Fqbn: fqbn.String(),
Address: discoveryPort.Address,
Protocol: discoveryPort.Protocol,
})
if err != nil {
Expand Down
89 changes: 87 additions & 2 deletions commands/upload/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,16 @@ func SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsReques
return nil, &arduino.InvalidInstanceError{}
}

fqbn, err := cores.ParseFQBN(req.GetFqbn())
reqFQBN := req.GetFqbn()
if reqFQBN == "" {
var err error
reqFQBN, err = DetectConnectedBoard(pm, req.Address, req.Protocol)
if err != nil {
return nil, err
}
}

fqbn, err := cores.ParseFQBN(reqFQBN)
if err != nil {
return nil, &arduino.InvalidFQBNError{Cause: err}
}
Expand All @@ -72,6 +81,76 @@ func SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsReques
}, nil
}

// DetectConnectedBoard returns the FQBN of the board connected to specified address and protocol.
// In all other cases, like when more than a possible board is detected return an error.
func DetectConnectedBoard(pm *packagemanager.PackageManager, address, protocol string) (string, error) {
if address == "" {
return "", &arduino.MissingPortAddressError{}
}
if protocol == "" {
return "", &arduino.MissingPortProtocolError{}
}

dm := pm.DiscoveryManager()

// Run discoveries if they're not already running
if errs := dm.RunAll(); len(errs) > 0 {
// Some discovery managed to not run, just log the errors,
// we'll fail further down the line.
for _, err := range errs {
logrus.Error(err)
}
}

if errs := dm.StartAll(); len(errs) > 0 {
// Some discovery managed to not start, just log the errors,
// we'll fail further down the line.
for _, err := range errs {
logrus.Error(err)
}
}

ports, errs := dm.List()
if len(errs) > 0 {
// Errors at this time are not a big issue, we'll
// fail further down the line if we can't find a
// matching board and tell the user to provide
// an FQBN.
for _, err := range errs {
logrus.Error(err)
}
}

for _, p := range ports {
if p.Address == address && p.Protocol == protocol {
// Found matching port, let's see if we can detect
// which board is connected to it
boards := pm.IdentifyBoard(p.Properties)
if l := len(boards); l == 1 {
// We found only one board connected, that must
// the one the user want to upload to.
board := boards[0]
logrus.
WithField("board", board.String()).
WithField("address", address).
WithField("protocol", protocol).
Trace("Detected board")
return board.FQBN(), nil
} else if l >= 2 {
// There are multiple boards detected on this port,
// we have no way of knowing which one is the one.
return "", &arduino.MultipleBoardsDetectedError{Port: p}
} else {
// We found a matching port but there's either
// no board connected or we can't recognize it.
// The user must provide an FQBN.
break
}
}
}
return "", &arduino.MissingFQBNError{}
}

// getToolID returns the ID of the tool that supports the action and protocol combination by searching in props.
// Returns error if tool cannot be found.
func getToolID(props *properties.Map, action, protocol string) (string, error) {
Expand Down Expand Up @@ -190,9 +269,15 @@ func runProgramAction(pm *packagemanager.PackageManager,
if fqbnIn == "" && sk != nil && sk.Metadata != nil {
fqbnIn = sk.Metadata.CPU.Fqbn
}

if fqbnIn == "" {
return &arduino.MissingFQBNError{}
var err error
fqbnIn, err = DetectConnectedBoard(pm, port.Address, port.Protocol)
if err != nil {
return err
}
}

fqbn, err := cores.ParseFQBN(fqbnIn)
if err != nil {
return &arduino.InvalidFQBNError{Cause: err}
Expand Down
7 changes: 4 additions & 3 deletions rpc/cc/arduino/cli/commands/v1/commands.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f106863

Please sign in to comment.