Skip to content

Commit f2d14b2

Browse files
masciMaurizio Branca
and
Maurizio Branca
authored
Query the backend to get the fqbn when a core is not installed (#336)
* query the backend to get the fqbn * Update commands/board/list.go Co-Authored-By: Maurizio Branca <[email protected]>
1 parent 5858721 commit f2d14b2

File tree

7 files changed

+185
-17
lines changed

7 files changed

+185
-17
lines changed

Diff for: arduino/cores/packagemanager/identify.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
properties "github.com/arduino/go-properties-orderedmap"
2525
)
2626

27-
// IdentifyBoard returns a list of baords matching the provided identification properties.
27+
// IdentifyBoard returns a list of boards matching the provided identification properties.
2828
func (pm *PackageManager) IdentifyBoard(idProps *properties.Map) []*cores.Board {
2929
if idProps.Size() == 0 {
3030
return []*cores.Board{}

Diff for: cli/board/list.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,18 @@ func runListCommand(cmd *cobra.Command, args []string) {
6060
time.Sleep(timeout)
6161
}
6262

63-
resp, err := board.List(instance.CreateInstance().GetId())
63+
ports, err := board.List(instance.CreateInstance().GetId())
6464
if err != nil {
6565
formatter.PrintError(err, "Error detecting boards")
6666
os.Exit(errorcodes.ErrNetwork)
6767
}
6868

69-
if output.JSONOrElse(resp) {
70-
outputListResp(resp)
69+
if output.JSONOrElse(ports) {
70+
outputListResp(ports)
7171
}
7272
}
7373

74-
func outputListResp(resp *rpc.BoardListResp) {
75-
ports := resp.GetPorts()
74+
func outputListResp(ports []*rpc.DetectedPort) {
7675
if len(ports) == 0 {
7776
formatter.Print("No boards found.")
7877
return
@@ -84,7 +83,7 @@ func outputListResp(resp *rpc.BoardListResp) {
8483
})
8584
table := output.NewTable()
8685
table.SetHeader("Port", "Type", "Board Name", "FQBN")
87-
for _, port := range resp.GetPorts() {
86+
for _, port := range ports {
8887
address := port.GetProtocol() + "://" + port.GetAddress()
8988
if port.GetProtocol() == "serial" {
9089
address = port.GetAddress()

Diff for: cli/output/table.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ func (t *Table) makeTableRow(columns ...interface{}) *TableRow {
7171
case TextBox:
7272
cells[i] = text
7373
case string:
74-
cells[i] = Sprintf("%s", text)
74+
cells[i] = sprintf("%s", text)
7575
case fmt.Stringer:
76-
cells[i] = Sprintf("%s", text.String())
76+
cells[i] = sprintf("%s", text.String())
7777
default:
7878
panic(fmt.Sprintf("invalid column argument type: %t", col))
7979
}

Diff for: cli/output/text.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ func spaces(n int) string {
121121
return res
122122
}
123123

124-
// Sprintf FIXMEDOC
125-
func Sprintf(format string, args ...interface{}) TextBox {
124+
func sprintf(format string, args ...interface{}) TextBox {
126125
cleanArgs := make([]interface{}, len(args))
127126
for i, arg := range args {
128127
if text, ok := arg.(*Text); ok {

Diff for: commands/board/list.go

+79-5
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,65 @@
1818
package board
1919

2020
import (
21+
"encoding/json"
22+
"fmt"
23+
"io/ioutil"
24+
"net/http"
25+
26+
"github.com/arduino/arduino-cli/cli/globals"
2127
"github.com/arduino/arduino-cli/commands"
2228
rpc "github.com/arduino/arduino-cli/rpc/commands"
2329
"github.com/pkg/errors"
2430
)
2531

32+
var (
33+
// ErrNotFound is returned when the API returns 404
34+
ErrNotFound = errors.New("board not found")
35+
)
36+
37+
func apiByVidPid(url string) ([]*rpc.BoardListItem, error) {
38+
retVal := []*rpc.BoardListItem{}
39+
req, _ := http.NewRequest("GET", url, nil)
40+
req.Header = globals.HTTPClientHeader
41+
req.Header.Set("Content-Type", "application/json")
42+
43+
if res, err := http.DefaultClient.Do(req); err == nil {
44+
if res.StatusCode >= 400 {
45+
if res.StatusCode == 404 {
46+
return nil, ErrNotFound
47+
}
48+
return nil, errors.Errorf("the server responded with status %s", res.Status)
49+
}
50+
51+
body, _ := ioutil.ReadAll(res.Body)
52+
res.Body.Close()
53+
54+
var dat map[string]interface{}
55+
err = json.Unmarshal(body, &dat)
56+
if err != nil {
57+
return nil, errors.Wrap(err, "error processing response from server")
58+
}
59+
60+
name, nameFound := dat["name"].(string)
61+
fqbn, fbqnFound := dat["fqbn"].(string)
62+
63+
if !nameFound || !fbqnFound {
64+
return nil, errors.New("wrong format in server response")
65+
}
66+
67+
retVal = append(retVal, &rpc.BoardListItem{
68+
Name: name,
69+
FQBN: fqbn,
70+
})
71+
} else {
72+
return nil, errors.Wrap(err, "error querying Arduino Cloud Api")
73+
}
74+
75+
return retVal, nil
76+
}
77+
2678
// List FIXMEDOC
27-
func List(instanceID int32) (*rpc.BoardListResp, error) {
79+
func List(instanceID int32) ([]*rpc.DetectedPort, error) {
2880
pm := commands.GetPackageManager(instanceID)
2981
if pm == nil {
3082
return nil, errors.New("invalid instance")
@@ -40,29 +92,51 @@ func List(instanceID int32) (*rpc.BoardListResp, error) {
4092
}
4193
defer serialDiscovery.Close()
4294

43-
resp := &rpc.BoardListResp{Ports: []*rpc.DetectedPort{}}
44-
4595
ports, err := serialDiscovery.List()
4696
if err != nil {
4797
return nil, errors.Wrap(err, "error getting port list from serial-discovery")
4898
}
4999

100+
retVal := []*rpc.DetectedPort{}
50101
for _, port := range ports {
51102
b := []*rpc.BoardListItem{}
103+
104+
// first query installed cores through the Package Manager
52105
for _, board := range pm.IdentifyBoard(port.IdentificationPrefs) {
53106
b = append(b, &rpc.BoardListItem{
54107
Name: board.Name(),
55108
FQBN: board.FQBN(),
56109
})
57110
}
111+
112+
// if installed cores didn't recognize the board, try querying
113+
// the builder API
114+
if len(b) == 0 {
115+
url := fmt.Sprintf("https://builder.arduino.cc/v3/boards/byVidPid/%s/%s",
116+
port.IdentificationPrefs.Get("vid"),
117+
port.IdentificationPrefs.Get("pid"))
118+
items, err := apiByVidPid(url)
119+
if err == ErrNotFound {
120+
// the board couldn't be detected, keep going with the next port
121+
continue
122+
} else if err != nil {
123+
// this is bad, bail out
124+
return nil, errors.Wrap(err, "error getting board info from Arduino Cloud")
125+
}
126+
127+
b = items
128+
}
129+
130+
// boards slice can be empty at this point if neither the cores nor the
131+
// API managed to recognize the connected board
58132
p := &rpc.DetectedPort{
59133
Address: port.Address,
60134
Protocol: port.Protocol,
61135
ProtocolLabel: port.ProtocolLabel,
62136
Boards: b,
63137
}
64-
resp.Ports = append(resp.Ports, p)
138+
retVal = append(retVal, p)
65139
}
66140

67-
return resp, nil
141+
return retVal, nil
68142
}

Diff for: commands/board/list_test.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 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 [email protected].
15+
16+
package board
17+
18+
import (
19+
"fmt"
20+
"net/http"
21+
"net/http/httptest"
22+
"testing"
23+
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestGetByVidPid(t *testing.T) {
28+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29+
fmt.Fprintln(w, `
30+
{
31+
"architecture": "samd",
32+
"fqbn": "arduino:samd:mkr1000",
33+
"href": "/v3/boards/arduino:samd:mkr1000",
34+
"id": "mkr1000",
35+
"name": "Arduino/Genuino MKR1000",
36+
"package": "arduino",
37+
"plan": "create-free"
38+
}
39+
`)
40+
}))
41+
defer ts.Close()
42+
43+
res, err := apiByVidPid(ts.URL)
44+
require.Nil(t, err)
45+
require.Len(t, res, 1)
46+
require.Equal(t, "Arduino/Genuino MKR1000", res[0].Name)
47+
require.Equal(t, "arduino:samd:mkr1000", res[0].FQBN)
48+
49+
// wrong url
50+
res, err = apiByVidPid("http://0.0.0.0")
51+
require.NotNil(t, err)
52+
}
53+
54+
func TestGetByVidPidNotFound(t *testing.T) {
55+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56+
w.WriteHeader(http.StatusNotFound)
57+
}))
58+
defer ts.Close()
59+
60+
res, err := apiByVidPid(ts.URL)
61+
require.NotNil(t, err)
62+
require.Equal(t, "board not found", err.Error())
63+
require.Len(t, res, 0)
64+
}
65+
66+
func TestGetByVidPid5xx(t *testing.T) {
67+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
68+
w.WriteHeader(http.StatusInternalServerError)
69+
w.Write([]byte("500 - Ooooops!"))
70+
}))
71+
defer ts.Close()
72+
73+
res, err := apiByVidPid(ts.URL)
74+
require.NotNil(t, err)
75+
require.Equal(t, "the server responded with status 500 Internal Server Error", err.Error())
76+
require.Len(t, res, 0)
77+
}
78+
79+
func TestGetByVidPidMalformedResponse(t *testing.T) {
80+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81+
fmt.Fprintln(w, "{}")
82+
}))
83+
defer ts.Close()
84+
85+
res, err := apiByVidPid(ts.URL)
86+
require.NotNil(t, err)
87+
require.Equal(t, "wrong format in server response", err.Error())
88+
require.Len(t, res, 0)
89+
}

Diff for: commands/daemon/daemon.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,14 @@ func (s *ArduinoCoreServerImpl) BoardDetails(ctx context.Context, req *rpc.Board
4949

5050
// BoardList FIXMEDOC
5151
func (s *ArduinoCoreServerImpl) BoardList(ctx context.Context, req *rpc.BoardListReq) (*rpc.BoardListResp, error) {
52-
return board.List(req.GetInstance().GetId())
52+
ports, err := board.List(req.GetInstance().GetId())
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
return &rpc.BoardListResp{
58+
Ports: ports,
59+
}, nil
5360
}
5461

5562
// BoardListAll FIXMEDOC

0 commit comments

Comments
 (0)