@@ -21,6 +21,8 @@ import { EventBus } from "../../components/eventBus";
2121import { ispConnected } from "../utils/connection.js" ;
2222import FC from "../fc" ;
2323
24+ const PORT_CHANGE_DEBOUNCE_MS = 500 ;
25+
2426const firmware_flasher = {
2527 targets : null ,
2628 buildApi : new BuildApi ( ) ,
@@ -40,6 +42,115 @@ const firmware_flasher = {
4042 // Properties to preserve firmware state during flashing
4143 preFlashingMessage : null ,
4244 preFlashingMessageType : null ,
45+ // Single debounce timer shared across instances
46+ portChangeTimer : null ,
47+ logHead : "[FIRMWARE_FLASHER]" ,
48+ // Event handlers to allow removal on tab change
49+ detectedUsbDevice : function ( device ) {
50+ const isFlashOnConnect = $ ( "input.flash_on_connect" ) . is ( ":checked" ) ;
51+
52+ console . log ( `${ firmware_flasher . logHead } Detected USB device:` , device ) ;
53+ console . log (
54+ `${ firmware_flasher . logHead } Reboot mode: %s, flash on connect` ,
55+ STM32 . rebootMode ,
56+ isFlashOnConnect ,
57+ ) ;
58+
59+ // If another operation is in progress, ignore port events (unless we're resuming from a reboot)
60+ if ( GUI . connect_lock && ! STM32 . rebootMode ) {
61+ console . log ( `${ firmware_flasher . logHead } Port event ignored due to active operation (connect_lock)` ) ;
62+ return ;
63+ }
64+
65+ // Proceed if we're resuming a reboot sequence or if flash-on-connect is enabled and no operation is active
66+ if ( STM32 . rebootMode || isFlashOnConnect ) {
67+ const wasReboot = ! ! STM32 . rebootMode ;
68+ STM32 . rebootMode = 0 ;
69+ // Only clear the global connect lock when we are resuming from a reboot
70+ // so we don't accidentally interrupt another active operation.
71+ if ( wasReboot ) {
72+ GUI . connect_lock = false ;
73+ }
74+ firmware_flasher . startFlashing ?. ( ) ;
75+ }
76+ } ,
77+ detectedSerialDevice : function ( device ) {
78+ console . log ( `${ firmware_flasher . logHead } Detected serial device:` , device ) ;
79+
80+ // If another operation is in progress, ignore port events.
81+ if ( GUI . connect_lock ) {
82+ console . log (
83+ `${ firmware_flasher . logHead } Serial device event ignored due to active operation (connect_lock)` ,
84+ ) ;
85+ return ;
86+ }
87+
88+ const isFlashOnConnect = $ ( "input.flash_on_connect" ) . is ( ":checked" ) ;
89+
90+ // If flash-on-connect is enabled, trigger startFlashing (if available) instead
91+ // of running AutoDetect.verifyBoard(), mirroring the onPortChange logic.
92+ if ( isFlashOnConnect ) {
93+ firmware_flasher . startFlashing ?. ( ) ;
94+ return ;
95+ }
96+
97+ try {
98+ AutoDetect . verifyBoard ( ) ;
99+ } catch ( e ) {
100+ console . warn ( `${ firmware_flasher . logHead } AutoDetect.verifyBoard threw:` , e ) ;
101+ }
102+ } ,
103+ onPortChange : function ( port ) {
104+ // Clear any pending debounce timer so rapid port events don't re-enter the handler.
105+ if ( firmware_flasher . portChangeTimer ) {
106+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
107+ firmware_flasher . portChangeTimer = null ;
108+ }
109+
110+ console . log ( `${ firmware_flasher . logHead } Port changed to:` , port ) ;
111+
112+ if ( GUI . connect_lock ) {
113+ console . log ( `${ firmware_flasher . logHead } Port change ignored during active operation (connect_lock set)` ) ;
114+ return ;
115+ }
116+
117+ // Auto-detect board when port changes and we're on firmware flasher tab
118+ if ( port && port !== "0" && $ ( "input.flash_on_connect" ) . is ( ":checked" ) === false && ! STM32 . rebootMode ) {
119+ console . log ( `${ firmware_flasher . logHead } Auto-detecting board for port change (debounced)` ) ;
120+
121+ // Debounced verification: re-check connect lock when the timeout fires
122+ firmware_flasher . portChangeTimer = setTimeout ( ( ) => {
123+ firmware_flasher . portChangeTimer = null ;
124+ if ( GUI . connect_lock ) {
125+ console . log ( `${ firmware_flasher . logHead } Skipping auto-detect due to active operation at timeout` ) ;
126+ return ;
127+ }
128+ try {
129+ AutoDetect . verifyBoard ( ) ;
130+ } catch ( e ) {
131+ console . warn ( `${ firmware_flasher . logHead } AutoDetect.verifyBoard threw (debounced):` , e ) ;
132+ }
133+ } , PORT_CHANGE_DEBOUNCE_MS ) ;
134+ } else if ( ! port || port === "0" ) {
135+ if ( ! GUI . connect_lock ) {
136+ console . log ( `${ firmware_flasher . logHead } Clearing board selection - no port selected` ) ;
137+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
138+ } else {
139+ console . log ( `${ firmware_flasher . logHead } Not clearing board selection because operation in progress` ) ;
140+ }
141+ }
142+ } ,
143+ onDeviceRemoved : function ( devicePath ) {
144+ console . log ( `${ firmware_flasher . logHead } Device removed:` , devicePath ) ;
145+
146+ // Avoid clearing when removal is expected during flashing/reboot
147+ if ( GUI . connect_lock || STM32 . rebootMode ) {
148+ return ;
149+ }
150+
151+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
152+ firmware_flasher . clearBufferedFirmware ?. ( ) ;
153+ } ,
43154} ;
44155
45156firmware_flasher . initialize = async function ( callback ) {
@@ -60,8 +171,6 @@ firmware_flasher.initialize = async function (callback) {
60171 self . intel_hex = undefined ;
61172 self . parsed_hex = undefined ;
62173
63- self . logHead = "[FIRMWARE_FLASHER]" ;
64-
65174 function getExtension ( key ) {
66175 if ( ! key ) {
67176 return undefined ;
@@ -742,20 +851,16 @@ firmware_flasher.initialize = async function (callback) {
742851 return output . join ( "" ) . split ( "\n" ) ;
743852 }
744853
745- function detectedUsbDevice ( device ) {
746- const isFlashOnConnect = $ ( "input.flash_on_connect" ) . is ( ":checked" ) ;
854+ // Expose the local startFlashing implementation to module callers/tests so
855+ // module-scoped handlers can safely call firmware_flasher.startFlashing()
856+ // even if those callers ran before initialize() completed.
857+ firmware_flasher . startFlashing = startFlashing ;
858+ firmware_flasher . clearBufferedFirmware = clearBufferedFirmware ;
747859
748- console . log ( `${ self . logHead } Detected USB device:` , device ) ;
749- console . log ( `${ self . logHead } Reboot mode: %s, flash on connect` , STM32 . rebootMode , isFlashOnConnect ) ;
750-
751- if ( STM32 . rebootMode || isFlashOnConnect ) {
752- STM32 . rebootMode = 0 ;
753- GUI . connect_lock = false ;
754- startFlashing ( ) ;
755- }
756- }
757-
758- EventBus . $on ( "port-handler:auto-select-usb-device" , detectedUsbDevice ) ;
860+ EventBus . $on ( "port-handler:auto-select-usb-device" , firmware_flasher . detectedUsbDevice ) ;
861+ EventBus . $on ( "port-handler:auto-select-serial-device" , firmware_flasher . detectedSerialDevice ) ;
862+ EventBus . $on ( "ports-input:change" , firmware_flasher . onPortChange ) ;
863+ EventBus . $on ( "port-handler:device-removed" , firmware_flasher . onDeviceRemoved ) ;
759864
760865 async function saveFirmware ( ) {
761866 const fileType = self . firmware_type ;
@@ -1472,6 +1577,12 @@ firmware_flasher.initialize = async function (callback) {
14721577 $ ( "a.exit_dfu" ) . removeClass ( "disabled" ) ;
14731578 }
14741579
1580+ const isFlashOnConnect = $ ( "input.flash_on_connect" ) . is ( ":checked" ) ;
1581+ if ( PortHandler . portAvailable && ! isFlashOnConnect ) {
1582+ console . log ( `${ self . logHead } 💥 Auto-detecting board for already connected device` ) ;
1583+ AutoDetect . verifyBoard ( ) ;
1584+ }
1585+
14751586 GUI . content_ready ( callback ) ;
14761587 }
14771588
@@ -1490,6 +1601,24 @@ firmware_flasher.cleanup = function (callback) {
14901601 $ ( document ) . unbind ( "keypress" ) ;
14911602 $ ( document ) . off ( "click" , "span.progressLabel a" ) ;
14921603
1604+ const cleanupHandler = ( evt , property ) => {
1605+ const handler = firmware_flasher [ property ] ;
1606+ if ( handler ) {
1607+ EventBus . $off ( evt , handler ) ;
1608+ }
1609+ } ;
1610+
1611+ cleanupHandler ( "port-handler:auto-select-usb-device" , "detectedUsbDevice" ) ;
1612+ cleanupHandler ( "port-handler:auto-select-serial-device" , "detectedSerialDevice" ) ;
1613+ cleanupHandler ( "ports-input:change" , "onPortChange" ) ;
1614+ cleanupHandler ( "port-handler:device-removed" , "onDeviceRemoved" ) ;
1615+
1616+ // Clear any pending debounce timer so it cannot fire after cleanup
1617+ if ( firmware_flasher . portChangeTimer ) {
1618+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
1619+ firmware_flasher . portChangeTimer = null ;
1620+ }
1621+
14931622 if ( callback ) callback ( ) ;
14941623} ;
14951624
0 commit comments