Skip to content

Commit 61eae53

Browse files
committed
Add flag to board list command to watch for connected and disconnected boards
1 parent 2931737 commit 61eae53

File tree

3 files changed

+123
-28
lines changed

3 files changed

+123
-28
lines changed

Diff for: cli/board/list.go

+57
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/arduino/arduino-cli/cli/errorcodes"
2626
"github.com/arduino/arduino-cli/cli/feedback"
2727
"github.com/arduino/arduino-cli/cli/instance"
28+
"github.com/arduino/arduino-cli/commands"
2829
"github.com/arduino/arduino-cli/commands/board"
2930
rpc "github.com/arduino/arduino-cli/rpc/commands"
3031
"github.com/arduino/arduino-cli/table"
@@ -43,15 +44,29 @@ func initListCommand() *cobra.Command {
4344

4445
listCommand.Flags().StringVar(&listFlags.timeout, "timeout", "0s",
4546
"The connected devices search timeout, raise it if your board doesn't show up (e.g. to 10s).")
47+
listCommand.Flags().BoolVarP(&listFlags.watch, "watch", "w", false,
48+
"Command keeps running and prints list of connected boards whenever there is a change.")
49+
4650
return listCommand
4751
}
4852

4953
var listFlags struct {
5054
timeout string // Expressed in a parsable duration, is the timeout for the list and attach commands.
55+
watch bool
5156
}
5257

