mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-16 04:45:20 +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": {
|
"portsSelectManual": {
|
||||||
"message": "Manual Selection"
|
"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": {
|
"portOverrideText": {
|
||||||
"message": "Port:"
|
"message": "Port:"
|
||||||
},
|
},
|
||||||
|
|
|
@ -224,6 +224,13 @@ input[type="number"]::-webkit-inner-spin-button {
|
||||||
color: var(--subtleAccent);
|
color: var(--subtleAccent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#firmware-virtual-option {
|
||||||
|
height: 76px;
|
||||||
|
width: 180px;
|
||||||
|
margin-right: 15px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#port-override-option {
|
#port-override-option {
|
||||||
height: 76px;
|
height: 76px;
|
||||||
margin-top: 7px;
|
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();
|
upload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ var CONFIGURATOR = {
|
||||||
|
|
||||||
connectionValid: false,
|
connectionValid: false,
|
||||||
connectionValidCliOnly: false,
|
connectionValidCliOnly: false,
|
||||||
|
virtualMode: false,
|
||||||
|
virtualApiVersion: '0.0.1',
|
||||||
cliActive: false,
|
cliActive: false,
|
||||||
cliValid: false,
|
cliValid: false,
|
||||||
gitChangesetId: 'unknown',
|
gitChangesetId: 'unknown',
|
||||||
|
|
|
@ -98,7 +98,7 @@ function closeSerial() {
|
||||||
// automatically close the port when application closes
|
// automatically close the port when application closes
|
||||||
const connectionId = serial.connectionId;
|
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
|
// 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.
|
// sending exit command just in case the cli tab was open.
|
||||||
// reset motors to default (mincommand)
|
// reset motors to default (mincommand)
|
||||||
|
|
|
@ -56,6 +56,10 @@ const MSP = {
|
||||||
JUMBO_FRAME_SIZE_LIMIT: 255,
|
JUMBO_FRAME_SIZE_LIMIT: 255,
|
||||||
|
|
||||||
read: function (readInfo) {
|
read: function (readInfo) {
|
||||||
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = new Uint8Array(readInfo.data);
|
const data = new Uint8Array(readInfo.data);
|
||||||
|
|
||||||
for (const chunk of data) {
|
for (const chunk of data) {
|
||||||
|
@ -310,6 +314,13 @@ const MSP = {
|
||||||
return bufferOut;
|
return bufferOut;
|
||||||
},
|
},
|
||||||
send_message: function (code, data, callback_sent, callback_msp, doCallbackOnError) {
|
send_message: function (code, data, callback_sent, callback_msp, doCallbackOnError) {
|
||||||
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
if (callback_msp) {
|
||||||
|
callback_msp();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (code === undefined) {
|
if (code === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@ const PortHandler = new function () {
|
||||||
PortHandler.initialize = function () {
|
PortHandler.initialize = function () {
|
||||||
this.portPickerElement = $('div#port-picker #port');
|
this.portPickerElement = $('div#port-picker #port');
|
||||||
|
|
||||||
|
// fill dropdown with version numbers
|
||||||
|
generateVirtualApiVersions();
|
||||||
|
|
||||||
// start listening, check after TIMEOUT_CHECK ms
|
// start listening, check after TIMEOUT_CHECK ms
|
||||||
this.check();
|
this.check();
|
||||||
};
|
};
|
||||||
|
@ -72,6 +75,12 @@ PortHandler.check_usb_devices = function (callback) {
|
||||||
data: {isDFU: true},
|
data: {isDFU: true},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
self.portPickerElement.append($('<option/>', {
|
||||||
|
value: 'virtual',
|
||||||
|
text: i18n.getMessage('portsSelectVirtual'),
|
||||||
|
data: {isVirtual: true},
|
||||||
|
}));
|
||||||
|
|
||||||
self.portPickerElement.append($('<option/>', {
|
self.portPickerElement.append($('<option/>', {
|
||||||
value: 'manual',
|
value: 'manual',
|
||||||
text: i18n.getMessage('portsSelectManual'),
|
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/>", {
|
this.portPickerElement.append($("<option/>", {
|
||||||
value: 'manual',
|
value: 'manual',
|
||||||
text: i18n.getMessage('portsSelectManual'),
|
text: i18n.getMessage('portsSelectManual'),
|
||||||
|
|
|
@ -8,7 +8,7 @@ const serial = {
|
||||||
bytesReceived: 0,
|
bytesReceived: 0,
|
||||||
bytesSent: 0,
|
bytesSent: 0,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
connectionType: 'serial', // 'serial' or 'tcp'
|
connectionType: 'serial', // 'serial' or 'tcp' or 'virtual'
|
||||||
connectionIP: '127.0.0.1',
|
connectionIP: '127.0.0.1',
|
||||||
connectionPort: 5761,
|
connectionPort: 5761,
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ const serial = {
|
||||||
const testUrl = path.match(/^tcp:\/\/([A-Za-z0-9\.-]+)(?:\:(\d+))?$/);
|
const testUrl = path.match(/^tcp:\/\/([A-Za-z0-9\.-]+)(?:\:(\d+))?$/);
|
||||||
if (testUrl) {
|
if (testUrl) {
|
||||||
self.connectTcp(testUrl[1], testUrl[2], options, callback);
|
self.connectTcp(testUrl[1], testUrl[2], options, callback);
|
||||||
|
} else if (path === 'virtual') {
|
||||||
|
self.connectVirtual(callback);
|
||||||
} else {
|
} else {
|
||||||
self.connectSerial(path, options, callback);
|
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) {
|
disconnect: function (callback) {
|
||||||
const self = this;
|
const self = this;
|
||||||
self.connected = false;
|
self.connected = false;
|
||||||
|
@ -203,26 +220,32 @@ const serial = {
|
||||||
for (let i = (self.onReceiveError.listeners.length - 1); i >= 0; i--) {
|
for (let i = (self.onReceiveError.listeners.length - 1); i >= 0; i--) {
|
||||||
self.onReceiveError.removeListener(self.onReceiveError.listeners[i]);
|
self.onReceiveError.removeListener(self.onReceiveError.listeners[i]);
|
||||||
}
|
}
|
||||||
|
if (self.connectionType !== 'virtual') {
|
||||||
|
if (self.connectionType === 'tcp') {
|
||||||
|
chrome.sockets.tcp.disconnect(self.connectionId, function () {
|
||||||
|
checkChromeRuntimeError();
|
||||||
|
console.log(`${self.connectionType}: disconnecting socket.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (self.connectionType === 'tcp') {
|
const disconnectFn = (self.connectionType === 'serial') ? chrome.serial.disconnect : chrome.sockets.tcp.close;
|
||||||
chrome.sockets.tcp.disconnect(self.connectionId, function () {
|
disconnectFn(self.connectionId, function (result) {
|
||||||
checkChromeRuntimeError();
|
checkChromeRuntimeError();
|
||||||
console.log(`${self.connectionType}: disconnecting socket.`);
|
|
||||||
|
result = result || self.connectionType === 'tcp';
|
||||||
|
console.log(`${self.connectionType}: ${result ? 'closed' : 'failed to close'} connection with ID: ${self.connectionId}, Sent: ${self.bytesSent} bytes, Received: ${self.bytesReceived} bytes`);
|
||||||
|
|
||||||
|
self.connectionId = false;
|
||||||
|
self.bitrate = 0;
|
||||||
|
|
||||||
|
if (callback) callback(result);
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
|
||||||
const disconnectFn = (self.connectionType === 'serial') ? chrome.serial.disconnect : chrome.sockets.tcp.close;
|
|
||||||
disconnectFn(self.connectionId, function (result) {
|
|
||||||
checkChromeRuntimeError();
|
|
||||||
|
|
||||||
result = result || self.connectionType === 'tcp';
|
|
||||||
console.log(`${self.connectionType}: ${result ? 'closed' : 'failed to close'} connection with ID: ${self.connectionId}, Sent: ${self.bytesSent} bytes, Received: ${self.bytesReceived} bytes`);
|
|
||||||
|
|
||||||
self.connectionId = false;
|
self.connectionId = false;
|
||||||
self.bitrate = 0;
|
if (callback) {
|
||||||
|
callback();
|
||||||
if (callback) callback(result);
|
}
|
||||||
});
|
}
|
||||||
} else {
|
} else {
|
||||||
// connection wasn't opened, so we won't try to close anything
|
// 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
|
// instead we will rise canceled flag which will prevent connect from continueing further after being canceled
|
||||||
|
|
|
@ -12,6 +12,12 @@ function initializeSerialBackend() {
|
||||||
else {
|
else {
|
||||||
$('#port-override-option').hide();
|
$('#port-override-option').hide();
|
||||||
}
|
}
|
||||||
|
if (selected_port.data().isVirtual) {
|
||||||
|
$('#firmware-virtual-option').show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#firmware-virtual-option').hide();
|
||||||
|
}
|
||||||
if (selected_port.data().isDFU) {
|
if (selected_port.data().isDFU) {
|
||||||
$('select#baud').hide();
|
$('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#port-picker #port, div#port-picker #baud, div#port-picker #delay').prop('disabled', true);
|
||||||
$('div.connect_controls div.connect_state').text(i18n.getMessage('connecting'));
|
$('div.connect_controls div.connect_state').text(i18n.getMessage('connecting'));
|
||||||
|
|
||||||
serial.connect(portName, {bitrate: selected_baud}, onOpen);
|
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();
|
toggleStatus();
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,6 +219,8 @@ function setConnectionTimeout() {
|
||||||
|
|
||||||
function onOpen(openInfo) {
|
function onOpen(openInfo) {
|
||||||
if (openInfo) {
|
if (openInfo) {
|
||||||
|
CONFIGURATOR.virtualMode = false;
|
||||||
|
|
||||||
// update connected_to
|
// update connected_to
|
||||||
GUI.connected_to = GUI.connecting_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() {
|
function abortConnect() {
|
||||||
$('div#connectbutton div.connect_state').text(i18n.getMessage('connect'));
|
$('div#connectbutton div.connect_state').text(i18n.getMessage('connect'));
|
||||||
$('div#connectbutton a.connect').removeClass('active');
|
$('div#connectbutton a.connect').removeClass('active');
|
||||||
|
|
|
@ -102,6 +102,8 @@ TABS.gps.initialize = function (callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let frame = document.getElementById('map');
|
||||||
|
|
||||||
// enable data pulling
|
// enable data pulling
|
||||||
GUI.interval_add('gps_pull', function gps_update() {
|
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.
|
// 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() {
|
$('#zoom_in').click(function() {
|
||||||
console.log('zoom in');
|
console.log('zoom in');
|
||||||
const message = {
|
const message = {
|
||||||
|
|
|
@ -1867,6 +1867,11 @@ OSD.msp = {
|
||||||
warningFlags |= (1 << i);
|
warningFlags |= (1 << i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
OSD.virtualMode.warningFlags = warningFlags;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(warningFlags);
|
console.log(warningFlags);
|
||||||
result.push16(warningFlags);
|
result.push16(warningFlags);
|
||||||
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_41)) {
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_41)) {
|
||||||
|
@ -1882,17 +1887,24 @@ OSD.msp = {
|
||||||
result.push8(OSD.data.parameters.cameraFrameHeight);
|
result.push8(OSD.data.parameters.cameraFrameHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
encodeLayout(displayItem) {
|
encodeLayout(displayItem) {
|
||||||
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
OSD.virtualMode.itemPositions[displayItem.index] = this.helpers.pack.position(displayItem);
|
||||||
|
}
|
||||||
|
|
||||||
const buffer = [];
|
const buffer = [];
|
||||||
buffer.push8(displayItem.index);
|
buffer.push8(displayItem.index);
|
||||||
buffer.push16(this.helpers.pack.position(displayItem));
|
buffer.push16(this.helpers.pack.position(displayItem));
|
||||||
return buffer;
|
return buffer;
|
||||||
},
|
},
|
||||||
encodeStatistics(statItem) {
|
encodeStatistics(statItem) {
|
||||||
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
OSD.virtualMode.statisticsState[statItem.index] = statItem.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
const buffer = [];
|
const buffer = [];
|
||||||
buffer.push8(statItem.index);
|
buffer.push8(statItem.index);
|
||||||
buffer.push16(statItem.enabled);
|
buffer.push16(statItem.enabled);
|
||||||
|
@ -1900,10 +1912,49 @@ OSD.msp = {
|
||||||
return buffer;
|
return buffer;
|
||||||
},
|
},
|
||||||
encodeTimer(timer) {
|
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];
|
const buffer = [-2, timer.index];
|
||||||
buffer.push16(this.helpers.pack.timer(timer));
|
buffer.push16(this.helpers.pack.timer(timer));
|
||||||
return buffer;
|
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
|
// Currently only parses MSP_MAX_OSD responses, add a switch on payload.code if more codes are handled
|
||||||
decode(payload) {
|
decode(payload) {
|
||||||
const view = payload.data;
|
const view = payload.data;
|
||||||
|
@ -2068,36 +2119,82 @@ OSD.msp = {
|
||||||
d.parameters.cameraFrameHeight = view.readU8();
|
d.parameters.cameraFrameHeight = view.readU8();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we have the number of profiles, process the OSD elements
|
this.processOsdElements(d, itemsPositionsRead);
|
||||||
for (const item of itemsPositionsRead) {
|
|
||||||
const j = d.displayItems.length;
|
OSD.updateDisplaySize();
|
||||||
let c;
|
},
|
||||||
let suffix;
|
decodeVirtual() {
|
||||||
let ignoreSize = false;
|
const d = OSD.data;
|
||||||
if (d.displayItems.length < OSD.constants.DISPLAY_FIELDS.length) {
|
|
||||||
c = OSD.constants.DISPLAY_FIELDS[j];
|
d.displayItems = [];
|
||||||
} else {
|
d.statItems = [];
|
||||||
c = OSD.constants.UNKNOWN_DISPLAY_FIELD;
|
d.warnings = [];
|
||||||
suffix = (1 + d.displayItems.length - OSD.constants.DISPLAY_FIELDS.length).toString();
|
d.timers = [];
|
||||||
ignoreSize = true;
|
|
||||||
|
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: c.text,
|
||||||
|
desc: c.desc,
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.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
|
this.processOsdElements(OSD.data, OSD.virtualMode.itemPositions);
|
||||||
for (const item of d.displayItems) {
|
|
||||||
if (typeof (item.preview) === 'function') {
|
|
||||||
item.preview = item.preview(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OSD.updateDisplaySize();
|
OSD.updateDisplaySize();
|
||||||
},
|
},
|
||||||
|
@ -2235,6 +2332,10 @@ TABS.osd.initialize = function(callback) {
|
||||||
GUI.active_tab = 'osd';
|
GUI.active_tab = 'osd';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
VirtualFC.setupVirtualOSD();
|
||||||
|
}
|
||||||
|
|
||||||
$('#content').load("./tabs/osd.html", function() {
|
$('#content').load("./tabs/osd.html", function() {
|
||||||
// Prepare symbols depending on the version
|
// Prepare symbols depending on the version
|
||||||
SYM.loadSymbols();
|
SYM.loadSymbols();
|
||||||
|
@ -2325,7 +2426,11 @@ TABS.osd.initialize = function(callback) {
|
||||||
|
|
||||||
OSD.chooseFields();
|
OSD.chooseFields();
|
||||||
|
|
||||||
OSD.msp.decode(info);
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
OSD.msp.decodeVirtual();
|
||||||
|
} else {
|
||||||
|
OSD.msp.decode(info);
|
||||||
|
}
|
||||||
|
|
||||||
if (OSD.data.state.haveMax7456FontDeviceConfigured && !OSD.data.state.isMax7456FontDeviceDetected) {
|
if (OSD.data.state.haveMax7456FontDeviceConfigured && !OSD.data.state.isMax7456FontDeviceDetected) {
|
||||||
$('.noOsdChipDetect').show();
|
$('.noOsdChipDetect').show();
|
||||||
|
|
|
@ -29,13 +29,20 @@ TABS.setup.initialize = function (callback) {
|
||||||
// translate to user-selected language
|
// translate to user-selected language
|
||||||
i18n.localizePage();
|
i18n.localizePage();
|
||||||
|
|
||||||
|
const backupButton = $('#content .backup');
|
||||||
|
|
||||||
if (semver.lt(FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE)) {
|
if (semver.lt(FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE)) {
|
||||||
$('#content .backup').addClass('disabled');
|
backupButton.addClass('disabled');
|
||||||
$('#content .restore').addClass('disabled');
|
$('#content .restore').addClass('disabled');
|
||||||
|
|
||||||
GUI.log(i18n.getMessage('initialSetupBackupAndRestoreApiVersion', [FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE]));
|
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
|
// initialize 3D Model
|
||||||
self.initModel();
|
self.initModel();
|
||||||
|
|
||||||
|
@ -157,7 +164,7 @@ TABS.setup.initialize = function (callback) {
|
||||||
console.log(`YAW reset to 0 deg, fix: ${self.yaw_fix} deg`);
|
console.log(`YAW reset to 0 deg, fix: ${self.yaw_fix} deg`);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#content .backup').click(function () {
|
backupButton.click(function () {
|
||||||
if ($(this).hasClass('disabled')) {
|
if ($(this).hasClass('disabled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,3 +42,34 @@ function bytesToSize(bytes) {
|
||||||
}
|
}
|
||||||
return false;
|
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/ConfigStorage.js"></script>
|
||||||
<script type="text/javascript" src="./js/data_storage.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/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_handler.js"></script>
|
||||||
<script type="text/javascript" src="./js/port_usage.js"></script>
|
<script type="text/javascript" src="./js/port_usage.js"></script>
|
||||||
<script type="text/javascript" src="./js/serial.js"></script>
|
<script type="text/javascript" src="./js/serial.js"></script>
|
||||||
|
@ -165,6 +166,11 @@
|
||||||
<input id="port-override" type="text" value="/dev/rfcomm0"/>
|
<input id="port-override" type="text" value="/dev/rfcomm0"/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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 id="portsinput">
|
||||||
<div class="dropdown dropdown-dark">
|
<div class="dropdown dropdown-dark">
|
||||||
<select class="dropdown-select" id="port" i18n_title="firmwareFlasherManualPort">
|
<select class="dropdown-select" id="port" i18n_title="firmwareFlasherManualPort">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue