mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-15 20:35:23 +03:00
Added Virtual Mode
This commit is contained in:
parent
09663e386e
commit
030a75a89e
15 changed files with 518 additions and 52 deletions
|
@ -52,6 +52,13 @@
|
|||
"portsSelectManual": {
|
||||
"message": "Manual Selection"
|
||||
},
|
||||
"portsSelectVirtual": {
|
||||
"message": "Virtual Mode (Experimental)",
|
||||
"description": "Configure a Virtual Flight Controller without the need of a physical FC."
|
||||
},
|
||||
"virtualMSPVersion": {
|
||||
"message": "Virtual Firmware Version"
|
||||
},
|
||||
"portOverrideText": {
|
||||
"message": "Port:"
|
||||
},
|
||||
|
|
|
@ -224,6 +224,13 @@ input[type="number"]::-webkit-inner-spin-button {
|
|||
color: var(--subtleAccent);
|
||||
}
|
||||
|
||||
#firmware-virtual-option {
|
||||
height: 76px;
|
||||
width: 180px;
|
||||
margin-right: 15px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#port-override-option {
|
||||
height: 76px;
|
||||
margin-top: 7px;
|
||||
|
|
212
src/js/VirtualFC.js
Normal file
212
src/js/VirtualFC.js
Normal file
|
@ -0,0 +1,212 @@
|
|||
'use strict';
|
||||
|
||||
const VirtualFC = {
|
||||
// these values are manufactured to unlock all the functionality of the configurator, they dont represent actual hardware
|
||||
setVirtualConfig() {
|
||||
const virtualFC = FC;
|
||||
|
||||
virtualFC.resetState();
|
||||
|
||||
virtualFC.CONFIG.flightControllerVersion = "4.2.4";
|
||||
virtualFC.CONFIG.apiVersion = CONFIGURATOR.virtualApiVersion;
|
||||
|
||||
virtualFC.FEATURE_CONFIG.features = new Features(FC.CONFIG);
|
||||
virtualFC.FEATURE_CONFIG.features.setMask(0);
|
||||
|
||||
virtualFC.BEEPER_CONFIG.beepers = new Beepers(FC.CONFIG);
|
||||
virtualFC.BEEPER_CONFIG.dshotBeaconConditions = new Beepers(FC.CONFIG, [ "RX_LOST", "RX_SET" ]);
|
||||
|
||||
virtualFC.MIXER_CONFIG.mixer = 3;
|
||||
|
||||
virtualFC.MOTOR_DATA = Array.from({length: 8});
|
||||
virtualFC.MOTOR_3D_CONFIG = true;
|
||||
virtualFC.MOTOR_CONFIG = {
|
||||
minthrottle: 1070,
|
||||
maxthrottle: 2000,
|
||||
mincommand: 1000,
|
||||
motor_count: 4,
|
||||
motor_poles: 14,
|
||||
use_dshot_telemetry: true,
|
||||
use_esc_sensor: false,
|
||||
};
|
||||
|
||||
virtualFC.SERVO_CONFIG = Array.from({length: 8});
|
||||
|
||||
for (let i = 0; i < virtualFC.SERVO_CONFIG.length; i++) {
|
||||
virtualFC.SERVO_CONFIG[i] = {
|
||||
middle: 1500,
|
||||
min: 1000,
|
||||
max: 2000,
|
||||
indexOfChannelToForward: 255,
|
||||
rate: 100,
|
||||
reversedInputSources: 0,
|
||||
};
|
||||
}
|
||||
|
||||
virtualFC.ADJUSTMENT_RANGES = Array.from({length: 16});
|
||||
|
||||
for (let i = 0; i < virtualFC.ADJUSTMENT_RANGES.length; i++) {
|
||||
virtualFC.ADJUSTMENT_RANGES[i] = {
|
||||
slotIndex: 0,
|
||||
auxChannelIndex: 0,
|
||||
range: {
|
||||
start: 900,
|
||||
end: 900,
|
||||
},
|
||||
adjustmentFunction: 0,
|
||||
auxSwitchChannelIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
virtualFC.SERIAL_CONFIG.ports = Array.from({length: 6});
|
||||
|
||||
virtualFC.SERIAL_CONFIG.ports[0] = {
|
||||
identifier: 20,
|
||||
auxChannelIndex: 0,
|
||||
functions: ["MSP"],
|
||||
msp_baudrate: 115200,
|
||||
gps_baudrate: 57600,
|
||||
telemetry_baudrate: "AUTO",
|
||||
blackbox_baudrate: 115200,
|
||||
};
|
||||
|
||||
for (let i = 1; i < virtualFC.SERIAL_CONFIG.ports.length; i++) {
|
||||
virtualFC.SERIAL_CONFIG.ports[i] = {
|
||||
identifier: i-1,
|
||||
auxChannelIndex: 0,
|
||||
functions: [],
|
||||
msp_baudrate: 115200,
|
||||
gps_baudrate: 57600,
|
||||
telemetry_baudrate: "AUTO",
|
||||
blackbox_baudrate: 115200,
|
||||
};
|
||||
}
|
||||
|
||||
virtualFC.LED_STRIP = Array.from({length: 256});
|
||||
|
||||
for (let i = 0; i < virtualFC.LED_STRIP.length; i++) {
|
||||
virtualFC.LED_STRIP[i] = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
functions: ["c"],
|
||||
color: 0,
|
||||
directions: [],
|
||||
parameters: 0,
|
||||
};
|
||||
}
|
||||
|
||||
virtualFC.ANALOG = {
|
||||
voltage: 12,
|
||||
mAhdrawn: 1200,
|
||||
rssi: 100,
|
||||
amperage: 3,
|
||||
};
|
||||
|
||||
virtualFC.CONFIG.sampleRateHz = 12000;
|
||||
virtualFC.PID_ADVANCED_CONFIG.pid_process_denom = 2;
|
||||
|
||||
virtualFC.BLACKBOX.supported = true;
|
||||
|
||||
virtualFC.VTX_CONFIG.vtx_type = 1;
|
||||
|
||||
virtualFC.BATTERY_CONFIG = {
|
||||
vbatmincellvoltage: 1,
|
||||
vbatmaxcellvoltage: 4,
|
||||
vbatwarningcellvoltage: 3,
|
||||
capacity: 10000,
|
||||
voltageMeterSource: 1,
|
||||
currentMeterSource: 1,
|
||||
};
|
||||
|
||||
virtualFC.BATTERY_STATE = {
|
||||
cellCount: 10,
|
||||
voltage: 20,
|
||||
mAhDrawn: 1000,
|
||||
amperage: 3,
|
||||
};
|
||||
|
||||
virtualFC.DATAFLASH = {
|
||||
ready: true,
|
||||
supported: true,
|
||||
sectors: 1024,
|
||||
totalSize: 40000,
|
||||
usedSize: 10000,
|
||||
};
|
||||
|
||||
virtualFC.SDCARD = {
|
||||
supported: true,
|
||||
state: 1,
|
||||
freeSizeKB: 1024,
|
||||
totalSizeKB: 2048,
|
||||
};
|
||||
|
||||
virtualFC.SENSOR_CONFIG = {
|
||||
acc_hardware: 1,
|
||||
baro_hardware: 1,
|
||||
mag_hardware: 1,
|
||||
};
|
||||
|
||||
virtualFC.RC = {
|
||||
channels: Array.from({length: 16}),
|
||||
active_channels: 16,
|
||||
};
|
||||
for (let i = 0; i < virtualFC.RC.channels.length; i++) {
|
||||
virtualFC.RC.channels[i] = 1500;
|
||||
}
|
||||
|
||||
// from https://github.com/betaflight/betaflight/blob/master/docs/Modes.md
|
||||
virtualFC.AUX_CONFIG = ["ARM","ANGLE","HORIZON","ANTI GRAVITY","MAG","HEADFREE","HEADADJ","CAMSTAB","PASSTHRU","BEEPERON","LEDLOW","CALIB",
|
||||
"OSD","TELEMETRY","SERVO1","SERVO2","SERVO3","BLACKBOX","FAILSAFE","AIRMODE","3D","FPV ANGLE MIX","BLACKBOX ERASE","CAMERA CONTROL 1",
|
||||
"CAMERA CONTROL 2","CAMERA CONTROL 3","FLIP OVER AFTER CRASH","BOXPREARM","BEEP GPS SATELLITE COUNT","VTX PIT MODE","USER1","USER2",
|
||||
"USER3","USER4","PID AUDIO","PARALYZE","GPS RESCUE","ACRO TRAINER","DISABLE VTX CONTROL","LAUNCH CONTROL"];
|
||||
FC.AUX_CONFIG_IDS = [0,1,2,4,5,6,7,8,12,13,15,17,19,20,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,39,40,41,42,43,44,45,46,47,48,49];
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
virtualFC.RXFAIL_CONFIG[i] = {
|
||||
mode: 1,
|
||||
value: 1500,
|
||||
};
|
||||
}
|
||||
|
||||
// 11 1111 (pass bitchecks)
|
||||
virtualFC.CONFIG.activeSensors = 63;
|
||||
},
|
||||
setupVirtualOSD(){
|
||||
const virtualOSD = OSD;
|
||||
|
||||
virtualOSD.data.video_system = 1;
|
||||
virtualOSD.data.unit_mode = 1;
|
||||
|
||||
virtualOSD.virtualMode = {
|
||||
itemPositions: Array.from({length: 60}),
|
||||
statisticsState: [],
|
||||
warningFlags: 0,
|
||||
timerData: [],
|
||||
};
|
||||
|
||||
virtualOSD.data.state = {
|
||||
haveMax7456Configured: true,
|
||||
haveOsdFeature: true,
|
||||
haveMax7456FontDeviceConfigured: true,
|
||||
isMax7456FontDeviceDetected: true,
|
||||
haveSomeOsd: true,
|
||||
};
|
||||
|
||||
virtualOSD.data.parameters = {
|
||||
cameraFrameWidth: 30,
|
||||
cameraFrameHeight: 30,
|
||||
};
|
||||
|
||||
virtualOSD.data.osd_profiles = {
|
||||
number: 3,
|
||||
selected: 0,
|
||||
};
|
||||
|
||||
virtualOSD.data.alarms = {
|
||||
rssi: { display_name: i18n.getMessage('osdTimerAlarmOptionRssi'), value: 0 },
|
||||
cap: { display_name: i18n.getMessage('osdTimerAlarmOptionCapacity'), value: 0 },
|
||||
alt: { display_name: i18n.getMessage('osdTimerAlarmOptionAltitude'), value: 0 },
|
||||
time: { display_name: 'Minutes', value: 0 },
|
||||
};
|
||||
},
|
||||
};
|
|
@ -928,6 +928,14 @@ function configuration_restore(callback) {
|
|||
}
|
||||
}
|
||||
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
FC.resetState();
|
||||
FC.CONFIG.apiVersion = CONFIGURATOR.virtualApiVersion;
|
||||
|
||||
sensor_status(FC.CONFIG.activeSensors);
|
||||
update_dataflash_global();
|
||||
}
|
||||
|
||||
upload();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ var CONFIGURATOR = {
|
|||
|
||||
connectionValid: false,
|
||||
connectionValidCliOnly: false,
|
||||
virtualMode: false,
|
||||
virtualApiVersion: '0.0.1',
|
||||
cliActive: false,
|
||||
cliValid: false,
|
||||
gitChangesetId: 'unknown',
|
||||
|
|
|
@ -98,7 +98,7 @@ function closeSerial() {
|
|||
// automatically close the port when application closes
|
||||
const connectionId = serial.connectionId;
|
||||
|
||||
if (connectionId && CONFIGURATOR.connectionValid) {
|
||||
if (connectionId && CONFIGURATOR.connectionValid && !CONFIGURATOR.virtualMode) {
|
||||
// code below is handmade MSP message (without pretty JS wrapper), it behaves exactly like MSP.send_message
|
||||
// sending exit command just in case the cli tab was open.
|
||||
// reset motors to default (mincommand)
|
||||
|
|
|
@ -56,6 +56,10 @@ const MSP = {
|
|||
JUMBO_FRAME_SIZE_LIMIT: 255,
|
||||
|
||||
read: function (readInfo) {
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = new Uint8Array(readInfo.data);
|
||||
|
||||
for (const chunk of data) {
|
||||
|
@ -310,6 +314,13 @@ const MSP = {
|
|||
return bufferOut;
|
||||
},
|
||||
send_message: function (code, data, callback_sent, callback_msp, doCallbackOnError) {
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
if (callback_msp) {
|
||||
callback_msp();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (code === undefined) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ const PortHandler = new function () {
|
|||
PortHandler.initialize = function () {
|
||||
this.portPickerElement = $('div#port-picker #port');
|
||||
|
||||
// fill dropdown with version numbers
|
||||
generateVirtualApiVersions();
|
||||
|
||||
// start listening, check after TIMEOUT_CHECK ms
|
||||
this.check();
|
||||
};
|
||||
|
@ -72,6 +75,12 @@ PortHandler.check_usb_devices = function (callback) {
|
|||
data: {isDFU: true},
|
||||
}));
|
||||
|
||||
self.portPickerElement.append($('<option/>', {
|
||||
value: 'virtual',
|
||||
text: i18n.getMessage('portsSelectVirtual'),
|
||||
data: {isVirtual: true},
|
||||
}));
|
||||
|
||||
self.portPickerElement.append($('<option/>', {
|
||||
value: 'manual',
|
||||
text: i18n.getMessage('portsSelectManual'),
|
||||
|
@ -213,6 +222,12 @@ PortHandler.updatePortSelect = function (ports) {
|
|||
}));
|
||||
}
|
||||
|
||||
this.portPickerElement.append($("<option/>", {
|
||||
value: 'virtual',
|
||||
text: i18n.getMessage('portsSelectVirtual'),
|
||||
data: {isVirtual: true},
|
||||
}));
|
||||
|
||||
this.portPickerElement.append($("<option/>", {
|
||||
value: 'manual',
|
||||
text: i18n.getMessage('portsSelectManual'),
|
||||
|
|
|
@ -8,7 +8,7 @@ const serial = {
|
|||
bytesReceived: 0,
|
||||
bytesSent: 0,
|
||||
failed: 0,
|
||||
connectionType: 'serial', // 'serial' or 'tcp'
|
||||
connectionType: 'serial', // 'serial' or 'tcp' or 'virtual'
|
||||
connectionIP: '127.0.0.1',
|
||||
connectionPort: 5761,
|
||||
|
||||
|
@ -20,6 +20,8 @@ const serial = {
|
|||
const testUrl = path.match(/^tcp:\/\/([A-Za-z0-9\.-]+)(?:\:(\d+))?$/);
|
||||
if (testUrl) {
|
||||
self.connectTcp(testUrl[1], testUrl[2], options, callback);
|
||||
} else if (path === 'virtual') {
|
||||
self.connectVirtual(callback);
|
||||
} else {
|
||||
self.connectSerial(path, options, callback);
|
||||
}
|
||||
|
@ -189,6 +191,21 @@ const serial = {
|
|||
}
|
||||
});
|
||||
},
|
||||
connectVirtual: function (callback) {
|
||||
const self = this;
|
||||
self.connectionType = 'virtual';
|
||||
|
||||
if (!self.openCanceled) {
|
||||
self.connected = true;
|
||||
self.connectionId = 'virtual';
|
||||
self.bitrate = 115200;
|
||||
self.bytesReceived = 0;
|
||||
self.bytesSent = 0;
|
||||
self.failed = 0;
|
||||
|
||||
callback();
|
||||
}
|
||||
},
|
||||
disconnect: function (callback) {
|
||||
const self = this;
|
||||
self.connected = false;
|
||||
|
@ -203,7 +220,7 @@ const serial = {
|
|||
for (let i = (self.onReceiveError.listeners.length - 1); i >= 0; i--) {
|
||||
self.onReceiveError.removeListener(self.onReceiveError.listeners[i]);
|
||||
}
|
||||
|
||||
if (self.connectionType !== 'virtual') {
|
||||
if (self.connectionType === 'tcp') {
|
||||
chrome.sockets.tcp.disconnect(self.connectionId, function () {
|
||||
checkChromeRuntimeError();
|
||||
|
@ -223,6 +240,12 @@ const serial = {
|
|||
|
||||
if (callback) callback(result);
|
||||
});
|
||||
} else {
|
||||
self.connectionId = false;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// connection wasn't opened, so we won't try to close anything
|
||||
// instead we will rise canceled flag which will prevent connect from continueing further after being canceled
|
||||
|
|
|
@ -12,6 +12,12 @@ function initializeSerialBackend() {
|
|||
else {
|
||||
$('#port-override-option').hide();
|
||||
}
|
||||
if (selected_port.data().isVirtual) {
|
||||
$('#firmware-virtual-option').show();
|
||||
}
|
||||
else {
|
||||
$('#firmware-virtual-option').hide();
|
||||
}
|
||||
if (selected_port.data().isDFU) {
|
||||
$('select#baud').hide();
|
||||
}
|
||||
|
@ -67,7 +73,14 @@ function initializeSerialBackend() {
|
|||
$('div#port-picker #port, div#port-picker #baud, div#port-picker #delay').prop('disabled', true);
|
||||
$('div.connect_controls div.connect_state').text(i18n.getMessage('connecting'));
|
||||
|
||||
if (selectedPort.data().isVirtual) {
|
||||
CONFIGURATOR.virtualMode = true;
|
||||
CONFIGURATOR.virtualApiVersion = $('#firmware-version-dropdown :selected').val();
|
||||
|
||||
serial.connect('virtual', {}, onOpenVirtual);
|
||||
} else {
|
||||
serial.connect(portName, {bitrate: selected_baud}, onOpen);
|
||||
}
|
||||
|
||||
toggleStatus();
|
||||
} else {
|
||||
|
@ -206,6 +219,8 @@ function setConnectionTimeout() {
|
|||
|
||||
function onOpen(openInfo) {
|
||||
if (openInfo) {
|
||||
CONFIGURATOR.virtualMode = false;
|
||||
|
||||
// update connected_to
|
||||
GUI.connected_to = GUI.connecting_to;
|
||||
|
||||
|
@ -297,6 +312,23 @@ function onOpen(openInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
function onOpenVirtual() {
|
||||
GUI.connected_to = GUI.connecting_to;
|
||||
GUI.connecting_to = false;
|
||||
|
||||
CONFIGURATOR.connectionValid = true;
|
||||
|
||||
mspHelper = new MspHelper();
|
||||
|
||||
VirtualFC.setVirtualConfig();
|
||||
|
||||
processBoardInfo();
|
||||
|
||||
update_dataflash_global();
|
||||
sensor_status(FC.CONFIG.activeSensors);
|
||||
updateTabList(FC.FEATURE_CONFIG.features);
|
||||
}
|
||||
|
||||
function abortConnect() {
|
||||
$('div#connectbutton div.connect_state').text(i18n.getMessage('connect'));
|
||||
$('div#connectbutton a.connect').removeClass('active');
|
||||
|
|
|
@ -102,6 +102,8 @@ TABS.gps.initialize = function (callback) {
|
|||
}
|
||||
}
|
||||
|
||||
let frame = document.getElementById('map');
|
||||
|
||||
// enable data pulling
|
||||
GUI.interval_add('gps_pull', function gps_update() {
|
||||
// avoid usage of the GPS commands until a GPS sensor is detected for targets that are compiled without GPS support.
|
||||
|
@ -137,8 +139,6 @@ TABS.gps.initialize = function (callback) {
|
|||
}
|
||||
});
|
||||
|
||||
let frame = document.getElementById('map');
|
||||
|
||||
$('#zoom_in').click(function() {
|
||||
console.log('zoom in');
|
||||
const message = {
|
||||
|
|
|
@ -1867,6 +1867,11 @@ OSD.msp = {
|
|||
warningFlags |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
OSD.virtualMode.warningFlags = warningFlags;
|
||||
}
|
||||
|
||||
console.log(warningFlags);
|
||||
result.push16(warningFlags);
|
||||
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_41)) {
|
||||
|
@ -1882,17 +1887,24 @@ OSD.msp = {
|
|||
result.push8(OSD.data.parameters.cameraFrameHeight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
},
|
||||
encodeLayout(displayItem) {
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
OSD.virtualMode.itemPositions[displayItem.index] = this.helpers.pack.position(displayItem);
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
buffer.push8(displayItem.index);
|
||||
buffer.push16(this.helpers.pack.position(displayItem));
|
||||
return buffer;
|
||||
},
|
||||
encodeStatistics(statItem) {
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
OSD.virtualMode.statisticsState[statItem.index] = statItem.enabled;
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
buffer.push8(statItem.index);
|
||||
buffer.push16(statItem.enabled);
|
||||
|
@ -1900,10 +1912,49 @@ OSD.msp = {
|
|||
return buffer;
|
||||
},
|
||||
encodeTimer(timer) {
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
OSD.virtualMode.timerData[timer.index] = {};
|
||||
OSD.virtualMode.timerData[timer.index].src = timer.src;
|
||||
OSD.virtualMode.timerData[timer.index].precision = timer.precision;
|
||||
OSD.virtualMode.timerData[timer.index].alarm = timer.alarm;
|
||||
}
|
||||
|
||||
const buffer = [-2, timer.index];
|
||||
buffer.push16(this.helpers.pack.timer(timer));
|
||||
return buffer;
|
||||
},
|
||||
processOsdElements(data, itemPositions){
|
||||
// Now we have the number of profiles, process the OSD elements
|
||||
for (const item of itemPositions) {
|
||||
const j = data.displayItems.length;
|
||||
let c;
|
||||
let suffix;
|
||||
let ignoreSize = false;
|
||||
if (data.displayItems.length < OSD.constants.DISPLAY_FIELDS.length) {
|
||||
c = OSD.constants.DISPLAY_FIELDS[j];
|
||||
} else {
|
||||
c = OSD.constants.UNKNOWN_DISPLAY_FIELD;
|
||||
suffix = (1 + data.displayItems.length - OSD.constants.DISPLAY_FIELDS.length).toString();
|
||||
ignoreSize = true;
|
||||
}
|
||||
data.displayItems.push($.extend({
|
||||
name: c.name,
|
||||
text: suffix ? [c.text, suffix] : c.text,
|
||||
desc: c.desc,
|
||||
index: j,
|
||||
draw_order: c.draw_order,
|
||||
preview: suffix ? c.preview + suffix : c.preview,
|
||||
ignoreSize,
|
||||
}, this.helpers.unpack.position(item, c)));
|
||||
}
|
||||
|
||||
// Generate OSD element previews and positionable that are defined by a function
|
||||
for (const item of data.displayItems) {
|
||||
if (typeof (item.preview) === 'function') {
|
||||
item.preview = item.preview(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Currently only parses MSP_MAX_OSD responses, add a switch on payload.code if more codes are handled
|
||||
decode(payload) {
|
||||
const view = payload.data;
|
||||
|
@ -2068,36 +2119,82 @@ OSD.msp = {
|
|||
d.parameters.cameraFrameHeight = view.readU8();
|
||||
}
|
||||
|
||||
// Now we have the number of profiles, process the OSD elements
|
||||
for (const item of itemsPositionsRead) {
|
||||
const j = d.displayItems.length;
|
||||
let c;
|
||||
let suffix;
|
||||
let ignoreSize = false;
|
||||
if (d.displayItems.length < OSD.constants.DISPLAY_FIELDS.length) {
|
||||
c = OSD.constants.DISPLAY_FIELDS[j];
|
||||
} else {
|
||||
c = OSD.constants.UNKNOWN_DISPLAY_FIELD;
|
||||
suffix = (1 + d.displayItems.length - OSD.constants.DISPLAY_FIELDS.length).toString();
|
||||
ignoreSize = true;
|
||||
}
|
||||
d.displayItems.push($.extend({
|
||||
this.processOsdElements(d, itemsPositionsRead);
|
||||
|
||||
OSD.updateDisplaySize();
|
||||
},
|
||||
decodeVirtual() {
|
||||
const d = OSD.data;
|
||||
|
||||
d.displayItems = [];
|
||||
d.statItems = [];
|
||||
d.warnings = [];
|
||||
d.timers = [];
|
||||
|
||||
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_36)) {
|
||||
// Parse statistics display enable
|
||||
const expectedStatsCount = OSD.constants.STATISTIC_FIELDS.length;
|
||||
|
||||
for (let i = 0; i < expectedStatsCount; i++) {
|
||||
const v = OSD.virtualMode.statisticsState[i] ? 1 : 0;
|
||||
|
||||
// Known statistics field
|
||||
if (i < expectedStatsCount) {
|
||||
const c = OSD.constants.STATISTIC_FIELDS[i];
|
||||
d.statItems.push({
|
||||
name: c.name,
|
||||
text: suffix ? [c.text, suffix] : c.text,
|
||||
text: c.text,
|
||||
desc: c.desc,
|
||||
index: j,
|
||||
draw_order: c.draw_order,
|
||||
preview: suffix ? c.preview + suffix : c.preview,
|
||||
ignoreSize,
|
||||
}, this.helpers.unpack.position(item, c)));
|
||||
index: i,
|
||||
enabled: v === 1,
|
||||
});
|
||||
|
||||
// Read all the data for any statistics we don't know about
|
||||
} else {
|
||||
const statisticNumber = i - expectedStatsCount + 1;
|
||||
d.statItems.push({
|
||||
name: 'UNKNOWN',
|
||||
text: ['osdTextStatUnknown', statisticNumber],
|
||||
desc: 'osdDescStatUnknown',
|
||||
index: i,
|
||||
enabled: v === 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate OSD element previews and positionable that are defined by a function
|
||||
for (const item of d.displayItems) {
|
||||
if (typeof (item.preview) === 'function') {
|
||||
item.preview = item.preview(d);
|
||||
// Parse configurable timers
|
||||
const expectedTimersCount = 3;
|
||||
for (let i = 0; i < expectedTimersCount; i++) {
|
||||
d.timers.push($.extend({
|
||||
index: i,
|
||||
}, OSD.virtualMode.timerData[i]));
|
||||
}
|
||||
|
||||
// Parse enabled warnings
|
||||
const warningCount = OSD.constants.WARNINGS.length;
|
||||
const warningFlags = OSD.virtualMode.warningFlags;
|
||||
|
||||
for (let i = 0; i < warningCount; i++) {
|
||||
const enabled = (warningFlags & (1 << i)) !== 0;
|
||||
|
||||
// Known warning field
|
||||
if (i < warningCount) {
|
||||
d.warnings.push($.extend(OSD.constants.WARNINGS[i], { enabled }));
|
||||
|
||||
// Push Unknown Warning field
|
||||
} else {
|
||||
const warningNumber = i - warningCount + 1;
|
||||
d.warnings.push({
|
||||
name: 'UNKNOWN',
|
||||
text: ['osdWarningTextUnknown', warningNumber],
|
||||
desc: 'osdWarningUnknown',
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.processOsdElements(OSD.data, OSD.virtualMode.itemPositions);
|
||||
|
||||
OSD.updateDisplaySize();
|
||||
},
|
||||
|
@ -2235,6 +2332,10 @@ TABS.osd.initialize = function(callback) {
|
|||
GUI.active_tab = 'osd';
|
||||
}
|
||||
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
VirtualFC.setupVirtualOSD();
|
||||
}
|
||||
|
||||
$('#content').load("./tabs/osd.html", function() {
|
||||
// Prepare symbols depending on the version
|
||||
SYM.loadSymbols();
|
||||
|
@ -2325,7 +2426,11 @@ TABS.osd.initialize = function(callback) {
|
|||
|
||||
OSD.chooseFields();
|
||||
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
OSD.msp.decodeVirtual();
|
||||
} else {
|
||||
OSD.msp.decode(info);
|
||||
}
|
||||
|
||||
if (OSD.data.state.haveMax7456FontDeviceConfigured && !OSD.data.state.isMax7456FontDeviceDetected) {
|
||||
$('.noOsdChipDetect').show();
|
||||
|
|
|
@ -29,13 +29,20 @@ TABS.setup.initialize = function (callback) {
|
|||
// translate to user-selected language
|
||||
i18n.localizePage();
|
||||
|
||||
const backupButton = $('#content .backup');
|
||||
|
||||
if (semver.lt(FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE)) {
|
||||
$('#content .backup').addClass('disabled');
|
||||
backupButton.addClass('disabled');
|
||||
$('#content .restore').addClass('disabled');
|
||||
|
||||
GUI.log(i18n.getMessage('initialSetupBackupAndRestoreApiVersion', [FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE]));
|
||||
}
|
||||
|
||||
// saving and uploading an imaginary config to hardware is a bad idea
|
||||
if (CONFIGURATOR.virtualMode) {
|
||||
backupButton.addClass('disabled');
|
||||
}
|
||||
|
||||
// initialize 3D Model
|
||||
self.initModel();
|
||||
|
||||
|
@ -157,7 +164,7 @@ TABS.setup.initialize = function (callback) {
|
|||
console.log(`YAW reset to 0 deg, fix: ${self.yaw_fix} deg`);
|
||||
});
|
||||
|
||||
$('#content .backup').click(function () {
|
||||
backupButton.click(function () {
|
||||
if ($(this).hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -42,3 +42,34 @@ function bytesToSize(bytes) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const majorFirmwareVersions = {
|
||||
'1.43': '4.2.*',
|
||||
'1.42': '4.1.*',
|
||||
'1.41': '4.0.*',
|
||||
'1.40': '3.5.*',
|
||||
'1.39': '3.4.*',
|
||||
'1.37': '3.3.0',
|
||||
'1.36': '3.2.*',
|
||||
'1.31': '3.1.0',
|
||||
};
|
||||
|
||||
function generateVirtualApiVersions() {
|
||||
const firmwareVersionDropdown = document.getElementById('firmware-version-dropdown');
|
||||
const max = semver.minor(CONFIGURATOR.API_VERSION_MAX_SUPPORTED);
|
||||
|
||||
for (let i = max; i > 0; i--) {
|
||||
const option = document.createElement("option");
|
||||
const verNum = `1.${i}`;
|
||||
option.value = `${verNum}.0`;
|
||||
option.text = `MSP: ${verNum} `;
|
||||
|
||||
if (majorFirmwareVersions.hasOwnProperty(verNum)) {
|
||||
option.text += ` | Firmware: ${majorFirmwareVersions[verNum]}`;
|
||||
} else if (i === max) {
|
||||
option.text += ` | Latest Firmware`;
|
||||
}
|
||||
|
||||
firmwareVersionDropdown.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
<script type="text/javascript" src="./js/ConfigStorage.js"></script>
|
||||
<script type="text/javascript" src="./js/data_storage.js"></script>
|
||||
<script type="text/javascript" src="./js/fc.js"></script>
|
||||
<script type="text/javascript" src="./js/VirtualFC.js"></script>
|
||||
<script type="text/javascript" src="./js/port_handler.js"></script>
|
||||
<script type="text/javascript" src="./js/port_usage.js"></script>
|
||||
<script type="text/javascript" src="./js/serial.js"></script>
|
||||
|
@ -165,6 +166,11 @@
|
|||
<input id="port-override" type="text" value="/dev/rfcomm0"/>
|
||||
</label>
|
||||
</div>
|
||||
<div id="firmware-virtual-option">
|
||||
<div class="dropdown dropdown-dark">
|
||||
<select id="firmware-version-dropdown" class="dropdown-select" i18n_title="virtualMSPVersion"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="portsinput">
|
||||
<div class="dropdown dropdown-dark">
|
||||
<select class="dropdown-select" id="port" i18n_title="firmwareFlasherManualPort">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue