Skip to content

Commit 4764e63

Browse files
committed
Improve events
1 parent 8c10a46 commit 4764e63

File tree

6 files changed

+320
-64
lines changed

6 files changed

+320
-64
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} - crc failed`);
17891793
}
17901794
}
17911795
}

src/js/tabs/firmware_flasher.js

Lines changed: 130 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,94 @@ const firmware_flasher = {
4040
// Properties to preserve firmware state during flashing
4141
preFlashingMessage: null,
4242
preFlashingMessageType: null,
43+
// Minimal module-scoped handler references for cleanup to use
44+
detectedUsbDevice: null,
45+
detectedSerialDevice: null,
46+
onPortChange: null,
47+
onDeviceRemoved: null,
48+
// Single debounce timer shared across instances
49+
_portChangeTimer: null,
50+
};
51+
52+
// Move port/device handlers to module scope for easier maintenance and testing.
53+
// These handlers reference the `firmware_flasher` object and shared imports (STM32, AutoDetect, GUI).
54+
const PORT_CHANGE_DEBOUNCE_MS = 500;
55+
56+
function handleDetectedDevice(device, isSerial) {
57+
const isFlashOnConnect = $("input.flash_on_connect").is(":checked");
58+
59+
console.log(
60+
`${firmware_flasher.logHead || "[FIRMWARE_FLASHER]"} Detected ${isSerial ? "serial" : "USB"} device:`,
61+
device,
62+
);
63+
console.log(
64+
`${firmware_flasher.logHead || "[FIRMWARE_FLASHER]"} Reboot mode: %s, flash on connect`,
65+
STM32.rebootMode,
66+
isFlashOnConnect,
67+
);
68+
69+
if (STM32.rebootMode || isFlashOnConnect) {
70+
STM32.rebootMode = 0;
71+
GUI.connect_lock = false;
72+
firmware_flasher.startFlashing();
73+
} else {
74+
if (isSerial) {
75+
// Auto-detect board when firmware flasher tab is active and no flash-on-connect
76+
console.log(`${firmware_flasher.logHead} Auto-detecting board for connected serial device`);
77+
}
78+
AutoDetect.verifyBoard();
79+
}
80+
}
81+
82+
firmware_flasher.detectedUsbDevice = function (device) {
83+
handleDetectedDevice(device, false);
84+
};
85+
86+
firmware_flasher.detectedSerialDevice = function (device) {
87+
handleDetectedDevice(device, true);
88+
};
89+
90+
firmware_flasher.onPortChange = function (port) {
91+
// Clear any pending debounce timer so rapid port events don't re-enter the handler.
92+
if (firmware_flasher._portChangeTimer) {
93+
clearTimeout(firmware_flasher._portChangeTimer);
94+
firmware_flasher._portChangeTimer = null;
95+
}
96+
97+
console.log(`${firmware_flasher.logHead || "[FIRMWARE_FLASHER]"} Port changed to:`, port);
98+
99+
if (GUI.connect_lock) {
100+
console.log(`${firmware_flasher.logHead} Port change ignored during active operation (connect_lock set)`);
101+
return;
102+
}
103+
104+
// Auto-detect board when port changes and we're on firmware flasher tab
105+
if (port && port !== "0" && $("input.flash_on_connect").is(":checked") === false && !STM32.rebootMode) {
106+
console.log(`${firmware_flasher.logHead} Auto-detecting board for port change (debounced)`);
107+
108+
// Debounced verification: re-check connect lock when the timeout fires
109+
firmware_flasher._portChangeTimer = setTimeout(() => {
110+
firmware_flasher._portChangeTimer = null;
111+
if (GUI.connect_lock) {
112+
console.log(`${firmware_flasher.logHead} Skipping auto-detect due to active operation at timeout`);
113+
return;
114+
}
115+
AutoDetect.verifyBoard();
116+
}, PORT_CHANGE_DEBOUNCE_MS); // Small delay to ensure port is ready
117+
} else if (!port || port === "0") {
118+
if (!GUI.connect_lock) {
119+
console.log(`${firmware_flasher.logHead} Clearing board selection - no port selected`);
120+
$('select[name="board"]').val("0").trigger("change");
121+
} else {
122+
console.log(`${firmware_flasher.logHead} Not clearing board selection because operation in progress`);
123+
}
124+
}
125+
};
126+
127+
firmware_flasher.onDeviceRemoved = function (devicePath) {
128+
console.log(`${firmware_flasher.logHead || "[FIRMWARE_FLASHER]"} Device removed:`, devicePath);
129+
$('select[name="board"]').val("0").trigger("change");
130+
firmware_flasher.clearBufferedFirmware();
43131
};
44132

45133
firmware_flasher.initialize = async function (callback) {
@@ -742,20 +830,16 @@ firmware_flasher.initialize = async function (callback) {
742830
return output.join("").split("\n");
743831
}
744832

745-
function detectedUsbDevice(device) {
746-
const isFlashOnConnect = $("input.flash_on_connect").is(":checked");
747-
748-
console.log(`${self.logHead} Detected USB device:`, device);
749-
console.log(`${self.logHead} Reboot mode: %s, flash on connect`, STM32.rebootMode, isFlashOnConnect);
833+
// Wire module-scoped handlers (defined above) into the instance once DOM is ready.
834+
// Expose instance functions used by the module handlers.
835+
firmware_flasher.clearBufferedFirmware = clearBufferedFirmware;
750836

751-
if (STM32.rebootMode || isFlashOnConnect) {
752-
STM32.rebootMode = 0;
753-
GUI.connect_lock = false;
754-
startFlashing();
755-
}
756-
}
837+
// startFlashing implementation will be registered after its declaration below.
757838

758-
EventBus.$on("port-handler:auto-select-usb-device", detectedUsbDevice);
839+
EventBus.$on("port-handler:auto-select-usb-device", firmware_flasher.detectedUsbDevice);
840+
EventBus.$on("port-handler:auto-select-serial-device", firmware_flasher.detectedSerialDevice);
841+
EventBus.$on("ports-input:change", firmware_flasher.onPortChange);
842+
EventBus.$on("port-handler:device-removed", firmware_flasher.onDeviceRemoved);
759843

760844
async function saveFirmware() {
761845
const fileType = self.firmware_type;
@@ -1389,6 +1473,11 @@ firmware_flasher.initialize = async function (callback) {
13891473
}
13901474
}
13911475

1476+
// Register the implementation so the module-level entrypoint can call it.
1477+
// Assign the concrete implementation directly (bound to this instance)
1478+
// so callers can invoke `firmware_flasher.startFlashing()` without a trampoline.
1479+
firmware_flasher.startFlashing = startFlashing.bind(self);
1480+
13921481
$("a.flash_firmware").on("click", async function () {
13931482
if (GUI.connect_lock) {
13941483
return;
@@ -1472,6 +1561,17 @@ firmware_flasher.initialize = async function (callback) {
14721561
$("a.exit_dfu").removeClass("disabled");
14731562
}
14741563

1564+
// Auto-detect board if drone is already connected when tab becomes active
1565+
if (
1566+
(PortHandler.portAvailable && !$('select[name="board"]').val()) ||
1567+
$('select[name="board"]').val() === "0"
1568+
) {
1569+
console.log(`${self.logHead} Auto-detecting board for already connected device`);
1570+
setTimeout(() => {
1571+
AutoDetect.verifyBoard();
1572+
}, 1000); // Small delay to ensure tab is fully loaded
1573+
}
1574+
14751575
GUI.content_ready(callback);
14761576
}
14771577

@@ -1490,6 +1590,24 @@ firmware_flasher.cleanup = function (callback) {
14901590
$(document).unbind("keypress");
14911591
$(document).off("click", "span.progressLabel a");
14921592

1593+
const cleanupHandler = (evt, property) => {
1594+
const handler = firmware_flasher[property];
1595+
if (handler) {
1596+
EventBus.$off(evt, handler);
1597+
}
1598+
};
1599+
1600+
cleanupHandler("port-handler:auto-select-usb-device", "detectedUsbDevice");
1601+
cleanupHandler("port-handler:auto-select-serial-device", "detectedSerialDevice");
1602+
cleanupHandler("ports-input:change", "onPortChange");
1603+
cleanupHandler("port-handler:device-removed", "onDeviceRemoved");
1604+
1605+
// Clear any pending debounce timer so it cannot fire after cleanup
1606+
if (firmware_flasher._portChangeTimer) {
1607+
clearTimeout(firmware_flasher._portChangeTimer);
1608+
firmware_flasher._portChangeTimer = null;
1609+
}
1610+
14931611
if (callback) callback();
14941612
};
14951613

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

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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";
7+
import { appendStringToArray, generateRandomString } from "./msp_helpers";
78

89
describe("MspHelper", () => {
910
const mspHelper = new MspHelper();
@@ -35,6 +36,60 @@ describe("MspHelper", () => {
3536

3637
expect(callbackCalled).toEqual(true);
3738
});
39+
40+
it("passes crcError flag to callbacks when crcError is set", () => {
41+
let received = null;
42+
43+
mspHelper.process_data({
44+
code: MSPCodes.MSP_BOARD_INFO,
45+
dataView: new DataView(new Uint8Array([]).buffer),
46+
crcError: true,
47+
callbacks: [
48+
{
49+
callback: (item) => {
50+
received = item;
51+
},
52+
code: MSPCodes.MSP_BOARD_INFO,
53+
},
54+
],
55+
});
56+
57+
expect(received).not.toEqual(null);
58+
expect(received.crcError).toEqual(true);
59+
expect(received.command).toEqual(MSPCodes.MSP_BOARD_INFO);
60+
});
61+
62+
it("passes full data and length to callbacks when crcError is set", () => {
63+
let received = null;
64+
65+
const payload = new Uint8Array([1, 2, 3, 4]);
66+
const dv = new DataView(payload.buffer);
67+
68+
mspHelper.process_data({
69+
code: MSPCodes.MSP_BOARD_INFO,
70+
dataView: dv,
71+
crcError: true,
72+
callbacks: [
73+
{
74+
callback: (item) => {
75+
received = item;
76+
},
77+
code: MSPCodes.MSP_BOARD_INFO,
78+
},
79+
],
80+
});
81+
82+
expect(received).not.toEqual(null);
83+
expect(received.length).toEqual(dv.byteLength);
84+
expect(received.data instanceof DataView).toEqual(true);
85+
86+
const receivedBytes = new Uint8Array(
87+
received.data.buffer,
88+
received.data.byteOffset,
89+
received.data.byteLength,
90+
);
91+
expect(Array.from(receivedBytes)).toEqual(Array.from(payload));
92+
});
3893
it("handles MSP_API_VERSION correctly", () => {
3994
let randomValues = crypto.getRandomValues(new Uint8Array(3));
4095
const [mspProtocolVersion, apiVersionMajor, apiVersionMinor] = randomValues;
@@ -122,45 +177,4 @@ describe("MspHelper", () => {
122177
});
123178
});
124179

125-
/**
126-
* Appends given string to an array. If required, it will append length of the string by length.
127-
* @param destination array to which we append given string (and length if required)
128-
* @param source string to append to an array
129-
* @param prefixWithLength should we prefix the string by its length in the array
130-
* @returns {*} string that was requested to be inserted to the array
131-
*/
132-
function appendStringToArray(destination, source, prefixWithLength = false) {
133-
const size = source.length;
134-
135-
if (prefixWithLength) {
136-
destination.push8(source.length);
137-
}
138-
139-
for (let i = 0; i < size; i++) {
140-
destination.push8(source.charCodeAt(i));
141-
}
142-
143-
return source;
144-
}
145-
146-
/**
147-
* Generates a random string of required length. If required length is -1, it will generate a random string of a random length.
148-
* @param length required random string length. If lower than 0, it will generate a string of random length.
149-
* @returns {string} random string (composed of letters [A-Za-z0-9])
150-
*/
151-
function generateRandomString(length = -1) {
152-
let result = "";
153-
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
154-
const charactersLength = characters.length;
155-
156-
if (length < 0) {
157-
length = crypto.getRandomValues(new Uint8Array(1))[0];
158-
}
159-
160-
const signature = crypto.getRandomValues(new Uint8Array(length));
161-
for (let i = 0; i < length; i++) {
162-
result += characters.charAt(signature[i] % charactersLength);
163-
}
164-
165-
return result;
166-
}
180+
// Helpers are provided by test/js/helpers/msp_helpers.js
File renamed without changes.

0 commit comments

Comments
 (0)