@@ -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,8 @@ const firmware_flasher = {
4043 // Properties to preserve firmware state during flashing
4144 preFlashingMessage : null ,
4245 preFlashingMessageType : null ,
46+ // Single debounce timer shared across instances
47+ portChangeTimer : null ,
4348} ;
4449
4550firmware_flasher . initialize = async function ( callback ) {
@@ -615,6 +620,10 @@ firmware_flasher.initialize = async function (callback) {
615620 self . filename = null ;
616621 }
617622
623+ // Expose the implementation to the module so module-scoped handlers that
624+ // may run before initialize() completes can call it safely.
625+ firmware_flasher . clearBufferedFirmware = clearBufferedFirmware ;
626+
618627 $ ( 'select[name="board"]' ) . select2 ( ) ;
619628 $ ( 'select[name="osdProtocols"]' ) . select2 ( ) ;
620629 $ ( 'select[name="radioProtocols"]' ) . select2 ( ) ;
@@ -742,20 +751,88 @@ firmware_flasher.initialize = async function (callback) {
742751 return output . join ( "" ) . split ( "\n" ) ;
743752 }
744753
745- function detectedUsbDevice ( device ) {
754+ firmware_flasher . detectedUsbDevice = function ( device ) {
746755 const isFlashOnConnect = $ ( "input.flash_on_connect" ) . is ( ":checked" ) ;
747756
748757 console . log ( `${ self . logHead } Detected USB device:` , device ) ;
749758 console . log ( `${ self . logHead } Reboot mode: %s, flash on connect` , STM32 . rebootMode , isFlashOnConnect ) ;
750759
760+ // If another operation is in progress, ignore port events (unless we're resuming from a reboot)
761+ if ( GUI . connect_lock && ! STM32 . rebootMode ) {
762+ console . log ( `${ self . logHead } Port event ignored due to active operation (connect_lock)` ) ;
763+ return ;
764+ }
765+
766+ // Proceed if we're resuming a reboot sequence or if flash-on-connect is enabled and no operation is active
751767 if ( STM32 . rebootMode || isFlashOnConnect ) {
768+ const wasReboot = ! ! STM32 . rebootMode ;
752769 STM32 . rebootMode = 0 ;
753- GUI . connect_lock = false ;
770+ // Only clear the global connect lock when we are resuming from a reboot
771+ // so we don't accidentally interrupt another active operation.
772+ if ( wasReboot ) {
773+ GUI . connect_lock = false ;
774+ }
754775 startFlashing ( ) ;
755776 }
756- }
777+ } ;
757778
758- EventBus . $on ( "port-handler:auto-select-usb-device" , detectedUsbDevice ) ;
779+ firmware_flasher . detectedSerialDevice = function ( ) {
780+ AutoDetect . verifyBoard ( ) ;
781+ } ;
782+
783+ firmware_flasher . onPortChange = function ( port ) {
784+ // Clear any pending debounce timer so rapid port events don't re-enter the handler.
785+ if ( firmware_flasher . portChangeTimer ) {
786+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
787+ firmware_flasher . portChangeTimer = null ;
788+ }
789+
790+ console . log ( `${ self . logHead } Port changed to:` , port ) ;
791+
792+ if ( GUI . connect_lock ) {
793+ console . log ( `${ self . logHead } Port change ignored during active operation (connect_lock set)` ) ;
794+ return ;
795+ }
796+
797+ // Auto-detect board when port changes and we're on firmware flasher tab
798+ if ( port && port !== "0" && $ ( "input.flash_on_connect" ) . is ( ":checked" ) === false && ! STM32 . rebootMode ) {
799+ console . log ( `${ self . logHead } Auto-detecting board for port change (debounced)` ) ;
800+
801+ // Debounced verification: re-check connect lock when the timeout fires
802+ firmware_flasher . portChangeTimer = setTimeout ( ( ) => {
803+ firmware_flasher . portChangeTimer = null ;
804+ if ( GUI . connect_lock ) {
805+ console . log ( `${ self . logHead } Skipping auto-detect due to active operation at timeout` ) ;
806+ return ;
807+ }
808+ AutoDetect . verifyBoard ( ) ;
809+ } , PORT_CHANGE_DEBOUNCE_MS ) ;
810+ } else if ( ! port || port === "0" ) {
811+ if ( ! GUI . connect_lock ) {
812+ console . log ( `${ self . logHead } Clearing board selection - no port selected` ) ;
813+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
814+ } else {
815+ console . log ( `${ self . logHead } Not clearing board selection because operation in progress` ) ;
816+ }
817+ }
818+ } ;
819+
820+ firmware_flasher . onDeviceRemoved = function ( devicePath ) {
821+ console . log ( `${ self . logHead } Device removed:` , devicePath ) ;
822+
823+ // Avoid clearing when removal is expected during flashing/reboot
824+ if ( GUI . connect_lock || STM32 . rebootMode ) {
825+ return ;
826+ }
827+
828+ $ ( 'select[name="board"]' ) . val ( "0" ) . trigger ( "change" ) ;
829+ clearBufferedFirmware ( ) ;
830+ } ;
831+
832+ EventBus . $on ( "port-handler:auto-select-usb-device" , firmware_flasher . detectedUsbDevice ) ;
833+ EventBus . $on ( "port-handler:auto-select-serial-device" , firmware_flasher . detectedSerialDevice ) ;
834+ EventBus . $on ( "ports-input:change" , firmware_flasher . onPortChange ) ;
835+ EventBus . $on ( "port-handler:device-removed" , firmware_flasher . onDeviceRemoved ) ;
759836
760837 async function saveFirmware ( ) {
761838 const fileType = self . firmware_type ;
@@ -1389,6 +1466,11 @@ firmware_flasher.initialize = async function (callback) {
13891466 }
13901467 }
13911468
1469+ // Expose the local startFlashing implementation to module callers/tests so
1470+ // module-scoped handlers can safely call firmware_flasher.startFlashing()
1471+ // even if those callers ran before initialize() completed.
1472+ firmware_flasher . startFlashing = startFlashing ;
1473+
13921474 $ ( "a.flash_firmware" ) . on ( "click" , async function ( ) {
13931475 if ( GUI . connect_lock ) {
13941476 return ;
@@ -1472,6 +1554,17 @@ firmware_flasher.initialize = async function (callback) {
14721554 $ ( "a.exit_dfu" ) . removeClass ( "disabled" ) ;
14731555 }
14741556
1557+ // Auto-detect board if drone is already connected when tab becomes active
1558+ if (
1559+ ( PortHandler . portAvailable && ! $ ( 'select[name="board"]' ) . val ( ) ) ||
1560+ $ ( 'select[name="board"]' ) . val ( ) === "0"
1561+ ) {
1562+ console . log ( `${ self . logHead } Auto-detecting board for already connected device` ) ;
1563+ setTimeout ( ( ) => {
1564+ AutoDetect . verifyBoard ( ) ;
1565+ } , AUTO_DETECT_DELAY_MS ) ; // Small delay to ensure tab is fully loaded
1566+ }
1567+
14751568 GUI . content_ready ( callback ) ;
14761569 }
14771570
@@ -1490,6 +1583,24 @@ firmware_flasher.cleanup = function (callback) {
14901583 $ ( document ) . unbind ( "keypress" ) ;
14911584 $ ( document ) . off ( "click" , "span.progressLabel a" ) ;
14921585
1586+ const cleanupHandler = ( evt , property ) => {
1587+ const handler = firmware_flasher [ property ] ;
1588+ if ( handler ) {
1589+ EventBus . $off ( evt , handler ) ;
1590+ }
1591+ } ;
1592+
1593+ cleanupHandler ( "port-handler:auto-select-usb-device" , "detectedUsbDevice" ) ;
1594+ cleanupHandler ( "port-handler:auto-select-serial-device" , "detectedSerialDevice" ) ;
1595+ cleanupHandler ( "ports-input:change" , "onPortChange" ) ;
1596+ cleanupHandler ( "port-handler:device-removed" , "onDeviceRemoved" ) ;
1597+
1598+ // Clear any pending debounce timer so it cannot fire after cleanup
1599+ if ( firmware_flasher . portChangeTimer ) {
1600+ clearTimeout ( firmware_flasher . portChangeTimer ) ;
1601+ firmware_flasher . portChangeTimer = null ;
1602+ }
1603+
14931604 if ( callback ) callback ( ) ;
14941605} ;
14951606
0 commit comments