@@ -20,6 +20,7 @@ import (
2020 "fmt"
2121 "io"
2222 "path/filepath"
23+ "strconv"
2324 "strings"
2425
2526 "github.com/arduino/arduino-cli/arduino/cores"
@@ -36,6 +37,43 @@ import (
3637 "github.com/sirupsen/logrus"
3738)
3839
40+ // SupportedUserFields returns a SupportedUserFieldsResponse containing all the UserFields supported
41+ // by the upload tools needed by the board using the protocol specified in SupportedUserFieldsRequest.
42+ func SupportedUserFields (ctx context.Context , req * rpc.SupportedUserFieldsRequest ) (* rpc.SupportedUserFieldsResponse , error ) {
43+ if req .Protocol == "" {
44+ return nil , fmt .Errorf ("missing protocol" )
45+ }
46+
47+ pm := commands .GetPackageManager (req .GetInstance ().GetId ())
48+ if pm == nil {
49+ return nil , fmt .Errorf ("invalid instance" )
50+ }
51+
52+ fqbn , err := cores .ParseFQBN (req .GetFqbn ())
53+ if err != nil {
54+ return nil , fmt .Errorf ("parsing fqbn: %s" , err )
55+ }
56+
57+ _ , platformRelease , board , _ , _ , err := pm .ResolveFQBN (fqbn )
58+ if err != nil {
59+ return nil , fmt .Errorf ("loading board data: %s" , err )
60+ }
61+
62+ toolId , err := getToolId (board .Properties , "upload" , req .Protocol )
63+ if err != nil {
64+ return nil , err
65+ }
66+
67+ userFields , err := getUserFields (toolId , platformRelease )
68+ if err != nil {
69+ return nil , err
70+ }
71+
72+ return & rpc.SupportedUserFieldsResponse {
73+ UserFields : userFields ,
74+ }, nil
75+ }
76+
3977// getToolId returns the ID of the tool that supports the action and protocol combination by searching in props.
4078// Returns error if tool cannot be found.
4179func getToolId (props * properties.Map , action , protocol string ) (string , error ) {
@@ -52,6 +90,35 @@ func getToolId(props *properties.Map, action, protocol string) (string, error) {
5290 return "" , fmt .Errorf ("cannot find tool: undefined '%s' property" , toolProperty )
5391}
5492
93+ // getUserFields return all user fields supported by the tools specified.
94+ // Returns error only in case the secret property is not a valid boolean.
95+ func getUserFields (toolId string , platformRelease * cores.PlatformRelease ) ([]* rpc.UserField , error ) {
96+ userFields := []* rpc.UserField {}
97+ fields := platformRelease .Properties .SubTree (fmt .Sprintf ("tools.%s.upload.field" , toolId ))
98+ keys := fields .FirstLevelKeys ()
99+
100+ for _ , key := range keys {
101+ value := fields .Get (key )
102+ secretProp := fmt .Sprintf ("%s.secret" , key )
103+ secret , ok := fields .GetOk (secretProp )
104+ if ! ok {
105+ secret = "false"
106+ }
107+ isSecret , err := strconv .ParseBool (secret )
108+ if err != nil {
109+ return nil , fmt .Errorf (`parsing "tools.%s.upload.field.%s.secret", property is not a boolean` , toolId , key )
110+ }
111+ userFields = append (userFields , & rpc.UserField {
112+ ToolId : toolId ,
113+ Name : key ,
114+ Label : value ,
115+ Secret : isSecret ,
116+ })
117+ }
118+
119+ return userFields , nil
120+ }
121+
55122// Upload FIXMEDOC
56123func Upload (ctx context.Context , req * rpc.UploadRequest , outStream io.Writer , errStream io.Writer ) (* rpc.UploadResponse , error ) {
57124 logrus .Tracef ("Upload %s on %s started" , req .GetSketchPath (), req .GetFqbn ())
@@ -80,6 +147,7 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
80147 outStream ,
81148 errStream ,
82149 req .GetDryRun (),
150+ req .GetUserFields (),
83151 )
84152 if err != nil {
85153 return nil , err
@@ -104,6 +172,7 @@ func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest,
104172 Programmer : req .GetProgrammer (),
105173 Verbose : req .GetVerbose (),
106174 Verify : req .GetVerify (),
175+ UserFields : req .GetUserFields (),
107176 }, outStream , errStream )
108177 return & rpc.UploadUsingProgrammerResponse {}, err
109178}
@@ -114,7 +183,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
114183 programmerID string ,
115184 verbose , verify , burnBootloader bool ,
116185 outStream , errStream io.Writer ,
117- dryRun bool ) error {
186+ dryRun bool , userFields map [ string ] string ) error {
118187
119188 if burnBootloader && programmerID == "" {
120189 return fmt .Errorf ("no programmer specified for burning bootloader" )
@@ -228,6 +297,14 @@ func runProgramAction(pm *packagemanager.PackageManager,
228297 }
229298 }
230299
300+ // Certain tools require the user to provide custom fields at run time,
301+ // if they've been provided set them
302+ // For more info:
303+ // https://github.com/arduino/tooling-rfcs/blob/main/RFCs/0002-pluggable-discovery.md#user-provided-fields
304+ for name , value := range userFields {
305+ uploadProperties .Set (fmt .Sprintf ("%s.field.%s" , action , name ), value )
306+ }
307+
231308 if ! uploadProperties .ContainsKey ("upload.protocol" ) && programmer == nil {
232309 return fmt .Errorf ("a programmer is required to upload for this board" )
233310 }
@@ -364,6 +441,11 @@ func runProgramAction(pm *packagemanager.PackageManager,
364441 }
365442 }
366443
444+ // Get Port properties gathered using Pluggable Discovery
445+ for prop , value := range actualPort .Properties {
446+ uploadProperties .Set (fmt .Sprintf ("upload.port.properties.%s" , prop ), value )
447+ }
448+
367449 // Run recipes for upload
368450 if burnBootloader {
369451 if err := runTool ("erase.pattern" , uploadProperties , outStream , errStream , verbose , dryRun ); err != nil {
0 commit comments