Skip to content

Commit 9299579

Browse files
cmaglieumbynos
andauthored
[skip-changelog] [breaking] Refactoring of board autodetection (#1717)
* Changes in errors.MultipleBoardsDetectedError and errors.NoBoardsDetectedError - The message formatting has been fixed - The errors now accepts a *rpc.Port instead of a *discovery.Port - The new error NoBoardsDetectedError has been added * Renamed some variable in compile/compile.go to improve readability fqbn -> fqbnArg port -> portArgs detectedFqbn -> fqbn discoveryPort -> port * Renamed some variable in debug/debug.go to improve readability fqbn -> fqbnArg port -> portArgs discoveryPort -> port * Fixed some error formatting * Renamed some variable in compile/compile.go to improve readability fqbn -> fqbnArg port -> portArgs discoveryPort -> port * Refactored board autodetection in upload/compile - use `board.List()` instead of implementing a duplicate of it - factored the logic that calculates FQBN, Port and possibly autodetects FQBN, so we have a single implementation for all commands of the cli. * Removed SupportedUserFieldsRequest.Address gRPC parameter Because it's no more needed * Added notes about technical debt * Factored --discovery-timeout flag and added timeout to board autodetection * Added docs * Fixed error message and integration tests * Update cli/arguments/port.go Co-authored-by: Umberto Baldi <34278123+umbynos@users.noreply.github.com> Co-authored-by: Umberto Baldi <34278123+umbynos@users.noreply.github.com>
1 parent 7f2e750 commit 9299579

File tree

17 files changed

+245
-228
lines changed

17 files changed

+245
-228
lines changed

arduino/errors.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"fmt"
2020
"strings"
2121

22-
"github.com/arduino/arduino-cli/arduino/discovery"
2322
"github.com/arduino/arduino-cli/i18n"
2423
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2524
"google.golang.org/grpc/codes"
@@ -125,16 +124,35 @@ func (e *InvalidVersionError) Unwrap() error {
125124
return e.Cause
126125
}
127126

127+
// NoBoardsDetectedError is returned when detecting the FQBN of a board
128+
// does not produce any result.
129+
type NoBoardsDetectedError struct {
130+
Port *rpc.Port
131+
}
132+
133+
func (e *NoBoardsDetectedError) Error() string {
134+
return tr(
135+
"Please specify an FQBN. The board on port %[1]s with protocol %[2]s can't be identified",
136+
e.Port.Address,
137+
e.Port.Protocol,
138+
)
139+
}
140+
141+
// ToRPCStatus converts the error into a *status.Status
142+
func (e *NoBoardsDetectedError) ToRPCStatus() *status.Status {
143+
return status.New(codes.InvalidArgument, e.Error())
144+
}
145+
128146
// MultipleBoardsDetectedError is returned when trying to detect
129147
// the FQBN of a board connected to a port fails because that
130148
// are multiple possible boards detected.
131149
type MultipleBoardsDetectedError struct {
132-
Port *discovery.Port
150+
Port *rpc.Port
133151
}
134152

135153
func (e *MultipleBoardsDetectedError) Error() string {
136154
return tr(
137-
"Please specify an FQBN. Multiple possible ports detected on port %s with protocol %s",
155+
"Please specify an FQBN. Multiple possible boards detected on port %[1]s with protocol %[2]s",
138156
e.Port.Address,
139157
e.Port.Protocol,
140158
)

cli/arguments/discovery_timeout.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2022 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package arguments
17+
18+
import (
19+
"time"
20+
21+
"github.com/spf13/cobra"
22+
)
23+
24+
// DiscoveryTimeout is the timeout given to discoveries to detect ports.
25+
type DiscoveryTimeout struct {
26+
timeout time.Duration
27+
}
28+
29+
// AddToCommand adds the flags used to set fqbn to the specified Command
30+
func (d *DiscoveryTimeout) AddToCommand(cmd *cobra.Command) {
31+
cmd.Flags().DurationVar(&d.timeout, "discovery-timeout", time.Second, tr("Max time to wait for port discovery, e.g.: 30s, 1m"))
32+
}
33+
34+
// Get returns the timeout
35+
func (d *DiscoveryTimeout) Get() time.Duration {
36+
return d.timeout
37+
}

cli/arguments/fqbn.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
package arguments
1717

1818
import (
19+
"os"
1920
"strings"
2021

22+
"github.com/arduino/arduino-cli/arduino"
23+
"github.com/arduino/arduino-cli/arduino/sketch"
24+
"github.com/arduino/arduino-cli/cli/errorcodes"
25+
"github.com/arduino/arduino-cli/cli/feedback"
26+
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2127
"github.com/spf13/cobra"
2228
)
2329

@@ -54,3 +60,44 @@ func (f *Fqbn) String() string {
5460
func (f *Fqbn) Set(fqbn string) {
5561
f.fqbn = fqbn
5662
}
63+
64+
// CalculateFQBNAndPort calculate the FQBN and Port metadata based on
65+
// parameters provided by the user.
66+
// This determine the FQBN based on:
67+
// - the value of the FQBN flag if explicitly specified, otherwise
68+
// - the FQBN value in sketch.json if available, otherwise
69+
// - it tries to autodetect the board connected to the given port flags
70+
// If all above methods fails, it returns the empty string.
71+
// The Port metadata are always returned except if:
72+
// - the port is not found, in this case nil is returned
73+
// - the FQBN autodetection fail, in this case the function prints an error and
74+
// terminates the execution
75+
func CalculateFQBNAndPort(portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, sk *sketch.Sketch) (string, *rpc.Port) {
76+
// TODO: REMOVE sketch.Sketch from here
77+
78+
fqbn := fqbnArg.String()
79+
if fqbn == "" && sk != nil && sk.Metadata != nil {
80+
// If the user didn't specify an FQBN and a sketch.json file is present
81+
// read it from there.
82+
fqbn = sk.Metadata.CPU.Fqbn
83+
}
84+
if fqbn == "" {
85+
if portArgs == nil || portArgs.address == "" {
86+
feedback.Error(&arduino.MissingFQBNError{})
87+
os.Exit(errorcodes.ErrGeneric)
88+
}
89+
fqbn, port := portArgs.DetectFQBN(instance)
90+
if fqbn == "" {
91+
feedback.Error(&arduino.MissingFQBNError{})
92+
os.Exit(errorcodes.ErrGeneric)
93+
}
94+
return fqbn, port
95+
}
96+
97+
port, err := portArgs.GetPort(instance, sk)
98+
if err != nil {
99+
feedback.Errorf(tr("Error getting port metadata: %v", err))
100+
os.Exit(errorcodes.ErrGeneric)
101+
}
102+
return fqbn, port.ToRPC()
103+
}

cli/arguments/port.go

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import (
2121
"os"
2222
"time"
2323

24+
"github.com/arduino/arduino-cli/arduino"
2425
"github.com/arduino/arduino-cli/arduino/discovery"
2526
"github.com/arduino/arduino-cli/arduino/sketch"
2627
"github.com/arduino/arduino-cli/cli/errorcodes"
2728
"github.com/arduino/arduino-cli/cli/feedback"
2829
"github.com/arduino/arduino-cli/commands"
30+
"github.com/arduino/arduino-cli/commands/board"
2931
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3032
"github.com/pkg/errors"
3133
"github.com/sirupsen/logrus"
@@ -38,7 +40,7 @@ import (
3840
type Port struct {
3941
address string
4042
protocol string
41-
timeout time.Duration
43+
timeout DiscoveryTimeout
4244
}
4345

4446
// AddToCommand adds the flags used to set port and protocol to the specified Command
@@ -51,7 +53,7 @@ func (p *Port) AddToCommand(cmd *cobra.Command) {
5153
cmd.RegisterFlagCompletionFunc("protocol", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
5254
return GetInstalledProtocols(), cobra.ShellCompDirectiveDefault
5355
})
54-
cmd.Flags().DurationVar(&p.timeout, "discovery-timeout", 5*time.Second, tr("Max time to wait for port discovery, e.g.: 30s, 1m"))
56+
p.timeout.AddToCommand(cmd)
5557
}
5658

5759
// GetPortAddressAndProtocol returns only the port address and the port protocol
@@ -72,6 +74,9 @@ func (p *Port) GetPortAddressAndProtocol(instance *rpc.Instance, sk *sketch.Sket
7274
// GetPort returns the Port obtained by parsing command line arguments.
7375
// The extra metadata for the ports is obtained using the pluggable discoveries.
7476
func (p *Port) GetPort(instance *rpc.Instance, sk *sketch.Sketch) (*discovery.Port, error) {
77+
// TODO: REMOVE sketch.Sketch from here
78+
// TODO: REMOVE discovery from here (use board.List instead)
79+
7580
address := p.address
7681
protocol := p.protocol
7782

@@ -122,7 +127,7 @@ func (p *Port) GetPort(instance *rpc.Instance, sk *sketch.Sketch) (*discovery.Po
122127
}
123128
}()
124129

125-
deadline := time.After(p.timeout)
130+
deadline := time.After(p.timeout.Get())
126131
for {
127132
select {
128133
case portEvent := <-eventChan:
@@ -149,15 +154,38 @@ func (p *Port) GetPort(instance *rpc.Instance, sk *sketch.Sketch) (*discovery.Po
149154

150155
// GetSearchTimeout returns the timeout
151156
func (p *Port) GetSearchTimeout() time.Duration {
152-
return p.timeout
157+
return p.timeout.Get()
153158
}
154159

155-
// GetDiscoveryPort is a helper function useful to get the port and handle possible errors
156-
func (p *Port) GetDiscoveryPort(instance *rpc.Instance, sk *sketch.Sketch) *discovery.Port {
157-
discoveryPort, err := p.GetPort(instance, sk)
160+
// DetectFQBN tries to identify the board connected to the port and returns the
161+
// discovered Port object together with the FQBN. If the port does not match
162+
// exactly 1 board,
163+
func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) {
164+
detectedPorts, err := board.List(&rpc.BoardListRequest{
165+
Instance: inst,
166+
Timeout: p.timeout.Get().Milliseconds(),
167+
})
158168
if err != nil {
159-
feedback.Errorf(tr("Error discovering port: %v"), err)
169+
feedback.Errorf(tr("Error during FQBN detection: %v", err))
160170
os.Exit(errorcodes.ErrGeneric)
161171
}
162-
return discoveryPort
172+
for _, detectedPort := range detectedPorts {
173+
port := detectedPort.GetPort()
174+
if p.address != port.GetAddress() {
175+
continue
176+
}
177+
if p.protocol != "" && p.protocol != port.GetProtocol() {
178+
continue
179+
}
180+
if len(detectedPort.MatchingBoards) > 1 {
181+
feedback.Error(&arduino.MultipleBoardsDetectedError{Port: port})
182+
os.Exit(errorcodes.ErrBadArgument)
183+
}
184+
if len(detectedPort.MatchingBoards) == 0 {
185+
feedback.Error(&arduino.NoBoardsDetectedError{Port: port})
186+
os.Exit(errorcodes.ErrBadArgument)
187+
}
188+
return detectedPort.MatchingBoards[0].Fqbn, port
189+
}
190+
return "", nil
163191
}

cli/arguments/sketch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func InitSketchPath(path string) (sketchPath *paths.Path) {
4848
func NewSketch(sketchPath *paths.Path) *sketch.Sketch {
4949
sketch, err := sketch.New(sketchPath)
5050
if err != nil {
51-
feedback.Errorf(tr("Error creating sketch: %v"), err)
51+
feedback.Errorf(tr("Error opening sketch: %v"), err)
5252
os.Exit(errorcodes.ErrGeneric)
5353
}
5454
return sketch

cli/board/list.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import (
1919
"fmt"
2020
"os"
2121
"sort"
22-
"time"
2322

2423
"github.com/arduino/arduino-cli/arduino/cores"
24+
"github.com/arduino/arduino-cli/cli/arguments"
2525
"github.com/arduino/arduino-cli/cli/errorcodes"
2626
"github.com/arduino/arduino-cli/cli/feedback"
2727
"github.com/arduino/arduino-cli/cli/instance"
@@ -33,21 +33,21 @@ import (
3333
)
3434

3535
var (
36-
timeout time.Duration
37-
watch bool
36+
timeoutArg arguments.DiscoveryTimeout
37+
watch bool
3838
)
3939

4040
func initListCommand() *cobra.Command {
4141
listCommand := &cobra.Command{
4242
Use: "list",
4343
Short: tr("List connected boards."),
4444
Long: tr("Detects and displays a list of boards connected to the current computer."),
45-
Example: " " + os.Args[0] + " board list --timeout 10s",
45+
Example: " " + os.Args[0] + " board list --discovery-timeout 10s",
4646
Args: cobra.NoArgs,
4747
Run: runListCommand,
4848
}
4949

50-
listCommand.Flags().DurationVar(&timeout, "discovery-timeout", time.Second, tr("Max time to wait for port discovery, e.g.: 30s, 1m"))
50+
timeoutArg.AddToCommand(listCommand)
5151
listCommand.Flags().BoolVarP(&watch, "watch", "w", false, tr("Command keeps running and prints list of connected boards whenever there is a change."))
5252

5353
return listCommand
@@ -66,7 +66,7 @@ func runListCommand(cmd *cobra.Command, args []string) {
6666

6767
ports, err := board.List(&rpc.BoardListRequest{
6868
Instance: inst,
69-
Timeout: timeout.Milliseconds(),
69+
Timeout: timeoutArg.Get().Milliseconds(),
7070
})
7171
if err != nil {
7272
feedback.Errorf(tr("Error detecting boards: %v"), err)

0 commit comments

Comments
 (0)