Skip to content

Commit 5aa4248

Browse files
authored
Optimized upload 1200 bps-touch and fixed some rare corner cases (#1267)
* Refactored upload output and debug info and removed unused functions * Skip serial port touch if the port is not connected * Perform touch only if required * Slightly change test to set serial port properties
1 parent 495a5f1 commit 5aa4248

File tree

2 files changed

+145
-85
lines changed

2 files changed

+145
-85
lines changed

Diff for: arduino/serialutils/serialutils.go

+99-51
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,13 @@
1616
package serialutils
1717

1818
import (
19+
"fmt"
1920
"time"
2021

2122
"github.com/pkg/errors"
2223
"go.bug.st/serial"
2324
)
2425

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-
4626
// TouchSerialPortAt1200bps open and close the serial port at 1200 bps. This
4727
// is used on many Arduino boards as a signal to put the board in "bootloader"
4828
// mode.
@@ -71,59 +51,127 @@ func TouchSerialPortAt1200bps(port string) error {
7151
return nil
7252
}
7353

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
54+
func getPortMap() (map[string]bool, error) {
55+
ports, err := serial.GetPortsList()
56+
if err != nil {
57+
return nil, errors.WithMessage(err, "listing serial ports")
8758
}
88-
return defaultPort, nil
59+
res := map[string]bool{}
60+
for _, port := range ports {
61+
res[port] = true
62+
}
63+
return res, nil
8964
}
9065

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-
}
66+
// ResetProgressCallbacks is a struct that defines a bunch of function callback
67+
// to observe the Reset function progress.
68+
type ResetProgressCallbacks struct {
69+
// TouchingPort is called to signal the 1200-bps touch of the reported port
70+
TouchingPort func(port string)
71+
// WaitingForNewSerial is called to signal that we are waiting for a new port
72+
WaitingForNewSerial func()
73+
// BootloaderPortFound is called to signal that the wait is completed and to
74+
// report the port found, or the empty string if no ports have been found and
75+
// the wait has timed-out.
76+
BootloaderPortFound func(port string)
77+
// Debug reports messages useful for debugging purposes. In normal conditions
78+
// these messages should not be displayed to the user.
79+
Debug func(msg string)
80+
}
10581

82+
// Reset a board using the 1200 bps port-touch and wait for new ports.
83+
// Both reset and wait are optional:
84+
// - if port is "" touch will be skipped
85+
// - if wait is false waiting will be skipped
86+
// If wait is true, this function will wait for a new port to appear and returns that
87+
// one, otherwise the empty string is returned if the new port can not be detected or
88+
// if the wait parameter is false.
89+
// The error is set if the port listing fails.
90+
func Reset(portToTouch string, wait bool, cb *ResetProgressCallbacks) (string, error) {
10691
last, err := getPortMap()
92+
if cb != nil && cb.Debug != nil {
93+
cb.Debug(fmt.Sprintf("LAST: %v", last))
94+
}
10795
if err != nil {
10896
return "", err
10997
}
11098

99+
if portToTouch != "" && last[portToTouch] {
100+
if cb != nil && cb.Debug != nil {
101+
cb.Debug(fmt.Sprintf("TOUCH: %v", portToTouch))
102+
}
103+
if cb != nil && cb.TouchingPort != nil {
104+
cb.TouchingPort(portToTouch)
105+
}
106+
if err := TouchSerialPortAt1200bps(portToTouch); err != nil {
107+
fmt.Println("TOUCH: error during reset:", err)
108+
}
109+
}
110+
111+
if !wait {
112+
return "", nil
113+
}
114+
if cb != nil && cb.WaitingForNewSerial != nil {
115+
cb.WaitingForNewSerial()
116+
}
117+
111118
deadline := time.Now().Add(10 * time.Second)
112119
for time.Now().Before(deadline) {
113120
now, err := getPortMap()
114121
if err != nil {
115122
return "", err
116123
}
117-
124+
if cb != nil && cb.Debug != nil {
125+
cb.Debug(fmt.Sprintf("WAIT: %v", now))
126+
}
127+
hasNewPorts := false
118128
for p := range now {
119129
if !last[p] {
120-
return p, nil // Found it!
130+
hasNewPorts = true
131+
break
132+
}
133+
}
134+
135+
if hasNewPorts {
136+
if cb != nil && cb.Debug != nil {
137+
cb.Debug("New ports found!")
138+
}
139+
140+
// on OS X, if the port is opened too quickly after it is detected,
141+
// a "Resource busy" error occurs, add a delay to workaround.
142+
// This apply to other platforms as well.
143+
time.Sleep(time.Second)
144+
145+
// Some boards have a glitch in the bootloader: some user experienced
146+
// the USB serial port appearing and disappearing rapidly before
147+
// settling.
148+
// This check ensure that the port is stable after one second.
149+
check, err := getPortMap()
150+
if err != nil {
151+
return "", err
152+
}
153+
if cb != nil && cb.Debug != nil {
154+
cb.Debug(fmt.Sprintf("CHECK: %v", check))
155+
}
156+
for p := range check {
157+
if !last[p] {
158+
if cb != nil && cb.BootloaderPortFound != nil {
159+
cb.BootloaderPortFound(p)
160+
}
161+
return p, nil // Found it!
162+
}
163+
}
164+
if cb != nil && cb.Debug != nil {
165+
cb.Debug("Port check failed... still waiting")
121166
}
122167
}
123168

124169
last = now
125170
time.Sleep(250 * time.Millisecond)
126171
}
127172

173+
if cb != nil && cb.BootloaderPortFound != nil {
174+
cb.BootloaderPortFound("")
175+
}
128176
return "", nil
129177
}

