Skip to content

Commit ae4db7f

Browse files
committed
Add board autodetection on upload
1 parent 822134d commit ae4db7f

File tree

9 files changed

+242
-56
lines changed

9 files changed

+242
-56
lines changed

Diff for: arduino/errors.go

+33
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package arduino
1818
import (
1919
"fmt"
2020

21+
"github.com/arduino/arduino-cli/arduino/discovery"
2122
"github.com/arduino/arduino-cli/i18n"
2223
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2324
"google.golang.org/grpc/codes"
@@ -123,6 +124,26 @@ func (e *InvalidVersionError) Unwrap() error {
123124
return e.Cause
124125
}
125126

127+
// MultipleBoardsDetectedError is returned when trying to detect
128+
// the FQBN of a board connected to a port fails because that
129+
// are multiple possible boards detected.
130+
type MultipleBoardsDetectedError struct {
131+
Port *discovery.Port
132+
}
133+
134+
func (e *MultipleBoardsDetectedError) Error() string {
135+
return tr(
136+
"Please specify an FQBN. Multiple possible ports detected on port %s with protocol %s",
137+
e.Port.Address,
138+
e.Port.Protocol,
139+
)
140+
}
141+
142+
// ToRPCStatus converts the error into a *status.Status
143+
func (e *MultipleBoardsDetectedError) ToRPCStatus() *status.Status {
144+
return status.New(codes.InvalidArgument, e.Error())
145+
}
146+
126147
// MissingFQBNError is returned when the FQBN is mandatory and not specified
127148
type MissingFQBNError struct{}
128149

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

177+
// MissingPortAddressError is returned when the port protocol is mandatory and not specified
178+
type MissingPortAddressError struct{}
179+
180+
func (e *MissingPortAddressError) Error() string {
181+
return tr("Missing port protocol")
182+
}
183+
184+
// ToRPCStatus converts the error into a *status.Status
185+
func (e *MissingPortAddressError) ToRPCStatus() *status.Status {
186+
return status.New(codes.InvalidArgument, e.Error())
187+
}
188+
156189
// MissingPortProtocolError is returned when the port protocol is mandatory and not specified
157190
type MissingPortProtocolError struct{}
158191

Diff for: cli/compile/compile.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/arduino/arduino-cli/cli/arguments"
2525
"github.com/arduino/arduino-cli/cli/feedback"
2626
"github.com/arduino/arduino-cli/cli/output"
27+
"github.com/arduino/arduino-cli/commands"
2728
"github.com/arduino/arduino-cli/configuration"
2829
"github.com/arduino/arduino-cli/i18n"
2930
"github.com/sirupsen/logrus"
@@ -150,9 +151,26 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
150151
overrides = o.Overrides
151152
}
152153

154+
detectedFqbn := fqbn.String()
155+
// If the user didn't provide an FQBN it might either mean
156+
// that she forgot or that is trying to compile and upload
157+
// using board autodetection.
158+
if detectedFqbn == "" {
159+
sk := arguments.NewSketch(sketchPath)
160+
discoveryPort := port.GetDiscoveryPort(inst, sk)
161+
rpcPort := discoveryPort.ToRPC()
162+
var err error
163+
pm := commands.GetPackageManager(inst.Id)
164+
detectedFqbn, err = upload.DetectConnectedBoard(pm, rpcPort.Address, rpcPort.Protocol)
165+
if err != nil {
166+
feedback.Errorf(tr("Error during FQBN detection: %v", err))
167+
os.Exit(errorcodes.ErrGeneric)
168+
}
169+
}
170+
153171
compileRequest := &rpc.CompileRequest{
154172
Instance: inst,
155-
Fqbn: fqbn.String(),
173+
Fqbn: detectedFqbn,
156174
SketchPath: sketchPath.String(),
157175
ShowProperties: showProperties,
158176
Preprocess: preprocess,
@@ -189,6 +207,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
189207
userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
190208
Instance: inst,
191209
Fqbn: fqbn.String(),
210+
Address: discoveryPort.Address,
192211
Protocol: discoveryPort.Protocol,
193212
})
194213
if err != nil {
@@ -204,7 +223,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
204223

205224
uploadRequest := &rpc.UploadRequest{
206225
Instance: inst,
207-
Fqbn: fqbn.String(),
226+
Fqbn: detectedFqbn,
208227
SketchPath: sketchPath.String(),
209228
Port: discoveryPort.ToRPC(),
210229
Verbose: verbose,

Diff for: cli/upload/upload.go

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func runUploadCommand(command *cobra.Command, args []string) {
104104
userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
105105
Instance: instance,
106106
Fqbn: fqbn.String(),
107+
Address: discoveryPort.Address,
107108
Protocol: discoveryPort.Protocol,
108109
})
109110
if err != nil {

Diff for: commands/upload/upload.go

+87-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,16 @@ func SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsReques
5252
return nil, &arduino.InvalidInstanceError{}
5353
}
5454

55-
fqbn, err := cores.ParseFQBN(req.GetFqbn())
55+
reqFQBN := req.GetFqbn()
56+
if reqFQBN == "" {
57+
var err error
58+
reqFQBN, err = DetectConnectedBoard(pm, req.Address, req.Protocol)
59+
if err != nil {
60+
return nil, err
61+
}
62+
}
63+
64+
fqbn, err := cores.ParseFQBN(reqFQBN)
5665
if err != nil {
5766
return nil, &arduino.InvalidFQBNError{Cause: err}
5867
}
@@ -72,6 +81,76 @@ func SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsReques
7281
}, nil
7382
}
7483

