fix(#248): VID-gate the USB DTR open pulse (don't wedge ESP32-C6)
The DTR low→high pulse in the desktop USB open path resets ESP32 USB-Serial/JTAG chips (VID 303A: C6/H2/S3-HWCDC) into ROM download mode mid-handshake, wedging the device until a physical reset. The pulse exists for nRF52 (VID 239A) reconnect-after-unclean-disconnect, so it can't just be removed. connect() now recovers the port's USB VID from the flserial enumeration (hardware_id) and gates the pulse: 239A keeps the DTR low→high pulse; 303A and any unknown/unparseable VID open with DTR asserted and no pulse. VID lookup is enumeration-only (never opens the port). New usb_vid helper parses both flserial hardware_id formats (Windows VID_XXXX and macOS/Linux VID:PID=xxxx:xxxx), unit tested. Build of epic #245.pull/255/head
parent
39274488c3
commit
ea4adb3faa
@ -0,0 +1,29 @@
|
||||
/// USB vendor-ID helpers for gating the desktop serial open sequence.
|
||||
///
|
||||
/// The DTR low→high pulse in the USB open path is a "fresh connection" signal
|
||||
/// nRF52 boards need to reconnect after an unclean disconnect. But on ESP32
|
||||
/// USB-Serial/JTAG chips (VID 303A: C6/H2/S3-HWCDC) the ROM maps DTR/RTS onto
|
||||
/// EN/BOOT, so the pulse resets the chip into ROM download mode mid-open and
|
||||
/// wedges it until a physical reset. We gate the pulse by vendor ID. (#244/#245)
|
||||
library;
|
||||
|
||||
/// Adafruit — nRF52 boards (RAK4631 etc.). Needs the DTR edge to reconnect.
|
||||
const int usbVidAdafruitNrf52 = 0x239A;
|
||||
|
||||
/// Espressif — ESP32 USB-Serial/JTAG. The DTR pulse resets it into ROM.
|
||||
const int usbVidEspressif = 0x303A;
|
||||
|
||||
final RegExp _vidWindows = RegExp(r'VID_([0-9A-Fa-f]{4})');
|
||||
final RegExp _vidUnix = RegExp(r'VID:PID=([0-9A-Fa-f]{4}):[0-9A-Fa-f]{4}');
|
||||
|
||||
/// Parses a USB vendor ID (0..0xFFFF) from a flserial `hardware_id` string, or
|
||||
/// null if none is present. Handles both formats flserial emits:
|
||||
/// - Windows (SetupAPI `SPDRP_HARDWAREID`): `USB\VID_303A&PID_1001&...`
|
||||
/// - macOS / Linux (pyserial-style): `USB VID:PID=303a:1001 SNR=...`
|
||||
int? parseUsbVid(String hardwareId) {
|
||||
final win = _vidWindows.firstMatch(hardwareId);
|
||||
if (win != null) return int.parse(win.group(1)!, radix: 16);
|
||||
final unix = _vidUnix.firstMatch(hardwareId);
|
||||
if (unix != null) return int.parse(unix.group(1)!, radix: 16);
|
||||
return null;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_open/utils/usb_vid.dart';
|
||||
|
||||
void main() {
|
||||
group('parseUsbVid', () {
|
||||
test('Windows SPDRP_HARDWAREID format', () {
|
||||
expect(
|
||||
parseUsbVid(r'USB\VID_303A&PID_1001&MI_00\8&25728F34&0&0000'),
|
||||
0x303A,
|
||||
);
|
||||
expect(
|
||||
parseUsbVid(r'USB\VID_239A&PID_8029&MI_00\8&6C3483E&0&0000'),
|
||||
0x239A,
|
||||
);
|
||||
});
|
||||
|
||||
test('macOS / Linux pyserial format', () {
|
||||
expect(parseUsbVid('USB VID:PID=303a:1001 SNR=1234'), 0x303A);
|
||||
expect(parseUsbVid('USB VID:PID=239a:8029 LOCATION=1-1'), 0x239A);
|
||||
});
|
||||
|
||||
test('hex is case-insensitive', () {
|
||||
expect(parseUsbVid(r'USB\VID_303a&PID_1001'), 0x303A);
|
||||
expect(parseUsbVid('USB VID:PID=239A:8029'), 0x239A);
|
||||
});
|
||||
|
||||
test('no VID present returns null', () {
|
||||
expect(parseUsbVid('n/a'), isNull);
|
||||
expect(parseUsbVid('USB Serial Device'), isNull);
|
||||
expect(parseUsbVid(''), isNull);
|
||||
expect(parseUsbVid('COM23'), isNull);
|
||||
});
|
||||
|
||||
test('known vendor-ID constants', () {
|
||||
expect(usbVidAdafruitNrf52, 0x239A);
|
||||
expect(usbVidEspressif, 0x303A);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in new issue