Skip to content

Commit 69ac12c

Browse files
authored
[skip-changelog] Factoring out serial subrotuines to perform board reset (#977)
So they can be reused in other projects.
1 parent 8c2f90a commit 69ac12c

File tree

2 files changed

+136
-78
lines changed

2 files changed

+136
-78
lines changed

Diff for: arduino/serialutils/serialutils.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 [email protected].
15+
16+
package serialutils
17+
18+
import (
19+
"time"
20+
21+
"github.com/pkg/errors"
22+
"go.bug.st/serial"
23+
)
24+
25+
// Reset a board using the 1200 bps port-touch. If wait is true, it will wait
26+
// for a new port to appear (which could change sometimes) and returns that.
27+
// The error is set if the port listing fails.
28+
func Reset(port string, wait bool) (string, error) {
29+
// Touch port at 1200bps
30+
if err := TouchSerialPortAt1200bps(port); err != nil {
31+
return "", errors.New("1200bps Touch")
32+
}
33+
34+
if wait {
35+
// Wait for port to disappear and reappear
36+
if p, err := WaitForNewSerialPortOrDefaultTo(port); err == nil {
37+
port = p
38+
} else {
39+
return "", errors.WithMessage(err, "detecting upload port")
40+
}
41+
}
42+
43+
return port, nil
44+
}
45+
46+
// TouchSerialPortAt1200bps open and close the serial port at 1200 bps. This
47+
// is used on many Arduino boards as a signal to put the board in "bootloader"
48+
// mode.
49+
func TouchSerialPortAt1200bps(port string) error {
50+
// Open port
51+
p, err := serial.Open(port, &serial.Mode{BaudRate: 1200})
52+
if err != nil {
53+
return errors.WithMessage(err, "opening port at 1200bps")
54+
}
55+
56+
// Set DTR to false
57+
if err = p.SetDTR(false); err != nil {
58+
p.Close()
59+
return errors.WithMessage(err, "setting DTR to OFF")
60+
}
61+
62+
// Close serial port
63+
p.Close()
64+
65+
// Scanning for available ports seems to open the port or
66+
// otherwise assert DTR, which would cancel the WDT reset if
67+
// it happens within 250 ms. So we wait until the reset should
68+
// have already occurred before going on.
69+
time.Sleep(500 * time.Millisecond)
70+
71+
return nil
72+
}
73+
74+
// WaitForNewSerialPortOrDefaultTo is meant to be called just after a reset. It watches the ports connected
75+
// to the machine until a port appears. The new appeared port is returned or, if the operation
76+
// timeouts, the default port provided as parameter is returned.
77+
func WaitForNewSerialPortOrDefaultTo(defaultPort string) (string, error) {
78+
if p, err := WaitForNewSerialPort(); err != nil {
79+
return "", errors.WithMessage(err, "detecting upload port")
80+
} else if p != "" {
81+
// on OS X, if the port is opened too quickly after it is detected,
82+
// a "Resource busy" error occurs, add a delay to workaround.
83+
// This apply to other platforms as well.
84+
time.Sleep(500 * time.Millisecond)
85+
86+
return p, nil
87+
}
88+
return defaultPort, nil
89+
}
90+
91+
// WaitForNewSerialPort is meant to be called just after a reset. It watches the ports connected
92+
// to the machine until a port appears. The new appeared port is returned.
93+
func WaitForNewSerialPort() (string, error) {
94+
getPortMap := func() (map[string]bool, error) {
95+
ports, err := serial.GetPortsList()
96+
if err != nil {
97+
return nil, errors.WithMessage(err, "listing serial ports")
98+
}
99+
res := map[string]bool{}
100+
for _, port := range ports {
101+
res[port] = true
102+
}
103+
return res, nil
104+
}
105+
106+
last, err := getPortMap()
107+
if err != nil {
108+
return "", err
109+
}
110+
111+
deadline := time.Now().Add(10 * time.Second)
112+
for time.Now().Before(deadline) {
113+
now, err := getPortMap()
114+
if err != nil {
115+
return "", err
116+
}
117+
118+
for p := range now {
119+
if !last[p] {
120+
return p, nil // Found it!
121+
}
122+
}
123+
124+
last = now
125+
time.Sleep(250 * time.Millisecond)
126+
}
127+
128+
return "", nil
129+
}

Diff for: commands/upload/upload.go

+7-78
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ import (
2222
"net/url"
2323
"path/filepath"
2424
"strings"
25-
"time"
2625

2726
"github.com/arduino/arduino-cli/arduino/cores"
2827
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
28+
"github.com/arduino/arduino-cli/arduino/serialutils"
2929
"github.com/arduino/arduino-cli/arduino/sketches"
30-
"github.com/arduino/arduino-cli/cli/feedback"
3130
"github.com/arduino/arduino-cli/commands"
3231
"github.com/arduino/arduino-cli/executils"
3332
rpc "github.com/arduino/arduino-cli/rpc/commands"
@@ -275,38 +274,26 @@ func runProgramAction(pm *packagemanager.PackageManager,
275274
outStream.Write([]byte(fmt.Sprintf("Performing 1200-bps touch reset on serial port %s", p)))
276275
outStream.Write([]byte(fmt.Sprintln()))
277276
}
278-
if err := touchSerialPortAt1200bps(p); err != nil {
277+
logrus.Infof("Touching port %s at 1200bps", port)
278+
if err := serialutils.TouchSerialPortAt1200bps(p); err != nil {
279279
outStream.Write([]byte(fmt.Sprintf("Cannot perform port reset: %s", err)))
280280
outStream.Write([]byte(fmt.Sprintln()))
281281
}
282282
break
283283
}
284284
}
285-
286-
// Scanning for available ports seems to open the port or
287-
// otherwise assert DTR, which would cancel the WDT reset if
288-
// it happened within 250 ms. So we wait until the reset should
289-
// have already occurred before we start scanning.
290-
time.Sleep(500 * time.Millisecond)
291285
}
292286

