From e55549e9a3de327b36bb3ba8bbb15b7d53ed35b2 Mon Sep 17 00:00:00 2001 From: Alessio Perugini Date: Tue, 12 Aug 2025 13:11:25 +0200 Subject: [PATCH 1/3] commands: use the new endpoint to detect boards --- commands/service_board_identify.go | 31 ++++++++++++++----------- commands/service_board_identify_test.go | 24 ++++++++++--------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/commands/service_board_identify.go b/commands/service_board_identify.go index 787de81cee3..8df2c44d4fa 100644 --- a/commands/service_board_identify.go +++ b/commands/service_board_identify.go @@ -130,7 +130,7 @@ func identifyViaCloudAPI(ctx context.Context, props *properties.Map, settings *c } var ( - vidPidURL = "https://builder.arduino.cc/v3/boards/byVidPid" + vidPidURL = "https://api2.arduino.cc/boards/v1/boards" validVidPid = regexp.MustCompile(`0[xX][a-fA-F\d]{4}`) ) @@ -169,7 +169,7 @@ func apiByVidPid(ctx context.Context, vid, pid string, settings *configuration.S return nil, errors.New(i18n.Tr("Invalid pid value: '%s'", pid)) } - url := fmt.Sprintf("%s/%s/%s", vidPidURL, vid, pid) + url := fmt.Sprintf("%s?vid-pid=%s-%s", vidPidURL, vid, pid) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("Content-Type", "application/json") @@ -198,20 +198,23 @@ func apiByVidPid(ctx context.Context, vid, pid string, settings *configuration.S return nil, err } - var dat map[string]interface{} + type boardsResponse struct { + Items []struct { + Name string `json:"name"` + FQBN string `json:"fqbn"` + } `json:"items"` + } + var dat boardsResponse if err := json.Unmarshal(resp, &dat); err != nil { return nil, fmt.Errorf("%s: %w", i18n.Tr("error processing response from server"), err) } - name, nameFound := dat["name"].(string) - fqbn, fbqnFound := dat["fqbn"].(string) - if !nameFound || !fbqnFound { - return nil, errors.New(i18n.Tr("wrong format in server response")) - } - return []*rpc.BoardListItem{ - { - Name: name, - Fqbn: fqbn, - }, - }, nil + response := make([]*rpc.BoardListItem, len(dat.Items)) + for i, v := range dat.Items { + if v.Name == "" || v.FQBN == "" { + return nil, errors.New(i18n.Tr("wrong format in server response")) + } + response[i] = &rpc.BoardListItem{Name: v.Name, Fqbn: v.FQBN} + } + return response, nil } diff --git a/commands/service_board_identify_test.go b/commands/service_board_identify_test.go index 31687359885..3a1aee360a6 100644 --- a/commands/service_board_identify_test.go +++ b/commands/service_board_identify_test.go @@ -35,13 +35,15 @@ func TestGetByVidPid(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, ` { - "architecture": "samd", - "fqbn": "arduino:samd:mkr1000", - "href": "/v3/boards/arduino:samd:mkr1000", - "id": "mkr1000", - "name": "Arduino/Genuino MKR1000", - "package": "arduino", - "plan": "create-free" + "items": [ + { + "fqbn": "arduino:avr:uno", + "vendor": "arduino", + "architecture": "avr", + "board_id": "uno", + "name": "Arduino Uno" + } + ] } `) })) @@ -49,11 +51,11 @@ func TestGetByVidPid(t *testing.T) { vidPidURL = ts.URL settings := configuration.NewSettings() - res, err := apiByVidPid(context.Background(), "0xf420", "0XF069", settings) + res, err := apiByVidPid(context.Background(), "0x2341", "0x0043", settings) require.Nil(t, err) require.Len(t, res, 1) - require.Equal(t, "Arduino/Genuino MKR1000", res[0].GetName()) - require.Equal(t, "arduino:samd:mkr1000", res[0].GetFqbn()) + require.Equal(t, "Arduino Uno", res[0].GetName()) + require.Equal(t, "arduino:avr:uno", res[0].GetFqbn()) // wrong vid (too long), wrong pid (not an hex value) @@ -95,7 +97,7 @@ func TestGetByVidPidMalformedResponse(t *testing.T) { settings := configuration.NewSettings() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{}") + fmt.Fprintln(w, `{"items":[{}]}`) })) defer ts.Close() From d56acae082c9ad88eb69b57c9bbb8e24a546b6db Mon Sep 17 00:00:00 2001 From: Alessio Perugini Date: Tue, 12 Aug 2025 13:16:11 +0200 Subject: [PATCH 2/3] commands: change cacheKey --- commands/service_board_identify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/service_board_identify.go b/commands/service_board_identify.go index 8df2c44d4fa..6caf658d044 100644 --- a/commands/service_board_identify.go +++ b/commands/service_board_identify.go @@ -137,7 +137,7 @@ var ( func cachedAPIByVidPid(ctx context.Context, vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) { var resp []*rpc.BoardListItem - cacheKey := fmt.Sprintf("cache.builder-api.v3/boards/byvid/pid/%s/%s", vid, pid) + cacheKey := fmt.Sprintf("cache.api2.arduino.cc/boards/v1/boards?vid-pid=%s-%s", vid, pid) if cachedResp := inventory.Store.GetString(cacheKey + ".data"); cachedResp != "" { ts := inventory.Store.GetTime(cacheKey + ".ts") if time.Since(ts) < time.Hour*24 { From 1c41822565d5e9f93fe6d5f40946147eec2edfae Mon Sep 17 00:00:00 2001 From: Alessio Perugini Date: Tue, 12 Aug 2025 13:28:16 +0200 Subject: [PATCH 3/3] commands: push always on top the `arduino` vendor --- commands/service_board_identify.go | 10 +++++++ commands/service_board_identify_test.go | 35 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/commands/service_board_identify.go b/commands/service_board_identify.go index 6caf658d044..fd3fcee300c 100644 --- a/commands/service_board_identify.go +++ b/commands/service_board_identify.go @@ -23,6 +23,7 @@ import ( "io" "net/http" "regexp" + "slices" "sort" "strings" "time" @@ -216,5 +217,14 @@ func apiByVidPid(ctx context.Context, vid, pid string, settings *configuration.S } response[i] = &rpc.BoardListItem{Name: v.Name, Fqbn: v.FQBN} } + + // In case multiple platform matches the same vid-pid we put on top + // the arduino ones + slices.SortFunc(response, func(a, b *rpc.BoardListItem) int { + if strings.HasPrefix(a.Fqbn, "arduino") { + return -1 + } + return 0 + }) return response, nil } diff --git a/commands/service_board_identify_test.go b/commands/service_board_identify_test.go index 3a1aee360a6..28561b19cf8 100644 --- a/commands/service_board_identify_test.go +++ b/commands/service_board_identify_test.go @@ -63,6 +63,41 @@ func TestGetByVidPid(t *testing.T) { require.NotNil(t, err) } +func TestGetByVidPidPutArduinoOnTop(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, ` +{ + "items": [ + { + "fqbn": "esp32:esp32:nano_nora", + "vendor": "esp32", + "architecture": "esp32", + "board_id": "nano_nora", + "name": "Arduino Nano ESP32" + }, + { + "fqbn": "arduino:esp32:nano_nora", + "vendor": "arduino", + "architecture": "esp32", + "board_id": "nano_nora", + "name": "Arduino Nano ESP32" + } + ] +} + `) + })) + defer ts.Close() + + vidPidURL = ts.URL + settings := configuration.NewSettings() + res, err := apiByVidPid(context.Background(), "0x2341", "0x0070", settings) + require.Nil(t, err) + require.Len(t, res, 2) + require.Equal(t, "Arduino Nano ESP32", res[0].GetName()) + require.Equal(t, "arduino:esp32:nano_nora", res[0].GetFqbn()) + require.Equal(t, "esp32:esp32:nano_nora", res[1].GetFqbn()) +} + func TestGetByVidPidNotFound(t *testing.T) { settings := configuration.NewSettings()