mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-23 08:15:22 +03:00
* Add serial facade * Fix websocket connection * Refactor * Fix unplug * Fix reboot / unplug reconnect issue * The real deal (detail has no value)
830 lines
28 KiB
JavaScript
830 lines
28 KiB
JavaScript
import GUI, { TABS } from "./gui";
|
|
import { i18n } from "./localization";
|
|
// NOTE: this is a circular dependency, needs investigating
|
|
import MspHelper from "./msp/MSPHelper";
|
|
import Features from "./Features";
|
|
import VirtualFC from "./VirtualFC";
|
|
import Beepers from "./Beepers";
|
|
import FC from "./fc";
|
|
import MSP from "./msp";
|
|
import MSPCodes from "./msp/MSPCodes";
|
|
import PortUsage from "./port_usage";
|
|
import PortHandler from "./port_handler";
|
|
import CONFIGURATOR, { API_VERSION_1_45, API_VERSION_1_46, API_VERSION_1_47 } from "./data_storage";
|
|
import { bit_check } from "./bit.js";
|
|
import { sensor_status, have_sensor } from "./sensor_helpers";
|
|
import { update_dataflash_global } from "./update_dataflash_global";
|
|
import { gui_log } from "./gui_log";
|
|
import { updateTabList } from "./utils/updateTabList";
|
|
import { get as getConfig } from "./ConfigStorage";
|
|
import { tracking } from "./Analytics";
|
|
import semver from "semver";
|
|
import CryptoES from "crypto-es";
|
|
import $ from "jquery";
|
|
import BuildApi from "./BuildApi";
|
|
|
|
import { serial } from "./serial.js";
|
|
import { EventBus } from "../components/eventBus";
|
|
import { ispConnected } from "./utils/connection";
|
|
|
|
const logHead = "[SERIAL-BACKEND]";
|
|
|
|
let mspHelper;
|
|
let connectionTimestamp;
|
|
let liveDataRefreshTimerId = false;
|
|
|
|
let isConnected = false;
|
|
|
|
const REBOOT_CONNECT_MAX_TIME_MS = 10000;
|
|
let rebootTimestamp = 0;
|
|
|
|
const toggleStatus = function () {
|
|
isConnected = !isConnected;
|
|
};
|
|
|
|
function connectHandler(event) {
|
|
onOpen(event.detail);
|
|
toggleStatus();
|
|
}
|
|
|
|
function disconnectHandler(event) {
|
|
onClosed(event.detail);
|
|
}
|
|
|
|
export function initializeSerialBackend() {
|
|
$("a.connection_button__link").on("click", connectDisconnect);
|
|
|
|
EventBus.$on("port-handler:auto-select-serial-device", function (device) {
|
|
if (
|
|
!GUI.connected_to &&
|
|
!GUI.connecting_to &&
|
|
GUI.active_tab !== "firmware_flasher" &&
|
|
((PortHandler.portPicker.autoConnect && !["manual", "virtual"].includes(device)) ||
|
|
Date.now() - rebootTimestamp < REBOOT_CONNECT_MAX_TIME_MS)
|
|
) {
|
|
connectDisconnect();
|
|
}
|
|
});
|
|
|
|
EventBus.$on("port-handler:auto-select-bluetooth-device", function (device) {
|
|
if (
|
|
!GUI.connected_to &&
|
|
!GUI.connecting_to &&
|
|
GUI.active_tab !== "firmware_flasher" &&
|
|
((PortHandler.portPicker.autoConnect && !["manual", "virtual"].includes(device)) ||
|
|
Date.now() - rebootTimestamp < REBOOT_CONNECT_MAX_TIME_MS)
|
|
) {
|
|
connectDisconnect();
|
|
}
|
|
});
|
|
|
|
// Using serial and bluetooth we don't know which event we need before we connect
|
|
// Perhaps we should implement a Connection class that handles the connection and events for bluetooth, serial and sockets
|
|
// TODO: use event gattserverdisconnected for save and reboot and device removal.
|
|
|
|
serial.addEventListener("removedDevice", (event) => {
|
|
if (event.detail.path === GUI.connected_to) {
|
|
connectDisconnect();
|
|
}
|
|
});
|
|
|
|
PortHandler.initialize();
|
|
PortUsage.initialize();
|
|
}
|
|
|
|
function connectDisconnect() {
|
|
const selectedPort = PortHandler.portPicker.selectedPort;
|
|
const portName = selectedPort === "manual" ? PortHandler.portPicker.portOverride : selectedPort;
|
|
|
|
if (!GUI.connect_lock && selectedPort !== "noselection" && !selectedPort.path?.startsWith("usb_")) {
|
|
// GUI control overrides the user control
|
|
|
|
GUI.configuration_loaded = false;
|
|
|
|
const baudRate = PortHandler.portPicker.selectedBauds;
|
|
|
|
if (!isConnected) {
|
|
// prevent connection when we do not have permission
|
|
if (selectedPort.startsWith("requestpermission")) {
|
|
return;
|
|
}
|
|
|
|
console.log(`${logHead} Connecting to: ${portName}`);
|
|
GUI.connecting_to = portName;
|
|
|
|
// lock port select & baud while we are connecting / connected
|
|
PortHandler.portPickerDisabled = true;
|
|
$("div.connection_button__label").text(i18n.getMessage("connecting"));
|
|
|
|
// Set configuration flags for consistency with other code
|
|
CONFIGURATOR.virtualMode = selectedPort === "virtual";
|
|
CONFIGURATOR.bluetoothMode = selectedPort.startsWith("bluetooth");
|
|
CONFIGURATOR.manualMode = selectedPort === "manual";
|
|
|
|
// Select the appropriate protocol based directly on the port path
|
|
serial.selectProtocol(selectedPort);
|
|
console.log("Serial protocol selected:", serial._protocol, "using port", portName);
|
|
|
|
if (CONFIGURATOR.virtualMode) {
|
|
CONFIGURATOR.virtualApiVersion = PortHandler.portPicker.virtualMspVersion;
|
|
// Virtual mode uses a callback instead of port path
|
|
serial.connect(onOpenVirtual);
|
|
} else {
|
|
// Set up event listeners for all non-virtual connections
|
|
serial.removeEventListener("connect", connectHandler);
|
|
serial.addEventListener("connect", connectHandler);
|
|
|
|
serial.removeEventListener("disconnect", disconnectHandler);
|
|
serial.addEventListener("disconnect", disconnectHandler);
|
|
|
|
// All non-virtual modes pass the port path and options
|
|
serial.connect(portName, { baudRate });
|
|
}
|
|
} else {
|
|
// If connected, start disconnection sequence
|
|
GUI.timeout_kill_all();
|
|
GUI.interval_kill_all();
|
|
GUI.tab_switch_cleanup(() => (GUI.tab_switch_in_progress = false));
|
|
|
|
function onFinishCallback() {
|
|
finishClose(toggleStatus);
|
|
}
|
|
|
|
mspHelper?.setArmingEnabled(true, false, onFinishCallback);
|
|
}
|
|
|
|
// show CLI panel on Control+I
|
|
document.onkeydown = function (e) {
|
|
if (e.code === "KeyI" && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
|
|
if (
|
|
serial.connected &&
|
|
GUI.active_tab !== "cli" &&
|
|
semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_47)
|
|
) {
|
|
GUI.showCliPanel();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
function finishClose(finishedCallback) {
|
|
const wasConnected = CONFIGURATOR.connectionValid;
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "Disconnected", {
|
|
time: connectionTimestamp ? Date.now() - connectionTimestamp : undefined,
|
|
});
|
|
|
|
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_46)) {
|
|
// close reset to custom defaults dialog
|
|
$("#dialogResetToCustomDefaults")[0].close();
|
|
}
|
|
|
|
serial.disconnect(onClosed);
|
|
|
|
MSP.disconnect_cleanup();
|
|
PortUsage.reset();
|
|
// To trigger the UI updates by Vue reset the state.
|
|
FC.resetState();
|
|
|
|
GUI.connected_to = false;
|
|
GUI.allowedTabs = GUI.defaultAllowedTabsWhenDisconnected.slice();
|
|
|
|
// close problems dialog
|
|
$("#dialogReportProblems-closebtn").click();
|
|
|
|
// unlock port select & baud
|
|
PortHandler.portPickerDisabled = false;
|
|
|
|
// reset connect / disconnect button
|
|
$("a.connection_button__link").removeClass("active");
|
|
$("div.connection_button__label").text(i18n.getMessage("connect"));
|
|
|
|
// reset active sensor indicators
|
|
sensor_status();
|
|
|
|
if (wasConnected) {
|
|
// detach listeners and remove element data
|
|
$("#content").empty();
|
|
|
|
// close cliPanel if left open
|
|
$(".dialogInteractive")[0].close();
|
|
}
|
|
|
|
$("#tabs .tab_landing a").click();
|
|
|
|
finishedCallback();
|
|
}
|
|
|
|
function setConnectionTimeout() {
|
|
// disconnect after 10 seconds with error if we don't get IDENT data
|
|
GUI.timeout_add(
|
|
"connecting",
|
|
function () {
|
|
if (!CONFIGURATOR.connectionValid) {
|
|
gui_log(i18n.getMessage("noConfigurationReceived"));
|
|
|
|
connectDisconnect();
|
|
}
|
|
},
|
|
10000,
|
|
);
|
|
}
|
|
|
|
function resetConnection() {
|
|
// reset connect / disconnect button
|
|
$("div.connection_button__label").text(i18n.getMessage("connect"));
|
|
$("a.connection_button__link").removeClass("active");
|
|
|
|
CONFIGURATOR.connectionValid = false;
|
|
CONFIGURATOR.cliValid = false;
|
|
CONFIGURATOR.cliActive = false;
|
|
CONFIGURATOR.cliEngineValid = false;
|
|
CONFIGURATOR.cliEngineActive = false;
|
|
|
|
// unlock port select & baud
|
|
PortHandler.portPickerDisabled = false;
|
|
}
|
|
|
|
function abortConnection() {
|
|
GUI.timeout_remove("connecting"); // kill connecting timer
|
|
|
|
GUI.connected_to = false;
|
|
GUI.connecting_to = false;
|
|
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "SerialPortFailed");
|
|
|
|
gui_log(i18n.getMessage("serialPortOpenFail"));
|
|
|
|
resetConnection();
|
|
}
|
|
|
|
/**
|
|
* purpose of this is to bridge the old and new api
|
|
* when serial events are handled.
|
|
*/
|
|
function read_serial_adapter(event) {
|
|
read_serial(event.detail.data);
|
|
}
|
|
|
|
function onOpen(openInfo) {
|
|
if (openInfo) {
|
|
CONFIGURATOR.virtualMode = false;
|
|
|
|
// update connected_to
|
|
GUI.connected_to = GUI.connecting_to;
|
|
|
|
// reset connecting_to
|
|
GUI.connecting_to = false;
|
|
|
|
gui_log(i18n.getMessage("serialPortOpened", [PortHandler.portPicker.selectedPort]));
|
|
|
|
// reset expert mode
|
|
const result = getConfig("expertMode")?.expertMode ?? false;
|
|
$('input[name="expertModeCheckbox"]').prop("checked", result).trigger("change");
|
|
|
|
// serial adds event listener for selected connection type
|
|
serial.removeEventListener("receive", read_serial_adapter);
|
|
serial.addEventListener("receive", read_serial_adapter);
|
|
|
|
setConnectionTimeout();
|
|
FC.resetState();
|
|
mspHelper = new MspHelper();
|
|
MSP.listen(mspHelper.process_data.bind(mspHelper));
|
|
MSP.timeout = 250;
|
|
console.log(`${logHead} Requesting configuration data`);
|
|
|
|
MSP.send_message(MSPCodes.MSP_API_VERSION, false, false, function () {
|
|
gui_log(i18n.getMessage("apiVersionReceived", FC.CONFIG.apiVersion));
|
|
|
|
if (FC.CONFIG.apiVersion.includes("null")) {
|
|
abortConnection();
|
|
return;
|
|
}
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_ACCEPTED)) {
|
|
MSP.send_message(MSPCodes.MSP_FC_VARIANT, false, false, function () {
|
|
if (FC.CONFIG.flightControllerIdentifier === "BTFL") {
|
|
MSP.send_message(MSPCodes.MSP_FC_VERSION, false, false, function () {
|
|
gui_log(
|
|
i18n.getMessage("fcInfoReceived", [
|
|
FC.CONFIG.flightControllerIdentifier,
|
|
FC.CONFIG.flightControllerVersion,
|
|
]),
|
|
);
|
|
|
|
MSP.send_message(MSPCodes.MSP_BUILD_INFO, false, false, function () {
|
|
gui_log(i18n.getMessage("buildInfoReceived", [FC.CONFIG.buildInfo]));
|
|
|
|
MSP.send_message(MSPCodes.MSP_BOARD_INFO, false, false, processBoardInfo);
|
|
});
|
|
});
|
|
} else {
|
|
tracking.sendEvent(
|
|
tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER,
|
|
"ConnectionRefusedFirmwareType",
|
|
{ identifier: FC.CONFIG.flightControllerIdentifier },
|
|
);
|
|
|
|
const dialog = $(".dialogConnectWarning")[0];
|
|
|
|
$(".dialogConnectWarning-content").html(i18n.getMessage("firmwareTypeNotSupported"));
|
|
|
|
$(".dialogConnectWarning-closebtn").click(function () {
|
|
dialog.close();
|
|
});
|
|
|
|
dialog.showModal();
|
|
|
|
connectCli();
|
|
}
|
|
});
|
|
} else {
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "ConnectionRefusedFirmwareVersion", {
|
|
apiVersion: FC.CONFIG.apiVersion,
|
|
});
|
|
|
|
const dialog = $(".dialogConnectWarning")[0];
|
|
|
|
$(".dialogConnectWarning-content").html(
|
|
i18n.getMessage("firmwareVersionNotSupported", [CONFIGURATOR.API_VERSION_ACCEPTED]),
|
|
);
|
|
|
|
$(".dialogConnectWarning-closebtn").click(function () {
|
|
dialog.close();
|
|
});
|
|
|
|
dialog.showModal();
|
|
|
|
connectCli();
|
|
}
|
|
});
|
|
} else {
|
|
abortConnection();
|
|
}
|
|
}
|
|
|
|
function onOpenVirtual() {
|
|
GUI.connected_to = GUI.connecting_to;
|
|
GUI.connecting_to = false;
|
|
|
|
CONFIGURATOR.connectionValid = true;
|
|
isConnected = true;
|
|
|
|
mspHelper = new MspHelper();
|
|
|
|
VirtualFC.setVirtualConfig();
|
|
|
|
processBoardInfo();
|
|
|
|
update_dataflash_global();
|
|
sensor_status(FC.CONFIG.activeSensors);
|
|
updateTabList(FC.FEATURE_CONFIG.features);
|
|
}
|
|
|
|
function processCustomDefaults() {
|
|
if (
|
|
bit_check(FC.CONFIG.targetCapabilities, FC.TARGET_CAPABILITIES_FLAGS.SUPPORTS_CUSTOM_DEFAULTS) &&
|
|
bit_check(FC.CONFIG.targetCapabilities, FC.TARGET_CAPABILITIES_FLAGS.HAS_CUSTOM_DEFAULTS) &&
|
|
FC.CONFIG.configurationState === FC.CONFIGURATION_STATES.DEFAULTS_BARE
|
|
) {
|
|
const dialog = $("#dialogResetToCustomDefaults")[0];
|
|
|
|
$("#dialogResetToCustomDefaults-acceptbtn").click(function () {
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "AcceptResetToCustomDefaults");
|
|
|
|
const buffer = [];
|
|
buffer.push(mspHelper.RESET_TYPES.CUSTOM_DEFAULTS);
|
|
MSP.send_message(MSPCodes.MSP_RESET_CONF, buffer, false);
|
|
|
|
dialog.close();
|
|
|
|
GUI.timeout_add(
|
|
"disconnect",
|
|
function () {
|
|
connectDisconnect(); // disconnect
|
|
},
|
|
0,
|
|
);
|
|
});
|
|
|
|
$("#dialogResetToCustomDefaults-cancelbtn").click(function () {
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "CancelResetToCustomDefaults");
|
|
|
|
dialog.close();
|
|
|
|
setConnectionTimeout();
|
|
|
|
checkReportProblems();
|
|
});
|
|
|
|
dialog.showModal();
|
|
|
|
GUI.timeout_remove("connecting"); // kill connecting timer
|
|
} else {
|
|
checkReportProblems();
|
|
}
|
|
}
|
|
|
|
function processBoardInfo() {
|
|
gui_log(i18n.getMessage("boardInfoReceived", [FC.CONFIG.hardwareName, FC.CONFIG.boardVersion]));
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_46)) {
|
|
checkReportProblems();
|
|
} else {
|
|
processCustomDefaults();
|
|
}
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "Loaded", {
|
|
boardIdentifier: FC.CONFIG.boardIdentifier,
|
|
targetName: FC.CONFIG.targetName,
|
|
boardName: FC.CONFIG.boardName,
|
|
hardware: FC.CONFIG.hardwareName,
|
|
manufacturerId: FC.CONFIG.manufacturerId,
|
|
apiVersion: FC.CONFIG.apiVersion,
|
|
flightControllerVersion: FC.CONFIG.flightControllerVersion,
|
|
flightControllerIdentifier: FC.CONFIG.flightControllerIdentifier,
|
|
mcu: FC.CONFIG.targetName,
|
|
});
|
|
}
|
|
|
|
function checkReportProblems() {
|
|
const PROBLEM_ANALYTICS_EVENT = "ProblemFound";
|
|
const problemItemTemplate = $("#dialogReportProblems-listItemTemplate");
|
|
|
|
function checkReportProblem(problemName, problems) {
|
|
if (bit_check(FC.CONFIG.configurationProblems, FC.CONFIGURATION_PROBLEM_FLAGS[problemName])) {
|
|
problems.push({ name: problemName, description: i18n.getMessage(`reportProblemsDialog${problemName}`) });
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
MSP.send_message(MSPCodes.MSP_STATUS, false, false, function () {
|
|
let needsProblemReportingDialog = false;
|
|
const problemDialogList = $("#dialogReportProblems-list");
|
|
problemDialogList.empty();
|
|
|
|
let problems = [];
|
|
let abort = false;
|
|
|
|
if (semver.minor(FC.CONFIG.apiVersion) > semver.minor(CONFIGURATOR.API_VERSION_MAX_SUPPORTED)) {
|
|
const problemName = "API_VERSION_MAX_SUPPORTED";
|
|
problems.push({
|
|
name: problemName,
|
|
description: i18n.getMessage(`reportProblemsDialog${problemName}`, [
|
|
CONFIGURATOR.latestVersion,
|
|
CONFIGURATOR.latestVersionReleaseUrl,
|
|
CONFIGURATOR.getDisplayVersion(),
|
|
FC.CONFIG.flightControllerVersion,
|
|
]),
|
|
});
|
|
needsProblemReportingDialog = true;
|
|
|
|
abort = true;
|
|
GUI.timeout_remove("connecting"); // kill connecting timer
|
|
connectDisconnect(); // disconnect
|
|
}
|
|
|
|
if (!abort) {
|
|
// only check for problems if we are not already aborting
|
|
needsProblemReportingDialog =
|
|
checkReportProblem("MOTOR_PROTOCOL_DISABLED", problems) || needsProblemReportingDialog;
|
|
|
|
if (have_sensor(FC.CONFIG.activeSensors, "acc")) {
|
|
needsProblemReportingDialog =
|
|
checkReportProblem("ACC_NEEDS_CALIBRATION", problems) || needsProblemReportingDialog;
|
|
}
|
|
}
|
|
|
|
if (needsProblemReportingDialog) {
|
|
problems.forEach((problem) => {
|
|
problemItemTemplate.clone().html(problem.description).appendTo(problemDialogList);
|
|
});
|
|
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, PROBLEM_ANALYTICS_EVENT, {
|
|
problems: problems.map((problem) => problem.name),
|
|
});
|
|
|
|
const problemDialog = $("#dialogReportProblems")[0];
|
|
$("#dialogReportProblems-closebtn").click(function () {
|
|
problemDialog.close();
|
|
});
|
|
|
|
problemDialog.showModal();
|
|
$("#dialogReportProblems").scrollTop(0);
|
|
$("#dialogReportProblems-closebtn").focus();
|
|
}
|
|
|
|
if (!abort) {
|
|
// if we are not aborting, we can continue
|
|
processUid();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function processBuildOptions() {
|
|
const supported = semver.eq(FC.CONFIG.apiVersion, API_VERSION_1_45);
|
|
|
|
// firmware 1_45 or higher is required to support cloud build options
|
|
// firmware 1_46 or higher retrieves build options from the flight controller
|
|
if (supported && FC.CONFIG.buildKey.length === 32 && ispConnected()) {
|
|
const buildApi = new BuildApi();
|
|
|
|
function onLoadCloudBuild(options) {
|
|
FC.CONFIG.buildOptions = options.Request.Options;
|
|
processCraftName();
|
|
}
|
|
|
|
buildApi.requestBuildOptions(FC.CONFIG.buildKey, onLoadCloudBuild, processCraftName);
|
|
} else {
|
|
processCraftName();
|
|
}
|
|
}
|
|
|
|
async function processBuildConfiguration() {
|
|
const supported = semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45);
|
|
|
|
if (supported) {
|
|
// get build key from firmware
|
|
await MSP.promise(MSPCodes.MSP2_GET_TEXT, mspHelper.crunch(MSPCodes.MSP2_GET_TEXT, MSPCodes.BUILD_KEY));
|
|
gui_log(i18n.getMessage("buildKey", FC.CONFIG.buildKey));
|
|
}
|
|
|
|
processBuildOptions();
|
|
}
|
|
|
|
async function processUid() {
|
|
await MSP.promise(MSPCodes.MSP_UID);
|
|
|
|
connectionTimestamp = Date.now();
|
|
|
|
gui_log(i18n.getMessage("uniqueDeviceIdReceived", FC.CONFIG.deviceIdentifier));
|
|
|
|
processBuildConfiguration();
|
|
|
|
tracking.sendEvent(tracking.EVENT_CATEGORIES.FLIGHT_CONTROLLER, "Connected", {
|
|
deviceIdentifier: CryptoES.SHA1(FC.CONFIG.deviceIdentifier),
|
|
});
|
|
}
|
|
|
|
async function processCraftName() {
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45)) {
|
|
await MSP.promise(MSPCodes.MSP2_GET_TEXT, mspHelper.crunch(MSPCodes.MSP2_GET_TEXT, MSPCodes.CRAFT_NAME));
|
|
} else {
|
|
await MSP.promise(MSPCodes.MSP_NAME);
|
|
}
|
|
|
|
gui_log(
|
|
i18n.getMessage(
|
|
"craftNameReceived",
|
|
semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45) ? [FC.CONFIG.craftName] : [FC.CONFIG.name],
|
|
),
|
|
);
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45)) {
|
|
await MSP.promise(MSPCodes.MSP2_GET_TEXT, mspHelper.crunch(MSPCodes.MSP2_GET_TEXT, MSPCodes.PILOT_NAME));
|
|
}
|
|
|
|
FC.CONFIG.armingDisabled = false;
|
|
mspHelper.setArmingEnabled(false, false, setRtc);
|
|
}
|
|
|
|
function setRtc() {
|
|
MSP.send_message(MSPCodes.MSP_SET_RTC, mspHelper.crunch(MSPCodes.MSP_SET_RTC), false, finishOpen);
|
|
}
|
|
|
|
function finishOpen() {
|
|
CONFIGURATOR.connectionValid = true;
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45) && FC.CONFIG.buildOptions.length) {
|
|
GUI.allowedTabs = Array.from(GUI.defaultAllowedTabs);
|
|
|
|
for (const tab of GUI.defaultCloudBuildTabOptions) {
|
|
if (FC.CONFIG.buildOptions.some((opt) => opt.toLowerCase().includes(tab))) {
|
|
GUI.allowedTabs.push(tab);
|
|
}
|
|
}
|
|
} else {
|
|
GUI.allowedTabs = Array.from(GUI.defaultAllowedFCTabsWhenConnected);
|
|
}
|
|
|
|
onConnect();
|
|
|
|
GUI.selectDefaultTabWhenConnected();
|
|
}
|
|
|
|
function connectCli() {
|
|
CONFIGURATOR.connectionValid = true; // making it possible to open the CLI tab
|
|
GUI.allowedTabs = ["cli"];
|
|
onConnect();
|
|
$("#tabs .tab_cli a").click();
|
|
}
|
|
|
|
function onConnect() {
|
|
if (
|
|
$("a.firmware_flasher_button__label").hasClass("active") ||
|
|
$("a.firmware_flasher_button__link").hasClass("active")
|
|
) {
|
|
$("a.firmware_flasher_button__label").removeClass("active");
|
|
$("a.firmware_flasher_button__link").removeClass("active");
|
|
}
|
|
|
|
GUI.timeout_remove("connecting"); // kill connecting timer
|
|
|
|
$("div.connection_button__label").text(i18n.getMessage("disconnect")).addClass("active");
|
|
$("a.connection_button__link").addClass("active");
|
|
|
|
$("#tabs ul.mode-disconnected").hide();
|
|
$("#tabs ul.mode-connected-cli").show();
|
|
|
|
// show only appropriate tabs
|
|
$("#tabs ul.mode-connected li").hide();
|
|
$("#tabs ul.mode-connected li")
|
|
.filter(function (index) {
|
|
const classes = $(this).attr("class").split(/\s+/);
|
|
let found = false;
|
|
|
|
$.each(GUI.allowedTabs, (_index, value) => {
|
|
const tabName = `tab_${value}`;
|
|
if ($.inArray(tabName, classes) >= 0) {
|
|
found = true;
|
|
}
|
|
});
|
|
|
|
if (FC.CONFIG.boardType == 0) {
|
|
if (classes.indexOf("osd-required") >= 0) {
|
|
found = false;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
})
|
|
.show();
|
|
|
|
if (FC.CONFIG.flightControllerVersion !== "") {
|
|
FC.FEATURE_CONFIG.features = new Features(FC.CONFIG);
|
|
FC.BEEPER_CONFIG.beepers = new Beepers(FC.CONFIG);
|
|
FC.BEEPER_CONFIG.dshotBeaconConditions = new Beepers(FC.CONFIG, ["RX_LOST", "RX_SET"]);
|
|
|
|
$("#tabs ul.mode-connected").show();
|
|
|
|
MSP.send_message(MSPCodes.MSP_FEATURE_CONFIG, false, false);
|
|
MSP.send_message(MSPCodes.MSP_BATTERY_CONFIG, false, false);
|
|
MSP.send_message(MSPCodes.MSP_BLACKBOX_CONFIG, false, false);
|
|
MSP.send_message(MSPCodes.MSP_DATAFLASH_SUMMARY, false, false);
|
|
MSP.send_message(MSPCodes.MSP_SDCARD_SUMMARY, false, false);
|
|
|
|
if (FC.CONFIG.boardType === 0 || FC.CONFIG.boardType === 2) {
|
|
startLiveDataRefreshTimer();
|
|
}
|
|
}
|
|
|
|
// header bar
|
|
$("#sensor-status").show();
|
|
$("#portsinput").hide();
|
|
$("#dataflash_wrapper_global").show();
|
|
}
|
|
|
|
function onClosed(result) {
|
|
gui_log(i18n.getMessage(result ? "serialPortClosedOk" : "serialPortClosedFail"));
|
|
|
|
$("#tabs ul.mode-connected").hide();
|
|
$("#tabs ul.mode-connected-cli").hide();
|
|
$("#tabs ul.mode-disconnected").show();
|
|
|
|
// header bar
|
|
$("#sensor-status").hide();
|
|
$("#portsinput").show();
|
|
$("#dataflash_wrapper_global").hide();
|
|
$("#quad-status_wrapper").hide();
|
|
|
|
console.log(`${logHead} Connection closed:`, result);
|
|
|
|
resetConnection();
|
|
|
|
clearLiveDataRefreshTimer();
|
|
|
|
MSP.clearListeners();
|
|
|
|
if (PortHandler.portPicker.selectedPort !== "virtual") {
|
|
serial.removeEventListener("receive", read_serial_adapter);
|
|
serial.removeEventListener("connect", connectHandler);
|
|
serial.removeEventListener("disconnect", disconnectHandler);
|
|
}
|
|
}
|
|
|
|
export function read_serial(info) {
|
|
if (CONFIGURATOR.cliActive) {
|
|
MSP.clearListeners();
|
|
MSP.disconnect_cleanup();
|
|
TABS.cli.read(info);
|
|
} else if (CONFIGURATOR.cliEngineActive) {
|
|
TABS.presets.read(info);
|
|
} else {
|
|
MSP.read(info);
|
|
}
|
|
}
|
|
|
|
export async function update_sensor_status() {
|
|
const statuswrapper = $("#quad-status_wrapper");
|
|
|
|
await MSP.promise(MSPCodes.MSP_ANALOG);
|
|
await MSP.promise(MSPCodes.MSP_BATTERY_STATE);
|
|
|
|
if (FC.ANALOG !== undefined) {
|
|
let nbCells = Math.floor(FC.ANALOG.voltage / FC.BATTERY_CONFIG.vbatmaxcellvoltage) + 1;
|
|
|
|
if (FC.ANALOG.voltage == 0) {
|
|
nbCells = 1;
|
|
}
|
|
|
|
const min = FC.BATTERY_CONFIG.vbatmincellvoltage * nbCells;
|
|
const max = FC.BATTERY_CONFIG.vbatmaxcellvoltage * nbCells;
|
|
const warn = FC.BATTERY_CONFIG.vbatwarningcellvoltage * nbCells;
|
|
const NO_BATTERY_VOLTAGE_MAXIMUM = 1.8; // Maybe is better to add a call to MSP_BATTERY_STATE but is not available for all versions
|
|
|
|
if (FC.ANALOG.voltage < min && FC.ANALOG.voltage > NO_BATTERY_VOLTAGE_MAXIMUM) {
|
|
$(".battery-status").addClass("state-empty").removeClass("state-ok").removeClass("state-warning");
|
|
$(".battery-status").css({ width: "100%" });
|
|
} else {
|
|
$(".battery-status").css({ width: `${((FC.ANALOG.voltage - min) / (max - min)) * 100}%` });
|
|
|
|
if (FC.ANALOG.voltage < warn) {
|
|
$(".battery-status").addClass("state-warning").removeClass("state-empty").removeClass("state-ok");
|
|
} else {
|
|
$(".battery-status").addClass("state-ok").removeClass("state-warning").removeClass("state-empty");
|
|
}
|
|
}
|
|
}
|
|
|
|
await MSP.promise(MSPCodes.MSP_BOXNAMES);
|
|
await MSP.promise(MSPCodes.MSP_STATUS_EX);
|
|
|
|
const active = performance.now() - FC.ANALOG.last_received_timestamp < 300;
|
|
$(".linkicon").toggleClass("active", active);
|
|
|
|
for (let i = 0; i < FC.AUX_CONFIG.length; i++) {
|
|
if (FC.AUX_CONFIG[i] === "ARM") {
|
|
$(".armedicon").toggleClass("active", bit_check(FC.CONFIG.mode, i));
|
|
}
|
|
if (FC.AUX_CONFIG[i] === "FAILSAFE") {
|
|
$(".failsafeicon").toggleClass("active", bit_check(FC.CONFIG.mode, i));
|
|
}
|
|
}
|
|
|
|
if (have_sensor(FC.CONFIG.activeSensors, "gps")) {
|
|
await MSP.promise(MSPCodes.MSP_RAW_GPS);
|
|
}
|
|
|
|
sensor_status(FC.CONFIG.activeSensors, FC.GPS_DATA.fix);
|
|
|
|
statuswrapper.show();
|
|
}
|
|
|
|
async function update_live_status() {
|
|
// cli or presets tab do not use MSP connection
|
|
if (GUI.active_tab !== "cli" && GUI.active_tab !== "presets") {
|
|
await update_sensor_status();
|
|
}
|
|
}
|
|
|
|
function clearLiveDataRefreshTimer() {
|
|
if (liveDataRefreshTimerId) {
|
|
clearInterval(liveDataRefreshTimerId);
|
|
liveDataRefreshTimerId = false;
|
|
}
|
|
}
|
|
|
|
function startLiveDataRefreshTimer() {
|
|
// live data refresh
|
|
clearLiveDataRefreshTimer();
|
|
liveDataRefreshTimerId = setInterval(update_live_status, 250);
|
|
}
|
|
|
|
export function reinitializeConnection(callback) {
|
|
// In virtual mode reconnect when autoconnect is enabled
|
|
if (CONFIGURATOR.virtualMode && PortHandler.portPicker.autoConnect) {
|
|
return setTimeout(function () {
|
|
$("a.connection_button__link").trigger("click");
|
|
}, 500);
|
|
}
|
|
|
|
rebootTimestamp = Date.now();
|
|
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false);
|
|
|
|
if (CONFIGURATOR.bluetoothMode) {
|
|
// Bluetooth devices are not disconnected when rebooting
|
|
connectDisconnect();
|
|
}
|
|
|
|
gui_log(i18n.getMessage("deviceRebooting"));
|
|
|
|
// wait for the device to reboot
|
|
setTimeout(function () {
|
|
gui_log(i18n.getMessage("deviceReady"));
|
|
}, 2000);
|
|
|
|
if (callback && typeof callback === "function") {
|
|
callback();
|
|
}
|
|
}
|