84+
// DetectConnectedBoard returns the FQBN of the board connected to specified address and protocol.
85+
// In all other cases, like when more than a possible board is detected return an error.
86+
func DetectConnectedBoard(pm *packagemanager.PackageManager, address, protocol string) (string, error) {
87+
if address == "" {
88+
return "", &arduino.MissingPortAddressError{}
89+
}
90+
if protocol == "" {
91+
return "", &arduino.MissingPortProtocolError{}
92+
}
93+
94+
dm := pm.DiscoveryManager()
95+
96+
// Run discoveries if they're not already running
97+
if errs := dm.RunAll(); len(errs) > 0 {
98+
// Some discovery managed to not run, just log the errors,
99+
// we'll fail further down the line.
100+
for _, err := range errs {
101+
logrus.Error(err)
102+
}
103+
}
104+
105+
if errs := dm.StartAll(); len(errs) > 0 {
106+
// Some discovery managed to not start, just log the errors,
107+
// we'll fail further down the line.
108+
for _, err := range errs {
109+
logrus.Error(err)
110+
}
111+
}
112+
113+
ports, errs := dm.List()
114+
if len(errs) > 0 {
115+
// Errors at this time are not a big issue, we'll
116+
// fail further down the line if we can't find a
117+
// matching board and tell the user to provide
118+
// an FQBN.
119+
for _, err := range errs {
120+
logrus.Error(err)
121+
}
122+
}
123+
124+
for _, p := range ports {
125+
if p.Address == address && p.Protocol == protocol {
126+
// Found matching port, let's see if we can detect
127+
// which board is connected to it
128+
boards := pm.IdentifyBoard(p.Properties)
129+
if l := len(boards); l == 1 {
130+
// We found only one board connected, that must
131+
// the one the user want to upload to.
132+
board := boards[0]
133+
logrus.
134+
WithField("board", board.String()).
135+
WithField("address", address).
136+
WithField("protocol", protocol).
137+
Trace("Detected board")
138+
return board.FQBN(), nil
139+
} else if l >= 2 {
140+
// There are multiple boards detected on this port,
141+
// we have no way of knowing which one is the one.
142+
return "", &arduino.MultipleBoardsDetectedError{Port: p}
143+
} else {
144+
// We found a matching port but there's either
145+
// no board connected or we can't recognize it.
146+
// The user must provide an FQBN.
147+
break
148+
}
149+
}
150+
}
151+
return "", &arduino.MissingFQBNError{}
152+
}
153+
75154
// getToolID returns the ID of the tool that supports the action and protocol combination by searching in props.
76155
// Returns error if tool cannot be found.
77156
func getToolID(props *properties.Map, action, protocol string) (string, error) {
@@ -190,9 +269,15 @@ func runProgramAction(pm *packagemanager.PackageManager,
190269
if fqbnIn == "" && sk != nil && sk.Metadata != nil {
191270
fqbnIn = sk.Metadata.CPU.Fqbn
192271
}
272+
193273
if fqbnIn == "" {
194-
return &arduino.MissingFQBNError{}
274+
var err error
275+
fqbnIn, err = DetectConnectedBoard(pm, port.Address, port.Protocol)
276+
if err != nil {
277+
return err
278+
}
195279
}
280+
196281
fqbn, err := cores.ParseFQBN(fqbnIn)
197282
if err != nil {
198283
return &arduino.InvalidFQBNError{Cause: err}

Diff for: rpc/cc/arduino/cli/commands/v1/commands.pb.go

+4-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)