mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-21 15:25:22 +03:00
Merge bbf4a01723
into be45ddf05e
This commit is contained in:
commit
dec455a103
3 changed files with 74 additions and 44 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue