diff --git a/pkg/espflasher/reset.go b/pkg/espflasher/reset.go index 855b443..789d2c2 100644 --- a/pkg/espflasher/reset.go +++ b/pkg/espflasher/reset.go @@ -185,8 +185,13 @@ func hardReset(port serial.Port, usesUSB bool) error { } set(port.SetRTS(true)) // EN=LOW (chip in reset) if usesUSB { + // The chip resets on this edge. An app that moves the USB PHY to + // USB-OTG removes the port before the release, so ignore that error. + reset := firstErr == nil time.Sleep(200 * time.Millisecond) - set(port.SetRTS(false)) + if err := port.SetRTS(false); !reset { + set(err) + } time.Sleep(200 * time.Millisecond) } else { time.Sleep(100 * time.Millisecond) diff --git a/pkg/espflasher/reset_test.go b/pkg/espflasher/reset_test.go index b6f0af1..170a0b7 100644 --- a/pkg/espflasher/reset_test.go +++ b/pkg/espflasher/reset_test.go @@ -411,6 +411,38 @@ func TestHardResetReportsError(t *testing.T) { } } +// goneAfterResetPort fails every write after the first SetRTS(true), like a +// USB-JTAG port that disappears when the new app moves the PHY to USB-OTG. +type goneAfterResetPort struct { + recordingPort + gone bool +} + +func (g *goneAfterResetPort) SetDTR(dtr bool) error { + g.recordingPort.SetDTR(dtr) //nolint:errcheck + if g.gone { + return errors.New("broken pipe") + } + return nil +} + +func (g *goneAfterResetPort) SetRTS(rts bool) error { + g.recordingPort.SetRTS(rts) //nolint:errcheck + if g.gone { + return errors.New("broken pipe") + } + g.gone = rts + return nil +} + +// TestHardResetUSBPortGoneAfterReset verifies the USB path reports success +// when only the release write fails, since the reset already happened. +func TestHardResetUSBPortGoneAfterReset(t *testing.T) { + assert.NoError(t, hardReset(&goneAfterResetPort{}, true)) + assert.Error(t, hardReset(&goneAfterResetPort{}, false), + "UART bridges keep the port, so a failed release still matters") +} + // TestFlasherResetReportsFailedReset verifies Reset() doesn't claim // "Device reset." when the DTR/RTS writes never reached the device. func TestFlasherResetReportsFailedReset(t *testing.T) {