5358
// runListCommand detects and lists the connected arduino boards
5459
func runListCommand(cmd *cobra.Command, args []string) {
60+
if listFlags.watch {
61+
inst, err := instance.CreateInstance()
62+
if err != nil {
63+
feedback.Errorf("Error detecting boards: %v", err)
64+
os.Exit(errorcodes.ErrGeneric)
65+
}
66+
watchList(inst)
67+
os.Exit(0)
68+
}
69+
5570
if timeout, err := time.ParseDuration(listFlags.timeout); err != nil {
5671
feedback.Errorf("Invalid timeout: %v", err)
5772
os.Exit(errorcodes.ErrBadArgument)
@@ -74,6 +89,48 @@ func runListCommand(cmd *cobra.Command, args []string) {
7489
feedback.PrintResult(result{ports})
7590
}
7691

92+
func watchList(inst *rpc.Instance) {
93+
pm := commands.GetPackageManager(inst.Id)
94+
eventsChan, err := commands.WatchListBoards(pm)
95+
if err != nil {
96+
feedback.Errorf("Error detecting boards: %v", err)
97+
os.Exit(errorcodes.ErrNetwork)
98+
}
99+
100+
boardPorts := map[string]*commands.BoardPort{}
101+
for event := range eventsChan {
102+
switch event.Type {
103+
case "add":
104+
boardPorts[event.Port.Address] = &commands.BoardPort{
105+
Address: event.Port.Address,
106+
Label: event.Port.AddressLabel,
107+
Prefs: event.Port.Properties,
108+
IdentificationPrefs: event.Port.IdentificationProperties,
109+
Protocol: event.Port.Protocol,
110+
ProtocolLabel: event.Port.ProtocolLabel,
111+
}
112+
case "remove":
113+
delete(boardPorts, event.Port.Address)
114+
}
115+
116+
ports := []*rpc.DetectedPort{}
117+
for _, p := range boardPorts {
118+
boardList, err := board.Identify(pm, p)
119+
if err != nil {
120+
feedback.Errorf("Error identifying board: %v", err)
121+
os.Exit(errorcodes.ErrNetwork)
122+
}
123+
ports = append(ports, &rpc.DetectedPort{
124+
Address: p.Address,
125+
Protocol: p.Protocol,
126+
ProtocolLabel: p.ProtocolLabel,
127+
Boards: boardList,
128+
})
129+
}
130+
feedback.PrintResult(result{ports})
131+
}
132+
}
133+
77134
// output from this command requires special formatting, let's create a dedicated
78135
// feedback.Result implementation
79136
type result struct {

Diff for: commands/board/list.go

+38-28
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"regexp"
2424
"sync"
2525

26+
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
2627
"github.com/arduino/arduino-cli/commands"
2728
"github.com/arduino/arduino-cli/httpclient"
2829
rpc "github.com/arduino/arduino-cli/rpc/commands"
@@ -107,6 +108,39 @@ func identifyViaCloudAPI(port *commands.BoardPort) ([]*rpc.BoardListItem, error)
107108
return apiByVidPid(id.Get("vid"), id.Get("pid"))
108109
}
109110

111+
// Identify returns a list of boards checking first the installed platforms or the Cloud API
112+
func Identify(pm *packagemanager.PackageManager, port *commands.BoardPort) ([]*rpc.BoardListItem, error) {
113+
boards := []*rpc.BoardListItem{}
114+
115+
// first query installed cores through the Package Manager
116+
logrus.Debug("Querying installed cores for board identification...")
117+
for _, board := range pm.IdentifyBoard(port.IdentificationPrefs) {
118+
boards = append(boards, &rpc.BoardListItem{
119+
Name: board.Name(),
120+
FQBN: board.FQBN(),
121+
})
122+
}
123+
124+
// if installed cores didn't recognize the board, try querying
125+
// the builder API if the board is a USB device port
126+
if len(boards) == 0 {
127+
items, err := identifyViaCloudAPI(port)
128+
if err == ErrNotFound {
129+
// the board couldn't be detected, print a warning
130+
logrus.Debug("Board not recognized")
131+
} else if err != nil {
132+
// this is bad, bail out
133+
return nil, errors.Wrap(err, "error getting board info from Arduino Cloud")
134+
}
135+
136+
// add a DetectedPort entry in any case: the `Boards` field will
137+
// be empty but the port will be shown anyways (useful for 3rd party
138+
// boards)
139+
boards = items
140+
}
141+
return boards, nil
142+
}
143+
110144
// List FIXMEDOC
111145
func List(instanceID int32) (r []*rpc.DetectedPort, e error) {
112146
m.Lock()
@@ -135,33 +169,9 @@ func List(instanceID int32) (r []*rpc.DetectedPort, e error) {
135169

136170
retVal := []*rpc.DetectedPort{}
137171
for _, port := range ports {
138-
b := []*rpc.BoardListItem{}
139-
140-
// first query installed cores through the Package Manager
141-
logrus.Debug("Querying installed cores for board identification...")
142-
for _, board := range pm.IdentifyBoard(port.IdentificationPrefs) {
143-
b = append(b, &rpc.BoardListItem{
144-
Name: board.Name(),
145-
FQBN: board.FQBN(),
146-
})
147-
}
148-
149-
// if installed cores didn't recognize the board, try querying
150-
// the builder API if the board is a USB device port
151-
if len(b) == 0 {
152-
items, err := identifyViaCloudAPI(port)
153-
if err == ErrNotFound {
154-
// the board couldn't be detected, print a warning
155-
logrus.Debug("Board not recognized")
156-
} else if err != nil {
157-
// this is bad, bail out
158-
return nil, errors.Wrap(err, "error getting board info from Arduino Cloud")
159-
}
160-
161-
// add a DetectedPort entry in any case: the `Boards` field will
162-
// be empty but the port will be shown anyways (useful for 3rd party
163-
// boards)
164-
b = items
172+
boards, err := Identify(pm, port)
173+
if err != nil {
174+
return nil, err
165175
}
166176

167177
// boards slice can be empty at this point if neither the cores nor the
@@ -170,7 +180,7 @@ func List(instanceID int32) (r []*rpc.DetectedPort, e error) {
170180
Address: port.Address,
171181
Protocol: port.Protocol,
172182
ProtocolLabel: port.ProtocolLabel,
173-
Boards: b,
183+
Boards: boards,
174184
}
175185
retVal = append(retVal, p)
176186
}

Diff for: commands/bundled_tools_serial_discovery.go

+28
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/arduino/arduino-cli/arduino/cores"
2525
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
26+
"github.com/arduino/arduino-cli/arduino/discovery"
2627
"github.com/arduino/arduino-cli/arduino/resources"
2728
"github.com/arduino/arduino-cli/executils"
2829
"github.com/arduino/go-properties-orderedmap"
@@ -193,6 +194,33 @@ func ListBoards(pm *packagemanager.PackageManager) ([]*BoardPort, error) {
193194
return retVal, finalError
194195
}
195196

197+
// WatchListBoards returns a channel that receives events from the bundled discovery tool
198+
func WatchListBoards(pm *packagemanager.PackageManager) (<-chan *discovery.Event, error) {
199+
t, err := getBuiltinSerialDiscoveryTool(pm)
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
if !t.IsInstalled() {
205+
return nil, fmt.Errorf("missing serial-discovery tool")
206+
}
207+
208+
disc, err := discovery.New("serial-discovery", t.InstallDir.Join(t.Tool.Name).String())
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
if err = disc.Start(); err != nil {
214+
return nil, fmt.Errorf("starting discovery: %v", err)
215+
}
216+
217+
if err = disc.StartSync(); err != nil {
218+
return nil, fmt.Errorf("starting sync: %v", err)
219+
}
220+
221+
return disc.EventChannel(10), nil
222+
}
223+
196224
func getBuiltinSerialDiscoveryTool(pm *packagemanager.PackageManager) (*cores.ToolRelease, error) {
197225
builtinPackage := pm.Packages.GetOrCreatePackage("builtin")
198226
ctagsTool := builtinPackage.GetOrCreateTool("serial-discovery")

0 commit comments

Comments
 (0)