Skip to content

Commit f2d14b2

Browse files
masciMaurizio Branca
andauthored
Query the backend to get the fqbn when a core is not installed (arduino#336)
* query the backend to get the fqbn * Update commands/board/list.go Co-Authored-By: Maurizio Branca <m.branca@arduino.cc>
1 parent 5858721 commit f2d14b2

File tree

7 files changed

+185
-17
lines changed

7 files changed

+185
-17
lines changed

arduino/cores/packagemanager/identify.go

Lines changed: 1 addition & 1 deletion
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{}

cli/board/list.go

Lines changed: 5 additions & 6 deletions
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()

cli/output/table.go

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

cli/output/text.go

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

commands/board/list.go

Lines changed: 79 additions & 5 deletions
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
}

commands/board/list_test.go

Lines changed: 89 additions & 0 deletions
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 license@arduino.cc.
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+
}

commands/daemon/daemon.go

Lines changed: 8 additions & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy