@@ -21,6 +21,9 @@ import { EventBus } from "../../components/eventBus";
2121import { ispConnected } from "../utils/connection.js" ;
2222import FC from "../fc" ;
2323
24+ const PORT_CHANGE_DEBOUNCE_MS = 500 ;
25+ const AUTO_DETECT_DELAY_MS = 1000 ;
26+
2427const firmware_flasher = {
2528 targets : null ,
2629 buildApi : new BuildApi ( ) ,
@@ -40,6 +43,71 @@ const firmware_flasher = {
4043 // Properties to preserve firmware state during flashing
4144 preFlashingMessage : null ,
4245 preFlashingMessageType : null ,
46+ // Minimal module-scoped handler references for cleanup to use
47+ // Module-level handler implementations so tests and early callers can
48+ // invoke them before `initialize()` binds the full DOM-aware versions.
49+ detectedUsbDevice : function ( device ) {
50+ // If a reboot-resume or flash-on-connect flow is active, we may need to
51+ // resume flashing. Otherwise USB attaches don't auto-detect a board.
52+ const isFlashOnConnect = ( typeof $ !== "undefined" && $ ( "input.flash_on_connect" ) . is ( " :checked" ) ) || false ;
53+ if ( STM32 . rebootMode || isFlashOnConnect ) {
54+ STM32 . rebootMode = 0 ;
55+ GUI . connect_lock = false ;
56+ if ( typeof firmware_flasher . startFlashing === "function" ) firmware_flasher . startFlashing ( ) ;
57+ }
58+ } ,
59+ detectedSerialDevice : function ( device ) {
60+ // Serial device connect should trigger auto-detect
61+ try {
62+ AutoDetect . verifyBoard ( ) ;
63+ } catch ( e ) {
64+ // tests may run in minimal env; swallow errors
65+ console . debug && console . debug ( "AutoDetect.verifyBoard invocation failed:" , e ) ;
66+ }
67+ } ,
68+ onPortChange : function ( port ) {
69+ // Debounce rapid port events
70+ if ( firmware_flasher . portChangeTimer ) {
71+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
72+ firmware_flasher . portChangeTimer = null ;
73+ }
74+
75+ if ( GUI . connect_lock ) return ;
76+
77+ if ( port && port !== "0" && ! ( STM32 && STM32 . rebootMode ) ) {
78+ firmware_flasher . portChangeTimer = setTimeout ( ( ) => {
79+ firmware_flasher . portChangeTimer = null ;
80+ if ( ! GUI . connect_lock ) {
81+ try {
82+ AutoDetect . verifyBoard ( ) ;
83+ } catch ( e ) {
84+ console . debug && console . debug ( "AutoDetect.verifyBoard error:" , e ) ;
85+ }
86+ }
87+ } , PORT_CHANGE_DEBOUNCE_MS ) ;
88+ } else if ( ! port || port === "0" ) {
89+ if ( ! GUI . connect_lock ) {
90+ try {
91+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
92+ } catch ( e ) { }
93+ }
94+ }
95+ } ,
96+ onDeviceRemoved : function ( devicePath ) {
97+ // Avoid clearing when removal is expected during flashing/reboot
98+ if ( GUI . connect_lock || ( STM32 && STM32 . rebootMode ) ) {
99+ return ;
100+ }
101+ try {
102+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
103+ } catch ( e ) { }
104+ try {
105+ // clearBufferedFirmware may be bound later; attempt best-effort
106+ if ( typeof firmware_flasher . clearBufferedFirmware === "function" ) firmware_flasher . clearBufferedFirmware ( ) ;
107+ } catch ( e ) { }
108+ } ,
109+ // Single debounce timer shared across instances
110+ portChangeTimer : null ,
43111} ;
44112
45113firmware_flasher . initialize = async function ( callback ) {
@@ -742,20 +810,86 @@ firmware_flasher.initialize = async function (callback) {
742810 return output . join ( "" ) . split ( "\n" ) ;
743811 }
744812
745- function detectedUsbDevice ( device ) {
813+ firmware_flasher . detectedUsbDevice = function ( device ) {
746814 const isFlashOnConnect = $ ( "input.flash_on_connect" ) . is ( ":checked" ) ;
747815
748816 console . log ( `${ self . logHead } Detected USB device:` , device ) ;
749817 console . log ( `${ self . logHead } Reboot mode: %s, flash on connect` , STM32 . rebootMode , isFlashOnConnect ) ;
750818
819+ // If another operation is in progress, ignore port events (unless we're resuming from a reboot)
820+ if ( GUI . connect_lock && ! STM32 . rebootMode ) {
821+ console . log ( `${ self . logHead } Port event ignored due to active operation (connect_lock)` ) ;
822+ return ;
823+ }
824+
825+ // Proceed if we're resuming a reboot sequence or if flash-on-connect is enabled and no operation is active
751826 if ( STM32 . rebootMode || isFlashOnConnect ) {
752827 STM32 . rebootMode = 0 ;
753828 GUI . connect_lock = false ;
754829 startFlashing ( ) ;
755830 }
756- }
831+ } ;
832+
833+ firmware_flasher . detectedSerialDevice = function ( device ) {
834+ AutoDetect . verifyBoard ( ) ;
835+ } ;
836+
837+ firmware_flasher . onPortChange = function ( port ) {
838+ // Clear any pending debounce timer so rapid port events don't re-enter the handler.
839+ if ( firmware_flasher . portChangeTimer ) {
840+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
841+ firmware_flasher . portChangeTimer = null ;
842+ }
757843
758- EventBus . $on ( "port-handler:auto-select-usb-device" , detectedUsbDevice ) ;
844+ console . log ( `${ self . logHead } Port changed to:` , port ) ;
845+
846+ if ( GUI . connect_lock ) {
847+ console . log ( `${ self . logHead } Port change ignored during active operation (connect_lock set)` ) ;
848+ return ;
849+ }
850+
851+ // Auto-detect board when port changes and we're on firmware flasher tab
852+ if ( port && port !== "0" && $ ( "input.flash_on_connect" ) . is ( ":checked" ) === false && ! STM32 . rebootMode ) {
853+ console . log ( `${ self . logHead } Auto-detecting board for port change (debounced)` ) ;
854+
855+ // Debounced verification: re-check connect lock when the timeout fires
856+ firmware_flasher . portChangeTimer = setTimeout ( ( ) => {
857+ firmware_flasher . portChangeTimer = null ;
858+ if ( GUI . connect_lock ) {
859+ console . log ( `${ self . logHead } Skipping auto-detect due to active operation at timeout` ) ;
860+ return ;
861+ }
862+ AutoDetect . verifyBoard ( ) ;
863+ } , PORT_CHANGE_DEBOUNCE_MS ) ; // Small delay to ensure port is ready
864+ } else if ( ! port || port === "0" ) {
865+ if ( ! GUI . connect_lock ) {
866+ console . log ( `${ self . logHead } Clearing board selection - no port selected` ) ;
867+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
868+ } else {
869+ console . log ( `${ self . logHead } Not clearing board selection because operation in progress` ) ;
870+ }
871+ }
872+ } ;
873+
874+ firmware_flasher . onDeviceRemoved = function ( devicePath ) {
875+ console . log ( `${ self . logHead } Device removed:` , devicePath ) ;
876+
877+ // If a flashing operation or reboot is in progress, the device may be
878+ // removed intentionally (it reboots into DFU). Don't clear selection or
879+ // buffered firmware in that case as it would prevent the flashing flow.
880+ if ( GUI . connect_lock || STM32 . rebootMode ) {
881+ console . log ( `${ self . logHead } Device removal during active operation/reboot; skipping clear` ) ;
882+ return ;
883+ }
884+
885+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
886+ clearBufferedFirmware ( ) ;
887+ } ;
888+
889+ EventBus . $on ( "port-handler:auto-select-usb-device" , firmware_flasher . detectedUsbDevice ) ;
890+ EventBus . $on ( "port-handler:auto-select-serial-device" , firmware_flasher . detectedSerialDevice ) ;
891+ EventBus . $on ( "ports-input:change" , firmware_flasher . onPortChange ) ;
892+ EventBus . $on ( "port-handler:device-removed" , firmware_flasher . onDeviceRemoved ) ;
759893
760894 async function saveFirmware ( ) {
761895 const fileType = self . firmware_type ;
@@ -1472,6 +1606,17 @@ firmware_flasher.initialize = async function (callback) {
14721606 $ ( "a.exit_dfu" ) . removeClass ( "disabled" ) ;
14731607 }
14741608
1609+ // Auto-detect board if drone is already connected when tab becomes active
1610+ if (
1611+ ( PortHandler . portAvailable && ! $ ( 'select[name="board"]' ) . val ( ) ) ||
1612+ $ ( 'select[name="board"]' ) . val ( ) === "0"
1613+ ) {
1614+ console . log ( `${ self . logHead } Auto-detecting board for already connected device` ) ;
1615+ setTimeout ( ( ) => {
1616+ AutoDetect . verifyBoard ( ) ;
1617+ } , AUTO_DETECT_DELAY_MS ) ; // Small delay to ensure tab is fully loaded
1618+ }
1619+
14751620 GUI . content_ready ( callback ) ;
14761621 }
14771622
@@ -1490,6 +1635,24 @@ firmware_flasher.cleanup = function (callback) {
14901635 $ ( document ) . unbind ( "keypress" ) ;
14911636 $ ( document ) . off ( "click" , "span.progressLabel a" ) ;
14921637
1638+ const cleanupHandler = ( evt , property ) => {
1639+ const handler = firmware_flasher [ property ] ;
1640+ if ( handler ) {
1641+ EventBus . $off ( evt , handler ) ;
1642+ }
1643+ } ;
1644+
1645+ cleanupHandler ( "port-handler:auto-select-usb-device" , "detectedUsbDevice" ) ;
1646+ cleanupHandler ( "port-handler:auto-select-serial-device" , "detectedSerialDevice" ) ;
1647+ cleanupHandler ( "ports-input:change" , "onPortChange" ) ;
1648+ cleanupHandler ( "port-handler:device-removed" , "onDeviceRemoved" ) ;
1649+
1650+ // Clear any pending debounce timer so it cannot fire after cleanup
1651+ if ( firmware_flasher . portChangeTimer ) {
1652+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
1653+ firmware_flasher . portChangeTimer = null ;
1654+ }
1655+
14931656 if ( callback ) callback ( ) ;
14941657} ;
14951658
0 commit comments