Diff for: commands/upload/upload.go

+46-34
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import (
3636
properties "github.com/arduino/go-properties-orderedmap"
3737
"github.com/pkg/errors"
3838
"github.com/sirupsen/logrus"
39-
"go.bug.st/serial"
4039
)
4140

4241
// Upload FIXMEDOC
@@ -293,46 +292,59 @@ func runProgramAction(pm *packagemanager.PackageManager,
293292
// to set the board in bootloader mode
294293
actualPort := port
295294
if programmer == nil && !burnBootloader {
296-
// Perform reset via 1200bps touch if requested
297-
if uploadProperties.GetBoolean("upload.use_1200bps_touch") {
298-
if port == "" {
299-
outStream.Write([]byte(fmt.Sprintln("Skipping 1200-bps touch reset: no serial port selected!")))
300-
} else {
301-
ports, err := serial.GetPortsList()
302-
if err != nil {
303-
return fmt.Errorf("cannot get serial port list: %s", err)
304-
}
305-
for _, p := range ports {
306-
if p == port {
307-
if verbose {
308-
outStream.Write([]byte(fmt.Sprintf("Performing 1200-bps touch reset on serial port %s", p)))
309-
outStream.Write([]byte(fmt.Sprintln()))
310-
}
311-
logrus.Infof("Touching port %s at 1200bps", port)
312-
if err := serialutils.TouchSerialPortAt1200bps(p); err != nil {
313-
outStream.Write([]byte(fmt.Sprintf("Cannot perform port reset: %s", err)))
314-
outStream.Write([]byte(fmt.Sprintln()))
315-
}
316-
break
295+
// Perform reset via 1200bps touch if requested and wait for upload port if requested.
296+
297+
touch := uploadProperties.GetBoolean("upload.use_1200bps_touch")
298+
wait := uploadProperties.GetBoolean("upload.wait_for_upload_port")
299+
portToTouch := ""
300+
if touch {
301+
portToTouch = port
302+
}
303+
304+
// if touch is requested but port is not specified, print a warning
305+
if touch && portToTouch == "" {
306+
outStream.Write([]byte(fmt.Sprintln("Skipping 1200-bps touch reset: no serial port selected!")))
307+
}
308+
309+
var cb *serialutils.ResetProgressCallbacks
310+
if verbose {
311+
cb = &serialutils.ResetProgressCallbacks{
312+
TouchingPort: func(port string) {
313+
logrus.WithField("phase", "board reset").Infof("Performing 1200-bps touch reset on serial port %s", port)
314+
outStream.Write([]byte(fmt.Sprintf("Performing 1200-bps touch reset on serial port %s", port)))
315+
outStream.Write([]byte(fmt.Sprintln()))
316+
},
317+
WaitingForNewSerial: func() {
318+
logrus.WithField("phase", "board reset").Info("Waiting for upload port...")
319+
outStream.Write([]byte(fmt.Sprintln("Waiting for upload port...")))
320+
},
321+
BootloaderPortFound: func(port string) {
322+
if port != "" {
323+
logrus.WithField("phase", "board reset").Infof("Upload port found on %s", port)
324+
outStream.Write([]byte(fmt.Sprintf("Upload port found on %s", port)))
325+
outStream.Write([]byte(fmt.Sprintln()))
326+
} else {
327+
logrus.WithField("phase", "board reset").Infof("No upload port found, using %s as fallback", actualPort)
328+
outStream.Write([]byte(fmt.Sprintf("No upload port found, using %s as fallback", actualPort)))
329+
outStream.Write([]byte(fmt.Sprintln()))
317330
}
318-
}
331+
},
332+
Debug: func(msg string) {
333+
logrus.WithField("phase", "board reset").Debug(msg)
334+
},
319335
}
320336
}
321-
322-
// Wait for upload port if requested
323-
if uploadProperties.GetBoolean("upload.wait_for_upload_port") {
324-
if verbose {
325-
outStream.Write([]byte(fmt.Sprintln("Waiting for upload port...")))
326-
}
327-
328-
actualPort, err = serialutils.WaitForNewSerialPortOrDefaultTo(actualPort)
329-
if err != nil {
330-
return errors.WithMessage(err, "detecting serial port")
337+
if newPort, err := serialutils.Reset(portToTouch, wait, cb); err != nil {
338+
outStream.Write([]byte(fmt.Sprintf("Cannot perform port reset: %s", err)))
339+
outStream.Write([]byte(fmt.Sprintln()))
340+
} else {
341+
if newPort != "" {
342+
actualPort = newPort
331343
}
332344
}
333345
}
334346

335-
if port != "" {
347+
if actualPort != "" {
336348
// Set serial port property
337349
uploadProperties.Set("serial.port", actualPort)
338350
if strings.HasPrefix(actualPort, "/dev/") {

0 commit comments

Comments
 (0)