From 597bf16e6e6007ed6df9cf8e9e0b9b1c72c14790 Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Wed, 15 Oct 2025 01:56:47 +0200 Subject: [PATCH 1/5] Fix firmware flasher events --- src/js/tabs/firmware_flasher.js | 149 ++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 15 deletions(-) diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index c70565ea14..1b41e00175 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -21,6 +21,9 @@ import { EventBus } from "../../components/eventBus"; import { ispConnected } from "../utils/connection.js"; import FC from "../fc"; +const PORT_CHANGE_DEBOUNCE_MS = 500; +const AUTO_DETECT_DELAY_MS = 1000; + const firmware_flasher = { targets: null, buildApi: new BuildApi(), @@ -40,6 +43,105 @@ const firmware_flasher = { // Properties to preserve firmware state during flashing preFlashingMessage: null, preFlashingMessageType: null, + // Single debounce timer shared across instances + portChangeTimer: null, + logHead: "[FIRMWARE_FLASHER]", + // Event handlers to allow removal on tab change + detectedUsbDevice: function (device) { + const isFlashOnConnect = $("input.flash_on_connect").is(":checked"); + + console.log(`${firmware_flasher.logHead} Detected USB device:`, device); + console.log( + `${firmware_flasher.logHead} Reboot mode: %s, flash on connect`, + STM32.rebootMode, + isFlashOnConnect, + ); + + // If another operation is in progress, ignore port events (unless we're resuming from a reboot) + if (GUI.connect_lock && !STM32.rebootMode) { + console.log(`${firmware_flasher.logHead} Port event ignored due to active operation (connect_lock)`); + return; + } + + // Proceed if we're resuming a reboot sequence or if flash-on-connect is enabled and no operation is active + if (STM32.rebootMode || isFlashOnConnect) { + const wasReboot = !!STM32.rebootMode; + STM32.rebootMode = 0; + // Only clear the global connect lock when we are resuming from a reboot + // so we don't accidentally interrupt another active operation. + if (wasReboot) { + GUI.connect_lock = false; + } + firmware_flasher.startFlashing?.(); + } + }, + detectedSerialDevice: function (device) { + console.log(`${firmware_flasher.logHead} Detected serial device:`, device); + + if (GUI.connect_lock && !STM32.rebootMode) { + console.log( + `${firmware_flasher.logHead} Serial device event ignored due to active operation (connect_lock)`, + ); + return; + } + + try { + AutoDetect.verifyBoard(); + } catch (e) { + console.warn(`${firmware_flasher.logHead} AutoDetect.verifyBoard threw:`, e); + } + }, + onPortChange: function (port) { + // Clear any pending debounce timer so rapid port events don't re-enter the handler. + if (firmware_flasher.portChangeTimer) { + clearTimeout(firmware_flasher.portChangeTimer); + firmware_flasher.portChangeTimer = null; + } + + console.log(`${firmware_flasher.logHead} Port changed to:`, port); + + if (GUI.connect_lock) { + console.log(`${firmware_flasher.logHead} Port change ignored during active operation (connect_lock set)`); + return; + } + + // Auto-detect board when port changes and we're on firmware flasher tab + if (port && port !== "0" && $("input.flash_on_connect").is(":checked") === false && !STM32.rebootMode) { + console.log(`${firmware_flasher.logHead} Auto-detecting board for port change (debounced)`); + + // Debounced verification: re-check connect lock when the timeout fires + firmware_flasher.portChangeTimer = setTimeout(() => { + firmware_flasher.portChangeTimer = null; + if (GUI.connect_lock) { + console.log(`${firmware_flasher.logHead} Skipping auto-detect due to active operation at timeout`); + return; + } + try { + AutoDetect.verifyBoard(); + } catch (e) { + console.warn(`${firmware_flasher.logHead} AutoDetect.verifyBoard threw (debounced):`, e); + } + }, PORT_CHANGE_DEBOUNCE_MS); + } else if (!port || port === "0") { + if (!GUI.connect_lock) { + console.log(`${firmware_flasher.logHead} Clearing board selection - no port selected`); + $('select[name="board"]').val("0").trigger("change"); + } else { + console.log(`${firmware_flasher.logHead} Not clearing board selection because operation in progress`); + } + } + }, + onDeviceRemoved: function (devicePath) { + console.log(`${firmware_flasher.logHead} Device removed:`, devicePath); + + // Avoid clearing when removal is expected during flashing/reboot + if (GUI.connect_lock || STM32.rebootMode) { + return; + } + + $('select[name="board"]').val("0").trigger("change"); + firmware_flasher.clearBufferedFirmware?.(); + }, }; firmware_flasher.initialize = async function (callback) { @@ -60,8 +162,6 @@ firmware_flasher.initialize = async function (callback) { self.intel_hex = undefined; self.parsed_hex = undefined; - self.logHead = "[FIRMWARE_FLASHER]"; - function getExtension(key) { if (!key) { return undefined; @@ -742,20 +842,16 @@ firmware_flasher.initialize = async function (callback) { return output.join("").split("\n"); } - function detectedUsbDevice(device) { - const isFlashOnConnect = $("input.flash_on_connect").is(":checked"); - - console.log(`${self.logHead} Detected USB device:`, device); - console.log(`${self.logHead} Reboot mode: %s, flash on connect`, STM32.rebootMode, isFlashOnConnect); - - if (STM32.rebootMode || isFlashOnConnect) { - STM32.rebootMode = 0; - GUI.connect_lock = false; - startFlashing(); - } - } + // Expose the local startFlashing implementation to module callers/tests so + // module-scoped handlers can safely call firmware_flasher.startFlashing() + // even if those callers ran before initialize() completed. + firmware_flasher.startFlashing = startFlashing; + firmware_flasher.clearBufferedFirmware = clearBufferedFirmware; - EventBus.$on("port-handler:auto-select-usb-device", detectedUsbDevice); + EventBus.$on("port-handler:auto-select-usb-device", firmware_flasher.detectedUsbDevice); + EventBus.$on("port-handler:auto-select-serial-device", firmware_flasher.detectedSerialDevice); + EventBus.$on("ports-input:change", firmware_flasher.onPortChange); + EventBus.$on("port-handler:device-removed", firmware_flasher.onDeviceRemoved); async function saveFirmware() { const fileType = self.firmware_type; @@ -1472,6 +1568,11 @@ firmware_flasher.initialize = async function (callback) { $("a.exit_dfu").removeClass("disabled"); } + if (PortHandler.portAvailable) { + console.log(`${self.logHead} 💥 Auto-detecting board for already connected device`); + AutoDetect.verifyBoard(); + } + GUI.content_ready(callback); } @@ -1490,6 +1591,24 @@ firmware_flasher.cleanup = function (callback) { $(document).unbind("keypress"); $(document).off("click", "span.progressLabel a"); + const cleanupHandler = (evt, property) => { + const handler = firmware_flasher[property]; + if (handler) { + EventBus.$off(evt, handler); + } + }; + + cleanupHandler("port-handler:auto-select-usb-device", "detectedUsbDevice"); + cleanupHandler("port-handler:auto-select-serial-device", "detectedSerialDevice"); + cleanupHandler("ports-input:change", "onPortChange"); + cleanupHandler("port-handler:device-removed", "onDeviceRemoved"); + + // Clear any pending debounce timer so it cannot fire after cleanup + if (firmware_flasher.portChangeTimer) { + clearTimeout(firmware_flasher.portChangeTimer); + firmware_flasher.portChangeTimer = null; + } + if (callback) callback(); }; From 08af27fb2e3d9d60514a80551b817c6a1b15ac1e Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Wed, 15 Oct 2025 02:08:53 +0200 Subject: [PATCH 2/5] CR review --- src/js/tabs/firmware_flasher.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index 1b41e00175..b601cc7f5a 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -1568,10 +1568,12 @@ firmware_flasher.initialize = async function (callback) { $("a.exit_dfu").removeClass("disabled"); } - if (PortHandler.portAvailable) { - console.log(`${self.logHead} 💥 Auto-detecting board for already connected device`); - AutoDetect.verifyBoard(); - } + + const isFlashOnConnect = $("input.flash_on_connect").is(":checked"); + if (PortHandler.portAvailable && !isFlashOnConnect) { + console.log(`${self.logHead} 💥 Auto-detecting board for already connected device`); + AutoDetect.verifyBoard(); + } GUI.content_ready(callback); } From 7c27962e311fb50182a2510fe94050b7bc4e47bf Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Wed, 15 Oct 2025 02:18:49 +0200 Subject: [PATCH 3/5] CR review II --- src/js/tabs/firmware_flasher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index b601cc7f5a..810df6a3c3 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -22,7 +22,6 @@ import { ispConnected } from "../utils/connection.js"; import FC from "../fc"; const PORT_CHANGE_DEBOUNCE_MS = 500; -const AUTO_DETECT_DELAY_MS = 1000; const firmware_flasher = { targets: null, From 4704978bc5ccd6f19118c8593c2a2ad6048fcd9e Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Wed, 15 Oct 2025 02:24:48 +0200 Subject: [PATCH 4/5] CR review III --- src/js/tabs/firmware_flasher.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index 810df6a3c3..c46349119f 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -1567,12 +1567,11 @@ firmware_flasher.initialize = async function (callback) { $("a.exit_dfu").removeClass("disabled"); } - - const isFlashOnConnect = $("input.flash_on_connect").is(":checked"); - if (PortHandler.portAvailable && !isFlashOnConnect) { - console.log(`${self.logHead} 💥 Auto-detecting board for already connected device`); - AutoDetect.verifyBoard(); - } + const isFlashOnConnect = $("input.flash_on_connect").is(":checked"); + if (PortHandler.portAvailable && !isFlashOnConnect) { + console.log(`${self.logHead} 💥 Auto-detecting board for already connected device`); + AutoDetect.verifyBoard(); + } GUI.content_ready(callback); } From dd87ca10fe5ef981abc4dd0a0e58d41aabf9344c Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Wed, 15 Oct 2025 02:40:07 +0200 Subject: [PATCH 5/5] Reduce --- src/js/tabs/firmware_flasher.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index c46349119f..d81a86621d 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -77,13 +77,23 @@ const firmware_flasher = { detectedSerialDevice: function (device) { console.log(`${firmware_flasher.logHead} Detected serial device:`, device); - if (GUI.connect_lock && !STM32.rebootMode) { + // If another operation is in progress, ignore port events. + if (GUI.connect_lock) { console.log( `${firmware_flasher.logHead} Serial device event ignored due to active operation (connect_lock)`, ); return; } + const isFlashOnConnect = $("input.flash_on_connect").is(":checked"); + + // If flash-on-connect is enabled, trigger startFlashing (if available) instead + // of running AutoDetect.verifyBoard(), mirroring the onPortChange logic. + if (isFlashOnConnect) { + firmware_flasher.startFlashing?.(); + return; + } + try { AutoDetect.verifyBoard(); } catch (e) {