293287
// Wait for upload port if requested
294288
if uploadProperties.GetBoolean("upload.wait_for_upload_port") {
295289
if verbose {
296290
outStream.Write([]byte(fmt.Sprintln("Waiting for upload port...")))
297291
}
298-
if p, err := waitForNewSerialPort(); err != nil {
299-
return fmt.Errorf("cannot detect serial ports: %s", err)
300-
} else if p == "" {
301-
feedback.Print("No new serial port detected.")
302-
} else {
303-
actualPort = p
304-
}
305292

306-
// on OS X, if the port is opened too quickly after it is detected,
307-
// a "Resource busy" error occurs, add a delay to workaround.
308-
// This apply to other platforms as well.
309-
time.Sleep(500 * time.Millisecond)
293+
actualPort, err = serialutils.WaitForNewSerialPortOrDefaultTo(actualPort)
294+
if err != nil {
295+
return errors.WithMessage(err, "detecting serial port")
296+
}
310297
}
311298
}
312299

@@ -382,64 +369,6 @@ func runTool(recipeID string, props *properties.Map, outStream, errStream io.Wri
382369
return nil
383370
}
384371

385-
func touchSerialPortAt1200bps(port string) error {
386-
logrus.Infof("Touching port %s at 1200bps", port)
387-
388-
// Open port
389-
p, err := serial.Open(port, &serial.Mode{BaudRate: 1200})
390-
if err != nil {
391-
return fmt.Errorf("opening port: %s", err)
392-
}
393-
defer p.Close()
394-
395-
if err = p.SetDTR(false); err != nil {
396-
return fmt.Errorf("cannot set DTR")
397-
}
398-
return nil
399-
}
400-
401-
// waitForNewSerialPort is meant to be called just after a reset. It watches the ports connected
402-
// to the machine until a port appears. The new appeared port is returned
403-
func waitForNewSerialPort() (string, error) {
404-
logrus.Infof("Waiting for upload port...")
405-
406-
getPortMap := func() (map[string]bool, error) {
407-
ports, err := serial.GetPortsList()
408-
if err != nil {
409-
return nil, err
410-
}
411-
res := map[string]bool{}
412-
for _, port := range ports {
413-
res[port] = true
414-
}
415-
return res, nil
416-
}
417-
418-
last, err := getPortMap()
419-
if err != nil {
420-
return "", fmt.Errorf("scanning serial port: %s", err)
421-
}
422-
423-
deadline := time.Now().Add(10 * time.Second)
424-
for time.Now().Before(deadline) {
425-
now, err := getPortMap()
426-
if err != nil {
427-
return "", fmt.Errorf("scanning serial port: %s", err)
428-
}
429-
430-
for p := range now {
431-
if !last[p] {
432-
return p, nil // Found it!
433-
}
434-
}
435-
436-
last = now
437-
time.Sleep(250 * time.Millisecond)
438-
}
439-
440-
return "", nil
441-
}
442-
443372
func determineBuildPathAndSketchName(importFile, importDir string, sketch *sketches.Sketch, fqbn *cores.FQBN) (*paths.Path, string, error) {
444373
// In general, compiling a sketch will produce a set of files that are
445374
// named as the sketch but have different extensions, for example Sketch.ino

0 commit comments

Comments
 (0)