Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 144 additions & 15 deletions src/js/tabs/firmware_flasher.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { EventBus } from "../../components/eventBus";
import { ispConnected } from "../utils/connection.js";
import FC from "../fc";

const PORT_CHANGE_DEBOUNCE_MS = 500;

const firmware_flasher = {
targets: null,
buildApi: new BuildApi(),
Expand All @@ -40,6 +42,115 @@ 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 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) {
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) {
Expand All @@ -60,8 +171,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;
Expand Down Expand Up @@ -742,20 +851,16 @@ firmware_flasher.initialize = async function (callback) {
return output.join("").split("\n");
}

function detectedUsbDevice(device) {
const isFlashOnConnect = $("input.flash_on_connect").is(":checked");
// 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;

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();
}
}

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;
Expand Down Expand Up @@ -1472,6 +1577,12 @@ 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();
}

GUI.content_ready(callback);
}

Expand All @@ -1490,6 +1601,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();
};

Expand Down