Skip to content

Commit 205ec78

Browse files
committed
Improve events
1 parent 8c10a46 commit 205ec78

File tree

5 files changed

+328
-14
lines changed

5 files changed

+328
-14
lines changed

src/js/msp/MSPHelper.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,13 +1779,17 @@ MspHelper.prototype.process_data = function (dataHandler) {
17791779

17801780
// remove object from array
17811781
dataHandler.callbacks.splice(i, 1);
1782-
if (!crcError) {
1783-
// fire callback
1784-
if (callback) {
1782+
// Always invoke callback and pass crcError flag. Previously callbacks were skipped
1783+
// when crcError was true; tests and callers expect the callback to still be notified.
1784+
if (callback) {
1785+
try {
17851786
callback({ command: code, data: data, length: data.byteLength, crcError: crcError });
1787+
} catch (e) {
1788+
console.error(`callback for code ${code} threw:`, e);
17861789
}
1787-
} else {
1788-
console.warn(`code: ${code} - crc failed. No callback`);
1790+
}
1791+
if (crcError) {
1792+
console.warn(`code: ${code} - callback invoked despite crc failure`);
17891793
}
17901794
}
17911795
}

src/js/tabs/firmware_flasher.js

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import { EventBus } from "../../components/eventBus";
2121
import { ispConnected } from "../utils/connection.js";
2222
import FC from "../fc";
2323

24+
const PORT_CHANGE_DEBOUNCE_MS = 500;
25+
const AUTO_DETECT_DELAY_MS = 1000;
26+
2427
const firmware_flasher = {
2528
targets: null,
2629
buildApi: new BuildApi(),
@@ -40,6 +43,55 @@ 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 () {
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 = $("input.flash_on_connect").is(":checked") || false;
53+
if (STM32.rebootMode || isFlashOnConnect) {
54+
STM32.rebootMode = 0;
55+
GUI.connect_lock = false;
56+
firmware_flasher.startFlashing();
57+
}
58+
},
59+
detectedSerialDevice: function () {
60+
// Serial device connect should trigger auto-detect
61+
AutoDetect.verifyBoard();
62+
},
63+
onPortChange: function (port) {
64+
// Debounce rapid port events
65+
if (firmware_flasher.portChangeTimer) {
66+
clearTimeout(firmware_flasher.portChangeTimer);
67+
firmware_flasher.portChangeTimer = null;
68+
}
69+
70+
if (GUI.connect_lock) return;
71+
72+
if (port && port !== "0" && !(STM32 && STM32.rebootMode)) {
73+
firmware_flasher.portChangeTimer = setTimeout(() => {
74+
firmware_flasher.portChangeTimer = null;
75+
if (!GUI.connect_lock) {
76+
AutoDetect.verifyBoard();
77+
}
78+
}, PORT_CHANGE_DEBOUNCE_MS);
79+
} else if (!port || port === "0") {
80+
if (!GUI.connect_lock) {
81+
$('select[name="board"]').val("0").trigger("change");
82+
}
83+
}
84+
},
85+
onDeviceRemoved: function () {
86+
// Avoid clearing when removal is expected during flashing/reboot
87+
if (GUI.connect_lock || STM32.rebootMode) {
88+
return;
89+
}
90+
$('select[name="board"]').val("0").trigger("change");
91+
firmware_flasher.clearBufferedFirmware();
92+
},
93+
// Single debounce timer shared across instances
94+
portChangeTimer: null,
4395
};
4496

4597
firmware_flasher.initialize = async function (callback) {
@@ -742,20 +794,91 @@ firmware_flasher.initialize = async function (callback) {
742794
return output.join("").split("\n");
743795
}
744796

745-
function detectedUsbDevice(device) {
797+
firmware_flasher.detectedUsbDevice = function (device) {
746798
const isFlashOnConnect = $("input.flash_on_connect").is(":checked");
747799

748800
console.log(`${self.logHead} Detected USB device:`, device);
749801
console.log(`${self.logHead} Reboot mode: %s, flash on connect`, STM32.rebootMode, isFlashOnConnect);
750802

803+
// If another operation is in progress, ignore port events (unless we're resuming from a reboot)
804+
if (GUI.connect_lock && !STM32.rebootMode) {
805+
console.log(`${self.logHead} Port event ignored due to active operation (connect_lock)`);
806+
return;
807+
}
808+
809+
// Proceed if we're resuming a reboot sequence or if flash-on-connect is enabled and no operation is active
751810
if (STM32.rebootMode || isFlashOnConnect) {
811+
const wasReboot = !!STM32.rebootMode;
752812
STM32.rebootMode = 0;
753-
GUI.connect_lock = false;
813+
// Only clear the global connect lock when we are resuming from a reboot
814+
// so we don't accidentally interrupt another active operation.
815+
if (wasReboot) {
816+
GUI.connect_lock = false;
817+
}
754818
startFlashing();
755819
}
756-
}
820+
};
821+
822+
firmware_flasher.detectedSerialDevice = function () {
823+
AutoDetect.verifyBoard();
824+
};
757825

758-
EventBus.$on("port-handler:auto-select-usb-device", detectedUsbDevice);
826+
firmware_flasher.onPortChange = function (port) {
827+
// Clear any pending debounce timer so rapid port events don't re-enter the handler.
828+
if (firmware_flasher.portChangeTimer) {
829+
clearTimeout(firmware_flasher.portChangeTimer);
830+
firmware_flasher.portChangeTimer = null;
831+
}
832+
833+
console.log(`${self.logHead} Port changed to:`, port);
834+
835+
if (GUI.connect_lock) {
836+
console.log(`${self.logHead} Port change ignored during active operation (connect_lock set)`);
837+
return;
838+
}
839+
840+
// Auto-detect board when port changes and we're on firmware flasher tab
841+
if (port && port !== "0" && $("input.flash_on_connect").is(":checked") === false && !STM32.rebootMode) {
842+
console.log(`${self.logHead} Auto-detecting board for port change (debounced)`);
843+
844+
// Debounced verification: re-check connect lock when the timeout fires
845+
firmware_flasher.portChangeTimer = setTimeout(() => {
846+
firmware_flasher.portChangeTimer = null;
847+
if (GUI.connect_lock) {
848+
console.log(`${self.logHead} Skipping auto-detect due to active operation at timeout`);
849+
return;
850+
}
851+
AutoDetect.verifyBoard();
852+
}, PORT_CHANGE_DEBOUNCE_MS); // Small delay to ensure port is ready
853+
} else if (!port || port === "0") {
854+
if (!GUI.connect_lock) {
855+
console.log(`${self.logHead} Clearing board selection - no port selected`);
856+
$('select[name="board"]').val("0").trigger("change");
857+
} else {
858+
console.log(`${self.logHead} Not clearing board selection because operation in progress`);
859+
}
860+
}
861+
};
862+
863+
firmware_flasher.onDeviceRemoved = function (devicePath) {
864+
console.log(`${self.logHead} Device removed:`, devicePath);
865+
866+
// If a flashing operation or reboot is in progress, the device may be
867+
// removed intentionally (it reboots into DFU). Don't clear selection or
868+
// buffered firmware in that case as it would prevent the flashing flow.
869+
if (GUI.connect_lock || STM32.rebootMode) {
870+
console.log(`${self.logHead} Device removal during active operation/reboot; skipping clear`);
871+
return;
872+
}
873+
874+
$('select[name="board"]').val("0").trigger("change");
875+
clearBufferedFirmware();
876+
};
877+
878+
EventBus.$on("port-handler:auto-select-usb-device", firmware_flasher.detectedUsbDevice);
879+
EventBus.$on("port-handler:auto-select-serial-device", firmware_flasher.detectedSerialDevice);
880+
EventBus.$on("ports-input:change", firmware_flasher.onPortChange);
881+
EventBus.$on("port-handler:device-removed", firmware_flasher.onDeviceRemoved);
759882

760883
async function saveFirmware() {
761884
const fileType = self.firmware_type;
@@ -1389,6 +1512,11 @@ firmware_flasher.initialize = async function (callback) {
13891512
}
13901513
}
13911514

1515+
// Expose the local startFlashing implementation to module callers/tests so
1516+
// module-scoped handlers can safely call firmware_flasher.startFlashing()
1517+
// even if those callers ran before initialize() completed.
1518+
firmware_flasher.startFlashing = startFlashing;
1519+
13921520
$("a.flash_firmware").on("click", async function () {
13931521
if (GUI.connect_lock) {
13941522
return;
@@ -1472,6 +1600,17 @@ firmware_flasher.initialize = async function (callback) {
14721600
$("a.exit_dfu").removeClass("disabled");
14731601
}
14741602

1603+
// Auto-detect board if drone is already connected when tab becomes active
1604+
if (
1605+
(PortHandler.portAvailable && !$('select[name="board"]').val()) ||
1606+
$('select[name="board"]').val() === "0"
1607+
) {
1608+
console.log(`${self.logHead} Auto-detecting board for already connected device`);
1609+
setTimeout(() => {
1610+
AutoDetect.verifyBoard();
1611+
}, AUTO_DETECT_DELAY_MS); // Small delay to ensure tab is fully loaded
1612+
}
1613+
14751614
GUI.content_ready(callback);
14761615
}
14771616

@@ -1490,6 +1629,24 @@ firmware_flasher.cleanup = function (callback) {
14901629
$(document).unbind("keypress");
14911630
$(document).off("click", "span.progressLabel a");
14921631

1632+
const cleanupHandler = (evt, property) => {
1633+
const handler = firmware_flasher[property];
1634+
if (handler) {
1635+
EventBus.$off(evt, handler);
1636+
}
1637+
};
1638+
1639+
cleanupHandler("port-handler:auto-select-usb-device", "detectedUsbDevice");
1640+
cleanupHandler("port-handler:auto-select-serial-device", "detectedSerialDevice");
1641+
cleanupHandler("ports-input:change", "onPortChange");
1642+
cleanupHandler("port-handler:device-removed", "onDeviceRemoved");
1643+
1644+
// Clear any pending debounce timer so it cannot fire after cleanup
1645+
if (firmware_flasher.portChangeTimer) {
1646+
clearTimeout(firmware_flasher.portChangeTimer);
1647+
firmware_flasher.portChangeTimer = null;
1648+
}
1649+
14931650
if (callback) callback();
14941651
};
14951652

test/js/msp/MSPHelper.test.js renamed to test/js/MSPHelper.test.js

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { beforeEach, describe, expect, it } from "vitest";
2-
import MspHelper from "../../../src/js/msp/MSPHelper";
3-
import MSPCodes from "../../../src/js/msp/MSPCodes";
4-
import "../../../src/js/injected_methods";
5-
import FC from "../../../src/js/fc";
6-
import { API_VERSION_1_47 } from "../../../src/js/data_storage";
2+
import MspHelper from "../../src/js/msp/MSPHelper";
3+
import MSPCodes from "../../src/js/msp/MSPCodes";
4+
import "../../src/js/injected_methods";
5+
import FC from "../../src/js/fc";
6+
import { API_VERSION_1_47 } from "../../src/js/data_storage";
77

88
describe("MspHelper", () => {
99
const mspHelper = new MspHelper();
@@ -35,6 +35,61 @@ describe("MspHelper", () => {
3535

3636
expect(callbackCalled).toEqual(true);
3737
});
38+
39+
it("passes crcError flag to callbacks when crcError is set", () => {
40+
let received = null;
41+
42+
mspHelper.process_data({
43+
code: MSPCodes.MSP_BOARD_INFO,
44+
dataView: new DataView(new Uint8Array([]).buffer),
45+
crcError: true,
46+
callbacks: [
47+
{
48+
callback: (item) => {
49+
received = item;
50+
},
51+
code: MSPCodes.MSP_BOARD_INFO,
52+
},
53+
],
54+
});
55+
56+
expect(received).not.toEqual(null);
57+
expect(received.crcError).toEqual(true);
58+
expect(received.command).toEqual(MSPCodes.MSP_BOARD_INFO);
59+
});
60+
61+
it("passes full data and length to callbacks when crcError is set", () => {
62+
let received = null;
63+
64+
const payload = new Uint8Array([1, 2, 3, 4]);
65+
const dv = new DataView(payload.buffer);
66+
67+
mspHelper.process_data({
68+
code: MSPCodes.MSP_BOARD_INFO,
69+
dataView: dv,
70+
crcError: true,
71+
callbacks: [
72+
{
73+
callback: (item) => {
74+
received = item;
75+
},
76+
code: MSPCodes.MSP_BOARD_INFO,
77+
},
78+
],
79+
});
80+
81+
expect(received).not.toEqual(null);
82+
expect(received.length).toEqual(dv.byteLength);
83+
expect(received.data instanceof DataView).toEqual(true);
84+
85+
const receivedBytes = new Uint8Array(
86+
received.data.buffer,
87+
received.data.byteOffset,
88+
received.data.byteLength,
89+
);
90+
expect(Array.from(receivedBytes)).toEqual(Array.from(payload));
91+
});
92+
3893
it("handles MSP_API_VERSION correctly", () => {
3994
let randomValues = crypto.getRandomValues(new Uint8Array(3));
4095
const [mspProtocolVersion, apiVersionMajor, apiVersionMinor] = randomValues;
@@ -48,6 +103,7 @@ describe("MspHelper", () => {
48103
expect(FC.CONFIG.mspProtocolVersion).toEqual(mspProtocolVersion);
49104
expect(FC.CONFIG.apiVersion).toEqual(`${apiVersionMajor}.${apiVersionMinor}.0`);
50105
});
106+
51107
it("handles MSP_PIDNAMES correctly", () => {
52108
let pidNamesCount = 1 + crypto.getRandomValues(new Uint8Array(1))[0];
53109
let expectedNames = Array.from({ length: pidNamesCount }).map((_) => generateRandomString());
@@ -64,6 +120,7 @@ describe("MspHelper", () => {
64120

65121
expect(FC.PID_NAMES).toEqual(expectedNames);
66122
});
123+
67124
it("handles MSP_MOTOR correctly", () => {
68125
let motorCount = crypto.getRandomValues(new Uint8Array(1))[0] % 8;
69126
let motorBytes = crypto.getRandomValues(new Uint16Array(motorCount));
@@ -77,6 +134,7 @@ describe("MspHelper", () => {
77134
expect(new Uint16Array(FC.MOTOR_DATA).slice(0, motorCount)).toEqual(motorBytes);
78135
expect(FC.MOTOR_DATA.slice(motorCount, 8)).toContain(undefined);
79136
});
137+
80138
it("handles MSP_BOARD_INFO correctly for API version", () => {
81139
FC.CONFIG.apiVersion = API_VERSION_1_47;
82140
let infoBuffer = [];
File renamed without changes.

0 commit comments

Comments
 (0)