@@ -21,6 +21,8 @@ import (
2121 "io"
2222 "path/filepath"
2323 "strings"
24+ "sync"
25+ "time"
2426
2527 "github.com/arduino/arduino-cli/arduino"
2628 "github.com/arduino/arduino-cli/arduino/cores"
@@ -29,8 +31,10 @@ import (
2931 "github.com/arduino/arduino-cli/arduino/serialutils"
3032 "github.com/arduino/arduino-cli/arduino/sketch"
3133 "github.com/arduino/arduino-cli/commands"
34+ "github.com/arduino/arduino-cli/commands/board"
3235 "github.com/arduino/arduino-cli/executils"
3336 "github.com/arduino/arduino-cli/i18n"
37+ f "github.com/arduino/arduino-cli/internal/algorithms"
3438 rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3539 paths "github.com/arduino/go-paths-helper"
3640 properties "github.com/arduino/go-properties-orderedmap"
@@ -134,15 +138,23 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
134138 return nil , & arduino.CantOpenSketchError {Cause : err }
135139 }
136140
137- pme , release := commands .GetPackageManagerExplorer (req )
141+ pme , pmeRelease := commands .GetPackageManagerExplorer (req )
138142 if pme == nil {
139143 return nil , & arduino.InvalidInstanceError {}
140144 }
141- defer release ()
145+ defer pmeRelease ()
146+
147+ watch , watchRelease , err := board .Watch (& rpc.BoardListWatchRequest {Instance : req .GetInstance ()})
148+ if err != nil {
149+ logrus .WithError (err ).Error ("Error watching board ports" )
150+ } else {
151+ defer watchRelease ()
152+ }
142153
143154 updatedPort , err := runProgramAction (
144155 pme ,
145156 sk ,
157+ watch ,
146158 req .GetImportFile (),
147159 req .GetImportDir (),
148160 req .GetFqbn (),
@@ -189,21 +201,43 @@ func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest,
189201
190202func runProgramAction (pme * packagemanager.Explorer ,
191203 sk * sketch.Sketch ,
204+ watch <- chan * rpc.BoardListWatchResponse ,
192205 importFile , importDir , fqbnIn string , port * rpc.Port ,
193206 programmerID string ,
194207 verbose , verify , burnBootloader bool ,
195208 outStream , errStream io.Writer ,
196209 dryRun bool , userFields map [string ]string ) (* rpc.Port , error ) {
197210
198- if burnBootloader && programmerID == "" {
199- return nil , & arduino.MissingProgrammerError {}
200- }
201211 if port == nil || (port .Address == "" && port .Protocol == "" ) {
202212 // For no-port uploads use "default" protocol
203213 port = & rpc.Port {Protocol : "default" }
204214 }
205215 logrus .WithField ("port" , port ).Tracef ("Upload port" )
206216
217+ // Default newPort
218+ uploadCompleted := func () * rpc.Port { return nil }
219+ if watch != nil {
220+ // Run port detector
221+ uploadCompletedCtx , cancel := context .WithCancel (context .Background ())
222+ var newUploadPort * rpc.Port
223+ var wg sync.WaitGroup
224+ wg .Add (1 )
225+ go func () {
226+ newUploadPort = detectUploadPort (port , watch , uploadCompletedCtx )
227+ wg .Done ()
228+ }()
229+ uploadCompleted = func () * rpc.Port {
230+ cancel ()
231+ wg .Wait ()
232+ return newUploadPort
233+ }
234+ defer uploadCompleted () // defer in case of exit on error (ensures goroutine completion)
235+ }
236+
237+ if burnBootloader && programmerID == "" {
238+ return nil , & arduino.MissingProgrammerError {}
239+ }
240+
207241 fqbn , err := cores .ParseFQBN (fqbnIn )
208242 if err != nil {
209243 return nil , & arduino.InvalidFQBNError {Cause : err }
@@ -485,13 +519,73 @@ func runProgramAction(pme *packagemanager.Explorer,
485519 }
486520
487521 logrus .Tracef ("Upload successful" )
488- return nil , nil // TODO: return new port
522+ return uploadCompleted () , nil
489523}
490524
491- func detectNewUploadPort (oldPort * rpc.Port ) * rpc.Port {
492- logrus .Tracef ("Detecting new board port" )
493- // TODO
494- return nil
525+ func detectUploadPort (uploadPort * rpc.Port , watch <- chan * rpc.BoardListWatchResponse , uploadCtx context.Context ) * rpc.Port {
526+ log := logrus .WithField ("task" , "port_detection" )
527+ log .Tracef ("Detecting new board port after upload" )
528+
529+ defer func () {
530+ // On exit, discard all events until the watcher is closed
531+ go f .DiscardCh (watch )
532+ }()
533+
534+ // Ignore all events during the upload
535+ for {
536+ select {
537+ case ev , ok := <- watch :
538+ if ! ok {
539+ log .Error ("Upload port detection failed, watcher closed" )
540+ return nil
541+ }
542+ log .WithField ("event" , ev ).Trace ("Ignored watcher event before upload" )
543+ continue
544+ case <- uploadCtx .Done ():
545+ // Upload completed, move to the next phase
546+ }
547+ break
548+ }
549+
550+ // Pick the first port that is detected after the upload
551+ desiredHwID := uploadPort .HardwareId
552+ timeout := time .After (5 * time .Second )
553+ var candidate * rpc.Port
554+ for {
555+ select {
556+ case ev , ok := <- watch :
557+ if ! ok {
558+ log .Error ("Upload port detection failed, watcher closed" )
559+ return candidate
560+ }
561+ if ev .EventType == "remove" && candidate != nil {
562+ if candidate .Equals (ev .Port .GetPort ()) {
563+ log .WithField ("event" , ev ).Trace ("Candidate port is no more available" )
564+ candidate = nil
565+ continue
566+ }
567+ }
568+ if ev .EventType != "add" {
569+ log .WithField ("event" , ev ).Trace ("Ignored non-add event" )
570+ continue
571+ }
572+ candidate = ev .Port .GetPort ()
573+ log .WithField ("event" , ev ).Trace ("New upload port candidate" )
574+
575+ // If the current candidate port does not have the desired HW-ID do
576+ // not return it immediately.
577+ if desiredHwID != "" && candidate .GetHardwareId () != desiredHwID {
578+ log .Trace ("New candidate port did not match desired HW ID, keep watching..." )
579+ continue
580+ }
581+
582+ log .Trace ("Found new upload port!" )
583+ return candidate
584+ case <- timeout :
585+ log .Trace ("Timeout waiting for candidate port" )
586+ return candidate
587+ }
588+ }
495589}
496590
497591func runTool (recipeID string , props * properties.Map , outStream , errStream io.Writer , verbose bool , dryRun bool , toolEnv []string ) error {
0 commit comments