Skip to content

Add board autodetection on upload #1581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading