Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 99ef6e4

Browse files
committedDec 3, 2021
Add board autodetection on upload
1 parent 822134d commit 99ef6e4

File tree

10 files changed

+265
-58
lines changed

10 files changed

+265
-58
lines changed
 

‎arduino/errors.go

Lines changed: 33 additions & 0 deletions
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

‎cli/compile/compile.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ import (
2121
"encoding/json"
2222
"os"
2323

24+
"github.com/arduino/arduino-cli/arduino/discovery"
25+
"github.com/arduino/arduino-cli/arduino/sketch"
2426
"github.com/arduino/arduino-cli/cli/arguments"
2527
"github.com/arduino/arduino-cli/cli/feedback"
2628
"github.com/arduino/arduino-cli/cli/output"
29+
"github.com/arduino/arduino-cli/commands"
2730
"github.com/arduino/arduino-cli/configuration"
2831
"github.com/arduino/arduino-cli/i18n"
2932
"github.com/sirupsen/logrus"
@@ -150,9 +153,28 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
150153
overrides = o.Overrides
151154
}
152155

156+
detectedFqbn := fqbn.String()
157+
var sk *sketch.Sketch
158+
var discoveryPort *discovery.Port
159+
// If the user didn't provide an FQBN it might either mean
160+
// that she forgot or that is trying to compile and upload
161+
// using board autodetection.
162+
if detectedFqbn == "" && uploadAfterCompile {
163+
sk = arguments.NewSketch(sketchPath)
164+
discoveryPort = port.GetDiscoveryPort(inst, sk)
165+
rpcPort := discoveryPort.ToRPC()
166+
var err error
167+
pm := commands.GetPackageManager(inst.Id)
168+
detectedFqbn, err = upload.DetectConnectedBoard(pm, rpcPort.Address, rpcPort.Protocol)
169+
if err != nil {
170+
feedback.Errorf(tr("Error during FQBN detection: %v", err))
171+
os.Exit(errorcodes.ErrGeneric)
172+
}
173+
}
174+
153175
compileRequest := &rpc.CompileRequest{
154176
Instance: inst,
155-
Fqbn: fqbn.String(),
177+
Fqbn: detectedFqbn,
156178
SketchPath: sketchPath.String(),
157179
ShowProperties: showProperties,
158180
Preprocess: preprocess,
@@ -183,12 +205,17 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
183205
}
184206

185207
if compileError == nil && uploadAfterCompile {
186-
sk := arguments.NewSketch(sketchPath)
187-
discoveryPort := port.GetDiscoveryPort(inst, sk)
208+
if sk == nil {
209+
sk = arguments.NewSketch(sketchPath)
210+
}
211+
if discoveryPort == nil {
212+
discoveryPort = port.GetDiscoveryPort(inst, sk)
213+
}
188214

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

205232
uploadRequest := &rpc.UploadRequest{
206233
Instance: inst,
207-
Fqbn: fqbn.String(),
234+
Fqbn: detectedFqbn,
208235
SketchPath: sketchPath.String(),
209236
Port: discoveryPort.ToRPC(),
210237
Verbose: verbose,

‎cli/upload/upload.go

Lines changed: 1 addition & 0 deletions
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 {

‎commands/upload/upload.go

Lines changed: 87 additions & 2 deletions
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}

‎rpc/cc/arduino/cli/commands/v1/commands.pb.go

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go

Lines changed: 25 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎rpc/cc/arduino/cli/commands/v1/upload.pb.go

Lines changed: 32 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎rpc/cc/arduino/cli/commands/v1/upload.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ message SupportedUserFieldsRequest {
162162
// necessary to pick the right upload tool for the board specified
163163
// with the FQBN.
164164
string protocol = 3;
165+
// If an FQBN is not provided but both protocol and address are
166+
// we can try and detect the FQBN using this information.
167+
string address = 4;
165168
}
166169

167170
message UserField {

‎test/test_compile_part_4.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,3 +365,16 @@ def test_compile_with_relative_build_path(run_command, data_dir, copy_sketch):
365365
assert "libraries" in built_files
366366
assert "preproc" in built_files
367367
assert "sketch" in built_files
368+
369+
370+
def test_compile_without_upload_and_fqbn(run_command, data_dir):
371+
assert run_command(["update"])
372+
373+
# Create a sketch
374+
sketch_name = "SketchSimple"
375+
sketch_path = Path(data_dir, sketch_name)
376+
assert run_command(["sketch", "new", sketch_path])
377+
378+
res = run_command(["compile", sketch_path])
379+
assert res.failed
380+
assert "Error during build: Missing FQBN (Fully Qualified Board Name)" in res.stderr

‎test/test_upload.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,39 @@ def test_upload_sketch_with_mismatched_casing(run_command, data_dir, detected_bo
388388
res = run_command(["upload", "-b", board.fqbn, "-p", board.address, sketch_path])
389389
assert res.failed
390390
assert "Error during Upload: no valid sketch found in" in res.stderr
391+
392+
393+
def test_upload_to_port_with_board_autodetect(run_command, data_dir, detected_boards):
394+
assert run_command(["update"])
395+
396+
# Create a sketch
397+
sketch_name = "SketchSimple"
398+
sketch_path = Path(data_dir, sketch_name)
399+
assert run_command(["sketch", "new", sketch_path])
400+
401+
for board in detected_boards:
402+
# Install core
403+
core = ":".join(board.fqbn.split(":")[:2])
404+
assert run_command(["core", "install", core])
405+
406+
assert run_command(["compile", "-b", board.fqbn, sketch_path])
407+
408+
res = run_command(["upload", "-p", board.address, sketch_path])
409+
assert res.ok
410+
411+
412+
def test_compile_and_upload_to_port_with_board_autodetect(run_command, data_dir, detected_boards):
413+
assert run_command(["update"])
414+
415+
# Create a sketch
416+
sketch_name = "SketchSimple"
417+
sketch_path = Path(data_dir, sketch_name)
418+
assert run_command(["sketch", "new", sketch_path])
419+
420+
for board in detected_boards:
421+
# Install core
422+
core = ":".join(board.fqbn.split(":")[:2])
423+
assert run_command(["core", "install", core])
424+
425+
res = run_command(["compile", "-u", "-p", board.address, sketch_path])
426+
assert res.ok

0 commit comments

Comments
 (0)
Please sign in to comment.