Skip to content

Commit 081eb7f

Browse files
fix a panic when the serial port is busy
1 parent 37c4d48 commit 081eb7f

File tree

2 files changed

+200
-1
lines changed

2 files changed

+200
-1
lines changed

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import (
77
"log"
88
"time"
99

10-
"github.com/arduino/arduino-cli/arduino/serialutils"
1110
"github.com/arduino/arduino-cli/executils"
1211
helper "github.com/arduino/fwuploader-plugin-helper"
1312
"github.com/arduino/go-paths-helper"
1413
"github.com/arduino/uno-r4-wifi-fwuploader-plugin/certificate"
1514
"github.com/arduino/uno-r4-wifi-fwuploader-plugin/serial"
15+
serialutils "github.com/arduino/uno-r4-wifi-fwuploader-plugin/serial/utils"
1616
semver "go.bug.st/relaxed-semver"
1717
serialx "go.bug.st/serial"
1818
"golang.org/x/exp/slog"

serial/utils/utils.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Those function are token from https://github.com/arduino/arduino-cli/blob/master/arduino/serialutils/serialutils.go
2+
// that's because we don't have the `tr` here and importing the serialutils from the cli will lead to a panic
3+
package utils
4+
5+
import (
6+
"fmt"
7+
"runtime"
8+
"strings"
9+
"time"
10+
11+
"go.bug.st/serial"
12+
)
13+
14+
// TouchSerialPortAt1200bps open and close the serial port at 1200 bps. This
15+
// is used on many Arduino boards as a signal to put the board in "bootloader"
16+
// mode.
17+
func TouchSerialPortAt1200bps(port string) error {
18+
// Open port
19+
p, err := serial.Open(port, &serial.Mode{BaudRate: 1200})
20+
if err != nil {
21+
return fmt.Errorf("opening port at 1200bps")
22+
}
23+
24+
if runtime.GOOS != "windows" {
25+
// This is not required on Windows
26+
// TODO: Investigate if it can be removed for other OS too
27+
28+
// Set DTR to false
29+
if err = p.SetDTR(false); err != nil {
30+
p.Close()
31+
return fmt.Errorf("setting DTR to OFF")
32+
}
33+
}
34+
35+
// Close serial port
36+
p.Close()
37+
38+
// Scanning for available ports seems to open the port or
39+
// otherwise assert DTR, which would cancel the WDT reset if
40+
// it happens within 250 ms. So we wait until the reset should
41+
// have already occurred before going on.
42+
time.Sleep(500 * time.Millisecond)
43+
44+
return nil
45+
}
46+
47+
func getPortMap() (map[string]bool, error) {
48+
ports, err := serial.GetPortsList()
49+
if err != nil {
50+
return nil, fmt.Errorf("listing serial ports")
51+
}
52+
res := map[string]bool{}
53+
for _, port := range ports {
54+
res[port] = true
55+
}
56+
return res, nil
57+
}
58+
59+
// ResetProgressCallbacks is a struct that defines a bunch of function callback
60+
// to observe the Reset function progress.
61+
type ResetProgressCallbacks struct {
62+
// TouchingPort is called to signal the 1200-bps touch of the reported port
63+
TouchingPort func(port string)
64+
// WaitingForNewSerial is called to signal that we are waiting for a new port
65+
WaitingForNewSerial func()
66+
// BootloaderPortFound is called to signal that the wait is completed and to
67+
// report the port found, or the empty string if no ports have been found and
68+
// the wait has timed-out.
69+
BootloaderPortFound func(port string)
70+
// Debug reports messages useful for debugging purposes. In normal conditions
71+
// these messages should not be displayed to the user.
72+
Debug func(msg string)
73+
}
74+
75+
// Reset a board using the 1200 bps port-touch and wait for new ports.
76+
// Both reset and wait are optional:
77+
// - if port is "" touch will be skipped
78+
// - if wait is false waiting will be skipped
79+
// If wait is true, this function will wait for a new port to appear and returns that
80+
// one, otherwise the empty string is returned if the new port can not be detected or
81+
// if the wait parameter is false.
82+
// If dryRun is set to true this function will only emulate the port reset without actually
83+
// performing it, this is useful to mockup for unit-testing and CI.
84+
// In dryRun mode if the `portToTouch` ends with `"999"` and wait is true, Reset will
85+
// return a new "bootloader" port as `portToTouch+"0"`.
86+
// The error is set if the port listing fails.
87+
func Reset(portToTouch string, wait bool, cb *ResetProgressCallbacks, dryRun bool) (string, error) {
88+
getPorts := getPortMap // non dry-run default
89+
if dryRun {
90+
emulatedPort := portToTouch
91+
getPorts = func() (map[string]bool, error) {
92+
res := map[string]bool{}
93+
if emulatedPort != "" {
94+
res[emulatedPort] = true
95+
}
96+
if strings.HasSuffix(emulatedPort, "999") {
97+
emulatedPort += "0"
98+
} else if emulatedPort == "" {
99+
emulatedPort = "newport"
100+
}
101+
return res, nil
102+
}
103+
}
104+
105+
last, err := getPorts()
106+
if cb != nil && cb.Debug != nil {
107+
cb.Debug(fmt.Sprintf("LAST: %v", last))
108+
}
109+
if err != nil {
110+
return "", err
111+
}
112+
113+
if portToTouch != "" && last[portToTouch] {
114+
if cb != nil && cb.Debug != nil {
115+
cb.Debug(fmt.Sprintf("TOUCH: %v", portToTouch))
116+
}
117+
if cb != nil && cb.TouchingPort != nil {
118+
cb.TouchingPort(portToTouch)
119+
}
120+
if dryRun {
121+
// do nothing!
122+
} else {
123+
if err := TouchSerialPortAt1200bps(portToTouch); err != nil && !wait {
124+
return "", fmt.Errorf("TOUCH: error during reset: %v", err)
125+
}
126+
}
127+
}
128+
129+
if !wait {
130+
return "", nil
131+
}
132+
if cb != nil && cb.WaitingForNewSerial != nil {
133+
cb.WaitingForNewSerial()
134+
}
135+
136+
deadline := time.Now().Add(10 * time.Second)
137+
if dryRun {
138+
// use a much lower timeout in dryRun
139+
deadline = time.Now().Add(100 * time.Millisecond)
140+
}
141+
for time.Now().Before(deadline) {
142+
now, err := getPorts()
143+
if err != nil {
144+
return "", err
145+
}
146+
if cb != nil && cb.Debug != nil {
147+
cb.Debug(fmt.Sprintf("WAIT: %v", now))
148+
}
149+
hasNewPorts := false
150+
for p := range now {
151+
if !last[p] {
152+
hasNewPorts = true
153+
break
154+
}
155+
}
156+
157+
if hasNewPorts {
158+
if cb != nil && cb.Debug != nil {
159+
cb.Debug("New ports found!")
160+
}
161+
162+
// on OS X, if the port is opened too quickly after it is detected,
163+
// a "Resource busy" error occurs, add a delay to workaround.
164+
// This apply to other platforms as well.
165+
time.Sleep(time.Second)
166+
167+
// Some boards have a glitch in the bootloader: some user experienced
168+
// the USB serial port appearing and disappearing rapidly before
169+
// settling.
170+
// This check ensure that the port is stable after one second.
171+
check, err := getPorts()
172+
if err != nil {
173+
return "", err
174+
}
175+
if cb != nil && cb.Debug != nil {
176+
cb.Debug(fmt.Sprintf("CHECK: %v", check))
177+
}
178+
for p := range check {
179+
if !last[p] {
180+
if cb != nil && cb.BootloaderPortFound != nil {
181+
cb.BootloaderPortFound(p)
182+
}
183+
return p, nil // Found it!
184+
}
185+
}
186+
if cb != nil && cb.Debug != nil {
187+
cb.Debug("Port check failed... still waiting")
188+
}
189+
}
190+
191+
last = now
192+
time.Sleep(250 * time.Millisecond)
193+
}
194+
195+
if cb != nil && cb.BootloaderPortFound != nil {
196+
cb.BootloaderPortFound("")
197+
}
198+
return "", nil
199+
}

0 commit comments

Comments
 (0)