Skip to content

Commit

Permalink
Add flag to board list command to watch for connected and disconnecte…
Browse files Browse the repository at this point in the history
…d boards
  • Loading branch information
silvanocerza committed Oct 16, 2020
1 parent 2931737 commit d94d178
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 28 deletions.
57 changes: 57 additions & 0 deletions cli/board/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/commands/board"
rpc "github.com/arduino/arduino-cli/rpc/commands"
"github.com/arduino/arduino-cli/table"
Expand All @@ -43,15 +44,29 @@ func initListCommand() *cobra.Command {

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

return listCommand
}

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

// runListCommand detects and lists the connected arduino boards
func runListCommand(cmd *cobra.Command, args []string) {
if listFlags.watch {
inst, err := instance.CreateInstance()
if err != nil {
feedback.Errorf("Error detecting boards: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
watchList(inst)
os.Exit(0)
}

if timeout, err := time.ParseDuration(listFlags.timeout); err != nil {
feedback.Errorf("Invalid timeout: %v", err)
os.Exit(errorcodes.ErrBadArgument)
Expand All @@ -74,6 +89,48 @@ func runListCommand(cmd *cobra.Command, args []string) {
feedback.PrintResult(result{ports})
}

func watchList(inst *rpc.Instance) {
pm := commands.GetPackageManager(inst.Id)
eventsChan, err := commands.WatchListBoards(pm)
if err != nil {
feedback.Errorf("Error detecting boards: %v", err)
os.Exit(errorcodes.ErrNetwork)
}

boardPorts := map[string]*commands.BoardPort{}
for event := range eventsChan {
switch event.Type {
case "add":
boardPorts[event.Port.Address] = &commands.BoardPort{
Address: event.Port.Address,
Label: event.Port.AddressLabel,
Prefs: event.Port.Properties,
IdentificationPrefs: event.Port.IdentificationProperties,
Protocol: event.Port.Protocol,
ProtocolLabel: event.Port.ProtocolLabel,
}
case "remove":
delete(boardPorts, event.Port.Address)
}

ports := []*rpc.DetectedPort{}
for _, p := range boardPorts {
boardList, err := board.Identify(pm, p)
if err != nil {
feedback.Errorf("Error identifying board: %v", err)
os.Exit(errorcodes.ErrNetwork)
}
ports = append(ports, &rpc.DetectedPort{
Address: p.Address,
Protocol: p.Protocol,
ProtocolLabel: p.ProtocolLabel,
Boards: boardList,
})
}
feedback.PrintResult(result{ports})
}
}

// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type result struct {
Expand Down
65 changes: 37 additions & 28 deletions commands/board/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"regexp"
"sync"

"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/httpclient"
rpc "github.com/arduino/arduino-cli/rpc/commands"
Expand Down Expand Up @@ -107,6 +108,38 @@ func identifyViaCloudAPI(port *commands.BoardPort) ([]*rpc.BoardListItem, error)
return apiByVidPid(id.Get("vid"), id.Get("pid"))
}

func Identify(pm *packagemanager.PackageManager, port *commands.BoardPort) ([]*rpc.BoardListItem, error) {
boards := []*rpc.BoardListItem{}

// first query installed cores through the Package Manager
logrus.Debug("Querying installed cores for board identification...")
for _, board := range pm.IdentifyBoard(port.IdentificationPrefs) {
boards = append(boards, &rpc.BoardListItem{
Name: board.Name(),
FQBN: board.FQBN(),
})
}

// if installed cores didn't recognize the board, try querying
// the builder API if the board is a USB device port
if len(boards) == 0 {
items, err := identifyViaCloudAPI(port)
if err == ErrNotFound {
// the board couldn't be detected, print a warning
logrus.Debug("Board not recognized")
} else if err != nil {
// this is bad, bail out
return nil, errors.Wrap(err, "error getting board info from Arduino Cloud")
}

// add a DetectedPort entry in any case: the `Boards` field will
// be empty but the port will be shown anyways (useful for 3rd party
// boards)
boards = items
}
return boards, nil
}

// List FIXMEDOC
func List(instanceID int32) (r []*rpc.DetectedPort, e error) {
m.Lock()
Expand Down Expand Up @@ -135,33 +168,9 @@ func List(instanceID int32) (r []*rpc.DetectedPort, e error) {

retVal := []*rpc.DetectedPort{}
for _, port := range ports {
b := []*rpc.BoardListItem{}

// first query installed cores through the Package Manager
logrus.Debug("Querying installed cores for board identification...")
for _, board := range pm.IdentifyBoard(port.IdentificationPrefs) {
b = append(b, &rpc.BoardListItem{
Name: board.Name(),
FQBN: board.FQBN(),
})
}

// if installed cores didn't recognize the board, try querying
// the builder API if the board is a USB device port
if len(b) == 0 {
items, err := identifyViaCloudAPI(port)
if err == ErrNotFound {
// the board couldn't be detected, print a warning
logrus.Debug("Board not recognized")
} else if err != nil {
// this is bad, bail out
return nil, errors.Wrap(err, "error getting board info from Arduino Cloud")
}

// add a DetectedPort entry in any case: the `Boards` field will
// be empty but the port will be shown anyways (useful for 3rd party
// boards)
b = items
boards, err := Identify(pm, port)
if err != nil {
return nil, err
}

// boards slice can be empty at this point if neither the cores nor the
Expand All @@ -170,7 +179,7 @@ func List(instanceID int32) (r []*rpc.DetectedPort, e error) {
Address: port.Address,
Protocol: port.Protocol,
ProtocolLabel: port.ProtocolLabel,
Boards: b,
Boards: boards,
}
retVal = append(retVal, p)
}
Expand Down
28 changes: 28 additions & 0 deletions commands/bundled_tools_serial_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/arduino/resources"
"github.com/arduino/arduino-cli/executils"
"github.com/arduino/go-properties-orderedmap"
Expand Down Expand Up @@ -193,6 +194,33 @@ func ListBoards(pm *packagemanager.PackageManager) ([]*BoardPort, error) {
return retVal, finalError
}

// WatchListBoards returns a channel that receives events from the bundled discovery tool
func WatchListBoards(pm *packagemanager.PackageManager) (<-chan *discovery.Event, error) {
t, err := getBuiltinSerialDiscoveryTool(pm)
if err != nil {
return nil, err
}

if !t.IsInstalled() {
return nil, fmt.Errorf("missing serial-discovery tool")
}

disc, err := discovery.New("serial-discovery", t.InstallDir.Join(t.Tool.Name).String())
if err != nil {
return nil, err
}

if err = disc.Start(); err != nil {
return nil, fmt.Errorf("starting discovery: %v", err)
}

if err = disc.StartSync(); err != nil {
return nil, fmt.Errorf("starting sync: %v", err)
}

return disc.EventChannel(10), nil
}

func getBuiltinSerialDiscoveryTool(pm *packagemanager.PackageManager) (*cores.ToolRelease, error) {
builtinPackage := pm.Packages.GetOrCreatePackage("builtin")
ctagsTool := builtinPackage.GetOrCreateTool("serial-discovery")
Expand Down

0 comments on commit d94d178

Please sign in to comment.