1
+
// This file is part of arduino-cli.
2
+
//
3
+
// Copyright 2020 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 commands
17
+
18
+
import (
19
+
"context"
20
+
"encoding/json"
21
+
"errors"
22
+
"fmt"
23
+
"io"
24
+
"net/http"
25
+
"regexp"
26
+
"sort"
27
+
"strings"
28
+
"time"
29
+
30
+
"github.com/arduino/arduino-cli/commands/cmderrors"
31
+
"github.com/arduino/arduino-cli/commands/internal/instances"
32
+
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
33
+
"github.com/arduino/arduino-cli/internal/cli/configuration"
34
+
"github.com/arduino/arduino-cli/internal/i18n"
35
+
"github.com/arduino/arduino-cli/internal/inventory"
36
+
"github.com/arduino/arduino-cli/pkg/fqbn"
37
+
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
38
+
"github.com/arduino/go-properties-orderedmap"
39
+
"github.com/sirupsen/logrus"
40
+
)
41
+
42
+
// BoardIdentify identifies the board based on the provided properties
43
+
func (s *arduinoCoreServerImpl) BoardIdentify(ctx context.Context, req *rpc.BoardIdentifyRequest) (*rpc.BoardIdentifyResponse, error) {
44
+
pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
45
+
if err != nil {
46
+
return nil, err
47
+
}
48
+
defer release()
49
+
50
+
props := properties.NewFromHashmap(req.GetProperties())
51
+
res, err := identify(pme, props, s.settings, !req.GetUseCloudApiForUnknownBoardDetection())
52
+
if err != nil {
53
+
return nil, err
54
+
}
55
+
return &rpc.BoardIdentifyResponse{
56
+
Boards: res,
57
+
}, nil
58
+
}
59
+
60
+
// identify returns a list of boards checking first the installed platforms or the Cloud API
61
+
func identify(pme *packagemanager.Explorer, properties *properties.Map, settings *configuration.Settings, skipCloudAPI bool) ([]*rpc.BoardListItem, error) {
62
+
if properties == nil {
63
+
return nil, nil
64
+
}
65
+
66
+
// first query installed cores through the Package Manager
67
+
boards := []*rpc.BoardListItem{}
68
+
logrus.Debug("Querying installed cores for board identification...")
69
+
for _, board := range pme.IdentifyBoard(properties) {
70
+
fqbn, err := fqbn.Parse(board.FQBN())
71
+
if err != nil {
72
+
return nil, &cmderrors.InvalidFQBNError{Cause: err}
73
+
}
74
+
fqbn.Configs = board.IdentifyBoardConfiguration(properties)
75
+
76
+
// We need the Platform maintaner for sorting so we set it here
77
+
platform := &rpc.Platform{
78
+
Metadata: &rpc.PlatformMetadata{
79
+
Maintainer: board.PlatformRelease.Platform.Package.Maintainer,
80
+
},
81
+
}
82
+
boards = append(boards, &rpc.BoardListItem{
83
+
Name: board.Name(),
84
+
Fqbn: fqbn.String(),
85
+
IsHidden: board.IsHidden(),
86
+
Platform: platform,
87
+
})
88
+
}
89
+
90
+
// if installed cores didn't recognize the board, try querying
91
+
// the builder API if the board is a USB device port
92
+
if len(boards) == 0 && !skipCloudAPI && !settings.SkipCloudApiForBoardDetection() {
93
+
items, err := identifyViaCloudAPI(properties, settings)
94
+
if err != nil {
95
+
// this is bad, but keep going
96
+
logrus.WithError(err).Debug("Error querying builder API")
97
+
}
98
+
boards = items
99
+
}
100
+
101
+
// Sort by FQBN alphabetically
102
+
sort.Slice(boards, func(i, j int) bool {
103
+
return strings.ToLower(boards[i].GetFqbn()) < strings.ToLower(boards[j].GetFqbn())
104
+
})
105
+
106
+
// Put Arduino boards before others in case there are non Arduino boards with identical VID:PID combination
107
+
sort.SliceStable(boards, func(i, j int) bool {
108
+
if boards[i].GetPlatform().GetMetadata().GetMaintainer() == "Arduino" && boards[j].GetPlatform().GetMetadata().GetMaintainer() != "Arduino" {
109
+
return true
110
+
}
111
+
return false
112
+
})
113
+
114
+
// We need the Board's Platform only for sorting but it shouldn't be present in the output
115
+
for _, board := range boards {
116
+
board.Platform = nil
117
+
}
118
+
119
+
return boards, nil
120
+
}
121
+
122
+
func identifyViaCloudAPI(props *properties.Map, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
123
+
// If the port is not USB do not try identification via cloud
124
+
if !props.ContainsKey("vid") || !props.ContainsKey("pid") {
125
+
return nil, nil
126
+
}
127
+
128
+
logrus.Debug("Querying builder API for board identification...")
129
+
return cachedAPIByVidPid(props.Get("vid"), props.Get("pid"), settings)
130
+
}
131
+
132
+
var (
133
+
vidPidURL = "https://builder.arduino.cc/v3/boards/byVidPid"
134
+
validVidPid = regexp.MustCompile(`0[xX][a-fA-F\d]{4}`)
135
+
)
136
+
137
+
func cachedAPIByVidPid(vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
138
+
var resp []*rpc.BoardListItem
139
+
140
+
cacheKey := fmt.Sprintf("cache.builder-api.v3/boards/byvid/pid/%s/%s", vid, pid)
141
+
if cachedResp := inventory.Store.GetString(cacheKey + ".data"); cachedResp != "" {
142
+
ts := inventory.Store.GetTime(cacheKey + ".ts")
143
+
if time.Since(ts) < time.Hour*24 {
144
+
// Use cached response
145
+
if err := json.Unmarshal([]byte(cachedResp), &resp); err == nil {
146
+
return resp, nil
147
+
}
148
+
}
149
+
}
150
+
151
+
resp, err := apiByVidPid(vid, pid, settings) // Perform API requrest
152
+
153
+
if err == nil {
154
+
if cachedResp, err := json.Marshal(resp); err == nil {
155
+
inventory.Store.Set(cacheKey+".data", string(cachedResp))
156
+
inventory.Store.Set(cacheKey+".ts", time.Now())
157
+
inventory.WriteStore()
158
+
}
159
+
}
160
+
return resp, err
161
+
}
162
+
163
+
func apiByVidPid(vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
164
+
// ensure vid and pid are valid before hitting the API
165
+
if !validVidPid.MatchString(vid) {
166
+
return nil, errors.New(i18n.Tr("Invalid vid value: '%s'", vid))
167
+
}
168
+
if !validVidPid.MatchString(pid) {
169
+
return nil, errors.New(i18n.Tr("Invalid pid value: '%s'", pid))
170
+
}
171
+
172
+
url := fmt.Sprintf("%s/%s/%s", vidPidURL, vid, pid)
173
+
req, _ := http.NewRequest("GET", url, nil)
174
+
req.Header.Set("Content-Type", "application/json")
175
+
176
+
httpClient, err := settings.NewHttpClient()
177
+
if err != nil {
178
+
return nil, fmt.Errorf("%s: %w", i18n.Tr("failed to initialize http client"), err)
179
+
}
180
+
181
+
res, err := httpClient.Do(req)
182
+
if err != nil {
183
+
return nil, fmt.Errorf("%s: %w", i18n.Tr("error querying Arduino Cloud Api"), err)
184
+
}
185
+
if res.StatusCode == 404 {
186
+
// This is not an error, it just means that the board is not recognized
187
+
return nil, nil
188
+
}
189
+
if res.StatusCode >= 400 {
190
+
return nil, errors.New(i18n.Tr("the server responded with status %s", res.Status))
191
+
}
192
+
193
+
resp, err := io.ReadAll(res.Body)
194
+
if err != nil {
195
+
return nil, err
196
+
}
197
+
if err := res.Body.Close(); err != nil {
198
+
return nil, err
199
+
}
200
+
201
+
var dat map[string]interface{}
202
+
if err := json.Unmarshal(resp, &dat); err != nil {
203
+
return nil, fmt.Errorf("%s: %w", i18n.Tr("error processing response from server"), err)
204
+
}
205
+
name, nameFound := dat["name"].(string)
206
+
fqbn, fbqnFound := dat["fqbn"].(string)
207
+
if !nameFound || !fbqnFound {
208
+
return nil, errors.New(i18n.Tr("wrong format in server response"))
209
+
}
210
+
211
+
return []*rpc.BoardListItem{
212
+
{
213
+
Name: name,
214
+
Fqbn: fqbn,
215
+
},
216
+
}, nil
217
+
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4