1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-21 15:25:22 +03:00
This commit is contained in:
Vitroid 2025-07-16 17:23:48 +02:00 committed by GitHub
commit dec455a103
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 74 additions and 44 deletions

View file

@ -334,18 +334,26 @@ class WebSerial extends EventTarget {
// AT32 on macOS requires smaller chunks (63 bytes) to work correctly due to // AT32 on macOS requires smaller chunks (63 bytes) to work correctly due to
// USB buffer size limitations in the macOS implementation // USB buffer size limitations in the macOS implementation
const batchWriteSize = 63; const batchWriteSize = 63;
let remainingData = data;
while (remainingData.byteLength > batchWriteSize) { // Ensure data is a Uint8Array for proper slicing
const sliceData = remainingData.slice(0, batchWriteSize); const dataArray = data instanceof Uint8Array ? data : new Uint8Array(data);
remainingData = remainingData.slice(batchWriteSize);
let offset = 0;
while (offset + batchWriteSize < dataArray.byteLength) {
const chunk = dataArray.slice(offset, offset + batchWriteSize);
offset += batchWriteSize;
try { try {
await this.writer.write(sliceData); await this.writer.write(chunk);
} catch (error) { } catch (error) {
console.error(`${logHead} Error writing batch chunk:`, error); console.error(`${logHead} Error writing batch chunk:`, error);
throw error; // Re-throw to be caught by the send method throw error; // Re-throw to be caught by the send method
} }
} }
await this.writer.write(remainingData);
// Write the remaining data
if (offset < dataArray.byteLength) {
await this.writer.write(dataArray.slice(offset));
}
} }
async send(data, callback) { async send(data, callback) {
@ -358,14 +366,17 @@ class WebSerial extends EventTarget {
} }
try { try {
if (this.isNeedBatchWrite) { // Create a buffer from the data
await this.batchWrite(data); const buffer = data instanceof ArrayBuffer ? data : new Uint8Array(data).buffer;
} else {
await this.writer.write(data);
}
this.bytesSent += data.byteLength;
const result = { bytesSent: data.byteLength }; if (this.isNeedBatchWrite) {
await this.batchWrite(buffer);
} else {
await this.writer.write(new Uint8Array(buffer));
}
this.bytesSent += buffer.byteLength;
const result = { bytesSent: buffer.byteLength };
if (callback) { if (callback) {
callback(result); callback(result);
} }

View file

@ -14,6 +14,7 @@ import $ from "jquery";
import { ispConnected } from "../utils/connection"; import { ispConnected } from "../utils/connection";
import { sensorTypes } from "../sensor_types"; import { sensorTypes } from "../sensor_types";
import { addArrayElementsAfter, replaceArrayElement } from "../utils/array"; import { addArrayElementsAfter, replaceArrayElement } from "../utils/array";
import { isFirefoxBrowser } from "../utils/checkBrowserCompatibility";
const setup = { const setup = {
yaw_fix: 0.0, yaw_fix: 0.0,
@ -387,17 +388,19 @@ setup.initialize = function (callback) {
const buildConfig = `<span class="buildInfoBtn" title="${i18n.getMessage( const buildConfig = `<span class="buildInfoBtn" title="${i18n.getMessage(
"initialSetupInfoBuildConfig", "initialSetupInfoBuildConfig",
)}: ${buildRoot}/json"> )}: ${buildRoot}/json">
<a href="${buildRoot}/json" target="_blank"><strong>${i18n.getMessage( <a href="${buildRoot}/json" target="_blank">
"initialSetupInfoBuildConfig", <strong>${i18n.getMessage("initialSetupInfoBuildConfig")}</strong>
)}</strong></a></span>`; </a>
</span>`;
// Creates the "Log" button // Creates the "Log" button
const buildLog = `<span class="buildInfoBtn" title="${i18n.getMessage( const buildLog = `<span class="buildInfoBtn" title="${i18n.getMessage(
"initialSetupInfoBuildLog", "initialSetupInfoBuildLog",
)}: ${buildRoot}/log"> )}: ${buildRoot}/log">
<a href="${buildRoot}/log" target="_blank"><strong>${i18n.getMessage( <a href="${buildRoot}/log" target="_blank">
"initialSetupInfoBuildLog", <strong>${i18n.getMessage("initialSetupInfoBuildLog")}</strong>
)}</strong></a></span>`; </a>
</span>`;
// Shows the "Config" and "Log" buttons // Shows the "Config" and "Log" buttons
build_info_e.html(`${buildConfig} ${buildLog}`); build_info_e.html(`${buildConfig} ${buildLog}`);
@ -429,19 +432,19 @@ setup.initialize = function (callback) {
// Creates the "Options" button (if possible) // Creates the "Options" button (if possible)
const buildOptions = buildOptionsValid const buildOptions = buildOptionsValid
? `<span class="buildInfoBtn" title="${i18n.getMessage("initialSetupInfoBuildOptionList")}"> ? `<span class="buildInfoBtn" title="${i18n.getMessage("initialSetupInfoBuildOptionList")}">
<a class="buildOptions" href="#"><strong>${i18n.getMessage( <a class="buildOptions" href="#">
"initialSetupInfoBuildOptions", <strong>${i18n.getMessage("initialSetupInfoBuildOptions")}</strong>
)}</strong></a></span>` </a>
</span>`
: ""; : "";
// Creates the "Download" button (if possible) // Creates the "Download" button (if possible)
const buildDownload = buildKeyValid const buildDownload = buildKeyValid
? `<span class="buildInfoBtn" title="${i18n.getMessage( ? `<span class="buildInfoBtn" title="${i18n.getMessage("initialSetupInfoBuildDownload")}: ${buildRoot}/hex">
"initialSetupInfoBuildDownload", <a href="${buildRoot}/hex" target="_blank">
)}: ${buildRoot}/hex"> <strong>${i18n.getMessage("initialSetupInfoBuildDownload")}</strong>
<a href="${buildRoot}/hex" target="_blank"><strong>${i18n.getMessage( </a>
"initialSetupInfoBuildDownload", </span>`
)}</strong></a></span>`
: ""; : "";
// Shows the "Options" and/or "Download" buttons // Shows the "Options" and/or "Download" buttons
@ -493,13 +496,14 @@ setup.initialize = function (callback) {
} }
function showNetworkStatus() { function showNetworkStatus() {
const isFirefox = isFirefoxBrowser();
const networkStatus = ispConnected(); const networkStatus = ispConnected();
let statusText = ""; let statusText = "";
const type = navigator.connection.effectiveType; const type = isFirefox ? "NA" : navigator.connection.effectiveType;
const downlink = navigator.connection.downlink; const downlink = isFirefox ? "NA" : navigator.connection.downlink;
const rtt = navigator.connection.rtt; const rtt = isFirefox ? "NA" : navigator.connection.rtt;
if (!networkStatus || !navigator.onLine || type === "none") { if (!networkStatus || !navigator.onLine || type === "none") {
statusText = i18n.getMessage("initialSetupNetworkInfoStatusOffline"); statusText = i18n.getMessage("initialSetupNetworkInfoStatusOffline");
@ -510,9 +514,9 @@ setup.initialize = function (callback) {
} }
$(".network-status").text(statusText); $(".network-status").text(statusText);
$(".network-type").text(navigator.connection.effectiveType); $(".network-type").text(type);
$(".network-downlink").text(`${navigator.connection.downlink} Mbps`); $(".network-downlink").text(`${downlink} Mbps`);
$(".network-rtt").text(navigator.connection.rtt); $(".network-rtt").text(rtt);
} }
prepareDisarmFlags(); prepareDisarmFlags();

View file

@ -28,15 +28,26 @@ export function getOS() {
} }
export function isChromiumBrowser() { export function isChromiumBrowser() {
if (navigator.userAgentData) { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
return navigator.userAgentData.brands.some((brand) => { if (!navigator.userAgentData) {
return brand.brand == "Chromium"; // Fallback to traditional userAgent string check
}); return /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
} }
// Fallback for older browsers/Android // https://learn.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance
const ua = navigator.userAgent.toLowerCase(); return navigator.userAgentData.brands.some((brand) => {
return ua.includes("chrom") || ua.includes("edg"); return brand.brand == "Chromium";
});
}
export function isFirefoxBrowser() {
if (navigator.userAgentData) {
return navigator.userAgentData.brands.some((brand) => {
return brand.brand == "Firefox";
});
}
// Fallback to traditional userAgent string check
return navigator.userAgent.includes("Firefox");
} }
export function isAndroid() { export function isAndroid() {
@ -65,15 +76,18 @@ export function checkBrowserCompatibility() {
const isWebBluetooth = checkWebBluetoothSupport(); const isWebBluetooth = checkWebBluetoothSupport();
const isWebUSB = checkWebUSBSupport(); const isWebUSB = checkWebUSBSupport();
const isChromium = isChromiumBrowser(); const isChromium = isChromiumBrowser();
const isFirefox = isFirefoxBrowser();
const isNative = Capacitor.isNativePlatform(); const isNative = Capacitor.isNativePlatform();
const compatible = isNative || (isChromium && (isWebSerial || isWebBluetooth || isWebUSB)); const compatible = isNative || ((isChromium || isFirefox) && (isWebSerial || isWebBluetooth || isWebUSB));
console.log("User Agent: ", navigator.userAgentData); console.log("User Agent: ", navigator.userAgentData);
console.log("Native: ", isNative); console.log("Native: ", isNative);
console.log("Chromium: ", isChromium); console.log("Chromium: ", isChromium);
console.log("Firefox: ", isFirefox);
console.log("Web Serial: ", isWebSerial); console.log("Web Serial: ", isWebSerial);
console.log("Web Bluetooth: ", isWebBluetooth);
console.log("Web USB: ", isWebUSB);
console.log("OS: ", getOS()); console.log("OS: ", getOS());
console.log("Android: ", isAndroid()); console.log("Android: ", isAndroid());
@ -86,7 +100,8 @@ export function checkBrowserCompatibility() {
let errorMessage = ""; let errorMessage = "";
if (!isChromium) { if (!isChromium) {
errorMessage = "Betaflight app requires a Chromium based browser (Chrome, Chromium, Edge).<br/>"; errorMessage =
"Betaflight app requires a Chromium based browser (Chrome, Chromium, Edge),<br> or Firefox based browser running the <a href='https://addons.mozilla.org/firefox/addon/webserial-for-firefox/'>WebSerial extension</a>.<br/>";
} }
if (!isWebBluetooth) { if (!isWebBluetooth) {