mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-15 20:35:23 +03:00
931 lines
35 KiB
JavaScript
931 lines
35 KiB
JavaScript
import { i18n } from "../localization";
|
|
import GUI from '../gui';
|
|
import { get as getConfig, set as setConfig } from '../ConfigStorage';
|
|
|
|
import CryptoES from 'crypto-es';
|
|
|
|
const receiver = {
|
|
rateChartHeight: 117,
|
|
analyticsChanges: {},
|
|
needReboot: false,
|
|
elrsPassphraseEnabled: false,
|
|
};
|
|
|
|
receiver.initialize = function (callback) {
|
|
const tab = this;
|
|
|
|
GUI.active_tab = 'receiver';
|
|
|
|
function lookup_elrs_passphrase(uidString) {
|
|
const passphraseMap = getConfig('passphrase_map').passphrase_map || {};
|
|
|
|
return passphraseMap[uidString] ?? 0;
|
|
}
|
|
|
|
function save_elrs_passphrase(uidString, passphrase) {
|
|
const passphraseMap = getConfig('passphrase_map').passphrase_map ?? {};
|
|
|
|
passphraseMap[uidString] = passphrase;
|
|
setConfig({'passphrase_map': passphraseMap});
|
|
}
|
|
|
|
function elrs_passphrase_to_bytes(text) {
|
|
let uidBytes = [0,0,0,0,0,0];
|
|
|
|
if (text) {
|
|
const bindingPhraseFull = `-DMY_BINDING_PHRASE="${text}"`;
|
|
const hash = CryptoES.MD5(bindingPhraseFull).toString();
|
|
uidBytes = Uint8Array.from(Buffer.from(hash, 'hex')).subarray(0, 6);
|
|
}
|
|
|
|
return uidBytes;
|
|
}
|
|
|
|
function get_rc_data() {
|
|
MSP.send_message(MSPCodes.MSP_RC, false, false, get_rssi_config);
|
|
}
|
|
|
|
function get_rssi_config() {
|
|
MSP.send_message(MSPCodes.MSP_RSSI_CONFIG, false, false, get_rc_tuning);
|
|
}
|
|
|
|
function get_rc_tuning() {
|
|
MSP.send_message(MSPCodes.MSP_RC_TUNING, false, false, get_rc_map);
|
|
}
|
|
|
|
function get_rc_map() {
|
|
MSP.send_message(MSPCodes.MSP_RX_MAP, false, false, load_rc_configs);
|
|
}
|
|
|
|
function load_rc_configs() {
|
|
MSP.send_message(MSPCodes.MSP_RC_DEADBAND, false, false, load_rx_config);
|
|
}
|
|
|
|
function load_rx_config() {
|
|
MSP.send_message(MSPCodes.MSP_RX_CONFIG, false, false, load_mixer_config);
|
|
}
|
|
|
|
function load_mixer_config() {
|
|
MSP.send_message(MSPCodes.MSP_MIXER_CONFIG, false, false, load_html);
|
|
}
|
|
|
|
function load_html() {
|
|
$('#content').load("./tabs/receiver.html", process_html);
|
|
}
|
|
|
|
MSP.send_message(MSPCodes.MSP_FEATURE_CONFIG, false, false, get_rc_data);
|
|
|
|
function process_html() {
|
|
self.analyticsChanges = {};
|
|
|
|
const featuresElement = $('.tab-receiver .features');
|
|
|
|
FC.FEATURE_CONFIG.features.generateElements(featuresElement);
|
|
|
|
// translate to user-selected language
|
|
i18n.localizePage();
|
|
|
|
$('.deadband input[name="yaw_deadband"]').val(FC.RC_DEADBAND_CONFIG.yaw_deadband);
|
|
$('.deadband input[name="deadband"]').val(FC.RC_DEADBAND_CONFIG.deadband);
|
|
$('.deadband input[name="3ddeadbandthrottle"]').val(FC.RC_DEADBAND_CONFIG.deadband3d_throttle);
|
|
|
|
$('.sticks input[name="stick_min"]').val(FC.RX_CONFIG.stick_min);
|
|
$('.sticks input[name="stick_center"]').val(FC.RX_CONFIG.stick_center);
|
|
$('.sticks input[name="stick_max"]').val(FC.RX_CONFIG.stick_max);
|
|
|
|
$('select[name="rcInterpolation-select"]').val(FC.RX_CONFIG.rcInterpolation);
|
|
$('input[name="rcInterpolationInterval-number"]').val(FC.RX_CONFIG.rcInterpolationInterval);
|
|
|
|
$('select[name="rcInterpolation-select"]').change(function () {
|
|
tab.updateRcInterpolationParameters();
|
|
}).change();
|
|
|
|
// generate bars
|
|
const bar_names = [
|
|
i18n.getMessage('controlAxisRoll'),
|
|
i18n.getMessage('controlAxisPitch'),
|
|
i18n.getMessage('controlAxisYaw'),
|
|
i18n.getMessage('controlAxisThrottle'),
|
|
];
|
|
|
|
const barContainer = $('.tab-receiver .bars');
|
|
let auxIndex = 1;
|
|
|
|
const numBars = (FC.RC.active_channels > 0) ? FC.RC.active_channels : 8;
|
|
|
|
for (let i = 0; i < numBars; i++) {
|
|
let name;
|
|
if (i < bar_names.length) {
|
|
name = bar_names[i];
|
|
} else {
|
|
name = i18n.getMessage(`controlAxisAux${auxIndex++}`);
|
|
}
|
|
|
|
barContainer.append(`\
|
|
<ul>\
|
|
<li class="name">${name}</li>\
|
|
<li class="meter">\
|
|
<div class="meter-bar">\
|
|
<div class="label"></div>\
|
|
<div class="fill${FC.RC.active_channels === 0 ? 'disabled' : ''}">\
|
|
<div class="label"></div>\
|
|
</div>\
|
|
</div>\
|
|
</li>\
|
|
</ul>\
|
|
`);
|
|
}
|
|
|
|
// we could probably use min and max throttle for the range, will see
|
|
const meterScale = {
|
|
'min': 800,
|
|
'max': 2200,
|
|
};
|
|
|
|
const meterFillArray = [];
|
|
$('.meter .fill', barContainer).each(function () {
|
|
meterFillArray.push($(this));
|
|
});
|
|
|
|
const meterLabelArray = [];
|
|
$('.meter', barContainer).each(function () {
|
|
meterLabelArray.push($('.label' , this));
|
|
});
|
|
|
|
// correct inner label margin on window resize (i don't know how we could do this in css)
|
|
tab.resize = function () {
|
|
const containerWidth = $('.meter:first', barContainer).width(),
|
|
labelWidth = $('.meter .label:first', barContainer).width(),
|
|
margin = (containerWidth / 2) - (labelWidth / 2);
|
|
|
|
for (let i = 0; i < meterLabelArray.length; i++) {
|
|
meterLabelArray[i].css('margin-left', margin);
|
|
}
|
|
};
|
|
|
|
$(window).on('resize', tab.resize).resize(); // trigger so labels get correctly aligned on creation
|
|
|
|
// handle rcmap & rssi aux channel
|
|
let rcMapLetters = ['A', 'E', 'R', 'T', '1', '2', '3', '4'];
|
|
|
|
let strBuffer = [];
|
|
for (let i = 0; i < FC.RC_MAP.length; i++) {
|
|
strBuffer[FC.RC_MAP[i]] = rcMapLetters[i];
|
|
}
|
|
|
|
// reconstruct
|
|
const str = strBuffer.join('');
|
|
|
|
// set current value
|
|
$('input[name="rcmap"]').val(str);
|
|
|
|
// validation / filter
|
|
const lastValid = str;
|
|
|
|
$('input[name="rcmap"]').on('input', function () {
|
|
let val = $(this).val();
|
|
|
|
// limit length to max 8
|
|
if (val.length > 8) {
|
|
val = val.substr(0, 8);
|
|
$(this).val(val);
|
|
}
|
|
});
|
|
|
|
$('input[name="rcmap"]').focusout(function () {
|
|
const val = $(this).val();
|
|
strBuffer = val.split('');
|
|
const duplicityBuffer = [];
|
|
|
|
if (val.length !== 8) {
|
|
$(this).val(lastValid);
|
|
return false;
|
|
}
|
|
|
|
// check if characters inside are all valid, also check for duplicity
|
|
for (let i = 0; i < val.length; i++) {
|
|
if (rcMapLetters.indexOf(strBuffer[i]) < 0) {
|
|
$(this).val(lastValid);
|
|
return false;
|
|
}
|
|
|
|
if (duplicityBuffer.indexOf(strBuffer[i]) < 0) {
|
|
duplicityBuffer.push(strBuffer[i]);
|
|
} else {
|
|
$(this).val(lastValid);
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
// handle helper
|
|
$('select[name="rcmap_helper"]').val(0); // go out of bounds
|
|
$('select[name="rcmap_helper"]').change(function () {
|
|
$('input[name="rcmap"]').val($(this).val());
|
|
});
|
|
|
|
// rssi
|
|
const rssi_channel_e = $('select[name="rssi_channel"]');
|
|
rssi_channel_e.append(`<option value="0">${i18n.getMessage("receiverRssiChannelDisabledOption")}</option>`);
|
|
//1-4 reserved for Roll Pitch Yaw & Throttle, starting at 5
|
|
for (let i = 5; i < FC.RC.active_channels + 1; i++) {
|
|
const messageKey = `controlAxisAux${i-4}`;
|
|
rssi_channel_e.append(`<option value="${i}">${i18n.getMessage(messageKey)}</option>`);
|
|
}
|
|
|
|
$('select[name="rssi_channel"]').val(FC.RSSI_CONFIG.channel);
|
|
|
|
const serialRxSelectElement = $('select.serialRX');
|
|
FC.getSerialRxTypes().forEach((serialRxType, index) => {
|
|
serialRxSelectElement.append(`<option value="${index}">${serialRxType}</option>`);
|
|
});
|
|
|
|
serialRxSelectElement.change(function () {
|
|
const serialRxValue = parseInt($(this).val());
|
|
|
|
let newValue;
|
|
if (serialRxValue !== FC.RX_CONFIG.serialrx_provider) {
|
|
newValue = $(this).find('option:selected').text();
|
|
updateSaveButton(true);
|
|
}
|
|
tab.analyticsChanges['SerialRx'] = newValue;
|
|
|
|
FC.RX_CONFIG.serialrx_provider = serialRxValue;
|
|
});
|
|
|
|
// select current serial RX type
|
|
serialRxSelectElement.val(FC.RX_CONFIG.serialrx_provider);
|
|
|
|
// Convert to select2 and order alphabetic
|
|
if (!GUI.isCordova()) {
|
|
serialRxSelectElement.sortSelect().select2();
|
|
}
|
|
|
|
const spiRxTypes = [
|
|
'NRF24_V202_250K',
|
|
'NRF24_V202_1M',
|
|
'NRF24_SYMA_X',
|
|
'NRF24_SYMA_X5C',
|
|
'NRF24_CX10',
|
|
'CX10A',
|
|
'NRF24_H8_3D',
|
|
'NRF24_INAV',
|
|
'FRSKY_D',
|
|
'FRSKY_X',
|
|
'A7105_FLYSKY',
|
|
'A7105_FLYSKY_2A',
|
|
'NRF24_KN',
|
|
'SFHSS',
|
|
'SPEKTRUM',
|
|
'FRSKY_X_LBT',
|
|
];
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_43)) {
|
|
spiRxTypes.push(
|
|
'REDPINE',
|
|
);
|
|
}
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
|
|
spiRxTypes.push(
|
|
'FRSKY_X_V2',
|
|
'FRSKY_X_LBT_V2',
|
|
'EXPRESSLRS',
|
|
);
|
|
}
|
|
|
|
const spiRxElement = $('select.spiRx');
|
|
for (let i = 0; i < spiRxTypes.length; i++) {
|
|
spiRxElement.append(`<option value="${i}">${spiRxTypes[i]}</option>`);
|
|
}
|
|
|
|
spiRxElement.change(function () {
|
|
const value = parseInt($(this).val());
|
|
|
|
let newValue = undefined;
|
|
if (value !== FC.RX_CONFIG.rxSpiProtocol) {
|
|
newValue = $(this).find('option:selected').text();
|
|
updateSaveButton(true);
|
|
}
|
|
tab.analyticsChanges['SPIRXProtocol'] = newValue;
|
|
|
|
FC.RX_CONFIG.rxSpiProtocol = value;
|
|
});
|
|
|
|
// select current serial RX type
|
|
spiRxElement.val(FC.RX_CONFIG.rxSpiProtocol);
|
|
|
|
if (!GUI.isCordova()) {
|
|
// Convert to select2 and order alphabetic
|
|
spiRxElement.sortSelect().select2();
|
|
}
|
|
|
|
if (FC.FEATURE_CONFIG.features.isEnabled('RX_SPI') && FC.RX_CONFIG.rxSpiProtocol == 19 && semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45)) {
|
|
tab.elrsPassphraseEnabled = true;
|
|
|
|
const elrsUid = $('span.elrsUid');
|
|
const elrsUidString = FC.RX_CONFIG.elrsUid.join(',');
|
|
|
|
elrsUid.text(elrsUidString);
|
|
|
|
const elrsPassphrase = $('input.elrsPassphrase');
|
|
|
|
const passphraseString = lookup_elrs_passphrase(elrsUidString);
|
|
if (passphraseString) {
|
|
elrsPassphrase.val(passphraseString);
|
|
}
|
|
elrsPassphrase.on('keyup', function() {
|
|
const passphrase = elrsPassphrase.val();
|
|
if (passphrase) {
|
|
elrsUid.text(elrs_passphrase_to_bytes(passphrase));
|
|
} else {
|
|
elrsUid.text("0.0.0.0.0.0");
|
|
}
|
|
updateSaveButton(true);
|
|
});
|
|
} else {
|
|
tab.elrsPassphraseEnabled = false;
|
|
}
|
|
|
|
// UI Hooks
|
|
|
|
function updateSaveButton(reboot=false) {
|
|
if (reboot) {
|
|
tab.needReboot = true;
|
|
}
|
|
if (tab.needReboot) {
|
|
$('.update_btn').hide();
|
|
$('.save_btn').show();
|
|
} else {
|
|
$('.update_btn').show();
|
|
$('.save_btn').hide();
|
|
}
|
|
}
|
|
|
|
$('input.feature', featuresElement).change(function () {
|
|
const element = $(this);
|
|
|
|
FC.FEATURE_CONFIG.features.updateData(element);
|
|
updateTabList(FC.FEATURE_CONFIG.features);
|
|
|
|
if (element.attr('name') === "RSSI_ADC" || element.attr('name') === "TELEMETRY") {
|
|
updateSaveButton(true);
|
|
}
|
|
});
|
|
|
|
function checkShowSerialRxBox() {
|
|
if (FC.FEATURE_CONFIG.features.isEnabled('RX_SERIAL')) {
|
|
$('div.serialRXBox').show();
|
|
} else {
|
|
$('div.serialRXBox').hide();
|
|
}
|
|
}
|
|
|
|
function checkShowSpiRxBox() {
|
|
if (FC.FEATURE_CONFIG.features.isEnabled('RX_SPI')) {
|
|
$('div.spiRxBox').show();
|
|
} else {
|
|
$('div.spiRxBox').hide();
|
|
}
|
|
}
|
|
|
|
function checkShowElrsPassphrase() {
|
|
$('#elrsContainer').toggle(tab.elrsPassphraseEnabled);
|
|
$('input.elrsUid').toggle(tab.elrsPassphraseEnabled);
|
|
}
|
|
|
|
$(featuresElement).filter('select').change(function () {
|
|
const element = $(this);
|
|
FC.FEATURE_CONFIG.features.updateData(element);
|
|
updateTabList(FC.FEATURE_CONFIG.features);
|
|
if (element.attr('name') === 'rxMode') {
|
|
checkShowSerialRxBox();
|
|
checkShowSpiRxBox();
|
|
checkShowElrsPassphrase();
|
|
updateSaveButton(true);
|
|
}
|
|
});
|
|
|
|
checkShowSerialRxBox();
|
|
checkShowSpiRxBox();
|
|
checkShowElrsPassphrase();
|
|
updateSaveButton();
|
|
|
|
$('a.refresh').click(function () {
|
|
tab.refresh(function () {
|
|
GUI.log(i18n.getMessage('receiverDataRefreshed'));
|
|
});
|
|
});
|
|
|
|
function saveConfiguration(boot=false) {
|
|
|
|
FC.RX_CONFIG.stick_max = parseInt($('.sticks input[name="stick_max"]').val());
|
|
FC.RX_CONFIG.stick_center = parseInt($('.sticks input[name="stick_center"]').val());
|
|
FC.RX_CONFIG.stick_min = parseInt($('.sticks input[name="stick_min"]').val());
|
|
FC.RC_DEADBAND_CONFIG.yaw_deadband = parseInt($('.deadband input[name="yaw_deadband"]').val());
|
|
FC.RC_DEADBAND_CONFIG.deadband = parseInt($('.deadband input[name="deadband"]').val());
|
|
FC.RC_DEADBAND_CONFIG.deadband3d_throttle = ($('.deadband input[name="3ddeadbandthrottle"]').val());
|
|
|
|
// catch rc map
|
|
rcMapLetters = ['A', 'E', 'R', 'T', '1', '2', '3', '4'];
|
|
strBuffer = $('input[name="rcmap"]').val().split('');
|
|
|
|
for (let i = 0; i < FC.RC_MAP.length; i++) {
|
|
FC.RC_MAP[i] = strBuffer.indexOf(rcMapLetters[i]);
|
|
}
|
|
|
|
// catch rssi aux
|
|
FC.RSSI_CONFIG.channel = parseInt($('select[name="rssi_channel"]').val());
|
|
|
|
FC.RX_CONFIG.rcInterpolation = parseInt($('select[name="rcInterpolation-select"]').val());
|
|
FC.RX_CONFIG.rcInterpolationInterval = parseInt($('input[name="rcInterpolationInterval-number"]').val());
|
|
|
|
FC.RX_CONFIG.rcSmoothingSetpointCutoff = parseInt($('input[name="rcSmoothingSetpointHz-number"]').val());
|
|
FC.RX_CONFIG.rcSmoothingFeedforwardCutoff = parseInt($('input[name="rcSmoothingFeedforwardCutoff-number"]').val());
|
|
FC.RX_CONFIG.rcSmoothingDerivativeType = parseInt($('select[name="rcSmoothingFeedforwardType-select"]').val());
|
|
FC.RX_CONFIG.rcInterpolationChannels = parseInt($('select[name="rcSmoothingChannels-select"]').val());
|
|
FC.RX_CONFIG.rcSmoothingInputType = parseInt($('select[name="rcSmoothingSetpointType-select"]').val());
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_42)) {
|
|
FC.RX_CONFIG.rcSmoothingAutoFactor = parseInt($('input[name="rcSmoothingAutoFactor-number"]').val());
|
|
}
|
|
|
|
if (tab.elrsPassphraseEnabled) {
|
|
const elrsUidChars = $('span.elrsUid')[0].innerText.split(',').map(uidChar => parseInt(uidChar, 10));
|
|
if (elrsUidChars.length === 6) {
|
|
FC.RX_CONFIG.elrsUid = elrsUidChars;
|
|
|
|
const elrsUid = $('span.elrsUid')[0].innerText;
|
|
const elrsPassphrase = $('input.elrsPassphrase').val();
|
|
save_elrs_passphrase(elrsUid, elrsPassphrase);
|
|
} else {
|
|
FC.RX_CONFIG.elrsUid = [0, 0, 0, 0, 0, 0];
|
|
}
|
|
}
|
|
|
|
function save_rssi_config() {
|
|
MSP.send_message(MSPCodes.MSP_SET_RSSI_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_RSSI_CONFIG), false, save_rc_configs);
|
|
}
|
|
|
|
function save_rc_configs() {
|
|
MSP.send_message(MSPCodes.MSP_SET_RC_DEADBAND, mspHelper.crunch(MSPCodes.MSP_SET_RC_DEADBAND), false, save_rx_config);
|
|
}
|
|
|
|
function save_rx_config() {
|
|
const nextCallback = (boot) ? save_feature_config : save_to_eeprom;
|
|
MSP.send_message(MSPCodes.MSP_SET_RX_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_RX_CONFIG), false, nextCallback);
|
|
}
|
|
|
|
function save_feature_config() {
|
|
MSP.send_message(MSPCodes.MSP_SET_FEATURE_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_FEATURE_CONFIG), false, save_to_eeprom);
|
|
}
|
|
|
|
function save_to_eeprom() {
|
|
MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, reboot);
|
|
}
|
|
|
|
function reboot() {
|
|
GUI.log(i18n.getMessage('configurationEepromSaved'));
|
|
if (boot) {
|
|
GUI.tab_switch_cleanup(function() {
|
|
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false, reinitializeConnection);
|
|
});
|
|
}
|
|
}
|
|
|
|
analytics.sendSaveAndChangeEvents(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, tab.analyticsChanges, 'receiver');
|
|
tab.analyticsChanges = {};
|
|
|
|
MSP.send_message(MSPCodes.MSP_SET_RX_MAP, mspHelper.crunch(MSPCodes.MSP_SET_RX_MAP), false, save_rssi_config);
|
|
}
|
|
|
|
$('a.update').click(function () {
|
|
saveConfiguration(false);
|
|
});
|
|
|
|
$('a.save').click(function () {
|
|
saveConfiguration(true);
|
|
tab.needReboot = false;
|
|
});
|
|
|
|
$("a.sticks").click(function() {
|
|
const windowWidth = 370;
|
|
const windowHeight = 510;
|
|
|
|
chrome.app.window.create("/tabs/receiver_msp.html", {
|
|
id: "receiver_msp",
|
|
innerBounds: {
|
|
minWidth: windowWidth, minHeight: windowHeight,
|
|
width: windowWidth, height: windowHeight,
|
|
maxWidth: windowWidth, maxHeight: windowHeight,
|
|
},
|
|
alwaysOnTop: true,
|
|
}, function(createdWindow) {
|
|
// Give the window a callback it can use to send the channels (otherwise it can't see those objects)
|
|
createdWindow.contentWindow.setRawRx = function(channels) {
|
|
if (CONFIGURATOR.connectionValid && GUI.active_tab !== 'cli') {
|
|
mspHelper.setRawRx(channels);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
DarkTheme.isDarkThemeEnabled(function(isEnabled) {
|
|
windowWatcherUtil.passValue(createdWindow, 'darkTheme', isEnabled);
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
let showBindButton = false;
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_43)) {
|
|
showBindButton = bit_check(FC.CONFIG.targetCapabilities, FC.TARGET_CAPABILITIES_FLAGS.SUPPORTS_RX_BIND);
|
|
|
|
$("a.bind").click(function() {
|
|
MSP.send_message(MSPCodes.MSP2_BETAFLIGHT_BIND);
|
|
|
|
GUI.log(i18n.getMessage('receiverButtonBindMessage'));
|
|
});
|
|
}
|
|
$(".bind_btn").toggle(showBindButton);
|
|
|
|
// RC Smoothing
|
|
const smoothingOnOff = semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44) ? FC.RX_CONFIG.rcSmoothingMode : FC.RX_CONFIG.rcSmoothingType;
|
|
|
|
$('.tab-receiver .rcSmoothing').show();
|
|
|
|
const rc_smoothing_protocol_e = $('select[name="rcSmoothing-select"]');
|
|
rc_smoothing_protocol_e.change(function () {
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
|
|
FC.RX_CONFIG.rcSmoothingMode = parseFloat($(this).val());
|
|
} else {
|
|
FC.RX_CONFIG.rcSmoothingType = parseFloat($(this).val());
|
|
}
|
|
updateInterpolationView();
|
|
});
|
|
rc_smoothing_protocol_e.val(smoothingOnOff);
|
|
|
|
const rcSmoothingNumberElement = $('input[name="rcSmoothingSetpointHz-number"]');
|
|
const rcSmoothingFeedforwardNumberElement = $('input[name="rcSmoothingFeedforwardCutoff-number"]');
|
|
rcSmoothingNumberElement.val(FC.RX_CONFIG.rcSmoothingSetpointCutoff);
|
|
rcSmoothingFeedforwardNumberElement.val(FC.RX_CONFIG.rcSmoothingFeedforwardCutoff);
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').show();
|
|
$('select[name="rcSmoothing-setpoint-manual-select"]').val("1");
|
|
if (FC.RX_CONFIG.rcSmoothingSetpointCutoff === 0) {
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').hide();
|
|
$('select[name="rcSmoothing-setpoint-manual-select"]').val("0");
|
|
}
|
|
$('select[name="rcSmoothing-setpoint-manual-select"]').change(function () {
|
|
if ($(this).val() === "0") {
|
|
rcSmoothingNumberElement.val(0);
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').hide();
|
|
}
|
|
if ($(this).val() === "1") {
|
|
rcSmoothingNumberElement.val(FC.RX_CONFIG.rcSmoothingSetpointCutoff);
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').show();
|
|
}
|
|
}).change();
|
|
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').show();
|
|
$('select[name="rcSmoothing-feedforward-select"]').val("1");
|
|
if (FC.RX_CONFIG.rcSmoothingFeedforwardCutoff === 0) {
|
|
$('select[name="rcSmoothing-feedforward-select"]').val("0");
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').hide();
|
|
}
|
|
$('select[name="rcSmoothing-feedforward-select"]').change(function () {
|
|
if ($(this).val() === "0") {
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').hide();
|
|
rcSmoothingFeedforwardNumberElement.val(0);
|
|
}
|
|
if ($(this).val() === "1") {
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').show();
|
|
rcSmoothingFeedforwardNumberElement.val(FC.RX_CONFIG.rcSmoothingFeedforwardCutoff);
|
|
}
|
|
}).change();
|
|
|
|
const rcSmoothingFeedforwardType = $('select[name="rcSmoothingFeedforwardType-select"]');
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_43)) {
|
|
rcSmoothingFeedforwardType.append($(`<option value="3">${i18n.getMessage("receiverRcSmoothingFeedforwardTypeAuto")}</option>`));
|
|
}
|
|
|
|
rcSmoothingFeedforwardType.val(FC.RX_CONFIG.rcSmoothingDerivativeType);
|
|
const rcSmoothingChannels = $('select[name="rcSmoothingChannels-select"]');
|
|
rcSmoothingChannels.val(FC.RX_CONFIG.rcInterpolationChannels);
|
|
const rcSmoothingSetpointType = $('select[name="rcSmoothingSetpointType-select"]');
|
|
rcSmoothingSetpointType.val(FC.RX_CONFIG.rcSmoothingInputType);
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_42)) {
|
|
$('select[name="rcSmoothing-setpoint-manual-select"], select[name="rcSmoothing-feedforward-select"]').change(function() {
|
|
if ($('select[name="rcSmoothing-setpoint-manual-select"]').val() === "0" || $('select[name="rcSmoothing-feedforward-select"]').val() === "0") {
|
|
$('.tab-receiver .rcSmoothing-auto-factor').show();
|
|
} else {
|
|
$('.tab-receiver .rcSmoothing-auto-factor').hide();
|
|
}
|
|
});
|
|
$('select[name="rcSmoothing-setpoint-manual-select"]').change();
|
|
|
|
const rcSmoothingAutoFactor = $('input[name="rcSmoothingAutoFactor-number"]');
|
|
rcSmoothingAutoFactor.val(FC.RX_CONFIG.rcSmoothingAutoFactor);
|
|
} else {
|
|
$('.tab-receiver .rcSmoothing-auto-factor').hide();
|
|
}
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
|
|
$('.receiverRcSmoothingAutoFactorHelp').attr('title', i18n.getMessage("receiverRcSmoothingAutoFactorHelp2"));
|
|
}
|
|
|
|
updateInterpolationView();
|
|
|
|
// Only show the MSP control sticks if the MSP Rx feature is enabled
|
|
$(".sticks_btn").toggle(FC.FEATURE_CONFIG.features.isEnabled('RX_MSP'));
|
|
|
|
const labelsChannelData = {
|
|
ch1: [],
|
|
ch2: [],
|
|
ch3: [],
|
|
ch4: [],
|
|
};
|
|
|
|
$(`.plot_control .ch1, .plot_control .ch2, .plot_control .ch3, .plot_control .ch4`).each(function (){
|
|
const element = $(this);
|
|
if (element.hasClass('ch1')){
|
|
labelsChannelData.ch1.push(element);
|
|
} else if (element.hasClass('ch2')){
|
|
labelsChannelData.ch2.push(element);
|
|
} else if (element.hasClass('ch3')){
|
|
labelsChannelData.ch3.push(element);
|
|
} else if (element.hasClass('ch4')){
|
|
labelsChannelData.ch4.push(element);
|
|
}
|
|
});
|
|
|
|
let plotUpdateRate;
|
|
const rxRefreshRate = $('select[name="rx_refresh_rate"]');
|
|
|
|
$('a.reset_rate').click(function () {
|
|
plotUpdateRate = 50;
|
|
rxRefreshRate.val(plotUpdateRate).change();
|
|
});
|
|
|
|
rxRefreshRate.change(function () {
|
|
plotUpdateRate = parseInt($(this).val(), 10);
|
|
|
|
// save update rate
|
|
setConfig({'rx_refresh_rate': plotUpdateRate});
|
|
|
|
function get_rc_refresh_data() {
|
|
MSP.send_message(MSPCodes.MSP_RC, false, false, update_ui);
|
|
}
|
|
|
|
// setup plot
|
|
const rxPlotData = new Array(FC.RC.active_channels);
|
|
for (let i = 0; i < rxPlotData.length; i++) {
|
|
rxPlotData[i] = [];
|
|
}
|
|
|
|
let samples = 0;
|
|
const svg = d3.select("svg");
|
|
const RX_plot_e = $('#RX_plot');
|
|
const margin = {top: 20, right: 0, bottom: 10, left: 40};
|
|
let width, height, widthScale, heightScale;
|
|
|
|
function update_receiver_plot_size() {
|
|
width = RX_plot_e.width() - margin.left - margin.right;
|
|
height = RX_plot_e.height() - margin.top - margin.bottom;
|
|
|
|
widthScale.range([0, width]);
|
|
heightScale.range([height, 0]);
|
|
}
|
|
|
|
function update_ui() {
|
|
|
|
if (FC.RC.active_channels > 0) {
|
|
|
|
// update bars with latest data
|
|
for (let i = 0; i < FC.RC.active_channels; i++) {
|
|
meterFillArray[i].css('width', `${((FC.RC.channels[i] - meterScale.min) / (meterScale.max - meterScale.min) * 100).clamp(0, 100)}%`);
|
|
meterLabelArray[i].text(FC.RC.channels[i]);
|
|
}
|
|
|
|
labelsChannelData.ch1[0].text(FC.RC.channels[0]);
|
|
labelsChannelData.ch2[0].text(FC.RC.channels[1]);
|
|
labelsChannelData.ch3[0].text(FC.RC.channels[2]);
|
|
labelsChannelData.ch4[0].text(FC.RC.channels[3]);
|
|
|
|
// push latest data to the main array
|
|
for (let i = 0; i < FC.RC.active_channels; i++) {
|
|
rxPlotData[i].push([samples, FC.RC.channels[i]]);
|
|
}
|
|
|
|
// Remove old data from array
|
|
while (rxPlotData[0].length > 300) {
|
|
for (let i = 0; i < rxPlotData.length; i++) {
|
|
rxPlotData[i].shift();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// update required parts of the plot
|
|
widthScale = d3.scale.linear().
|
|
domain([(samples - 299), samples]);
|
|
|
|
heightScale = d3.scale.linear().
|
|
domain([800, 2200]);
|
|
|
|
update_receiver_plot_size();
|
|
|
|
const xGrid = d3.svg.axis().
|
|
scale(widthScale).
|
|
orient("bottom").
|
|
tickSize(-height, 0, 0).
|
|
tickFormat("");
|
|
|
|
const yGrid = d3.svg.axis().
|
|
scale(heightScale).
|
|
orient("left").
|
|
tickSize(-width, 0, 0).
|
|
tickFormat("");
|
|
|
|
const xAxis = d3.svg.axis().
|
|
scale(widthScale).
|
|
orient("bottom").
|
|
tickFormat(function (d) {return d;});
|
|
|
|
const yAxis = d3.svg.axis().
|
|
scale(heightScale).
|
|
orient("left").
|
|
tickFormat(function (d) {return d;});
|
|
|
|
const line = d3.svg.line().
|
|
x(function (d) {return widthScale(d[0]);}).
|
|
y(function (d) {return heightScale(d[1]);});
|
|
|
|
svg.select(".x.grid").call(xGrid);
|
|
svg.select(".y.grid").call(yGrid);
|
|
svg.select(".x.axis").call(xAxis);
|
|
svg.select(".y.axis").call(yAxis);
|
|
|
|
const data = svg.select("g.data");
|
|
const lines = data.selectAll("path").data(rxPlotData, function (d, i) {return i;});
|
|
lines.enter().append("path").attr("class", "line");
|
|
lines.attr('d', line);
|
|
|
|
samples++;
|
|
}
|
|
|
|
// timer initialization
|
|
GUI.interval_remove('receiver_pull');
|
|
|
|
// enable RC data pulling
|
|
GUI.interval_add('receiver_pull', get_rc_refresh_data, plotUpdateRate, true);
|
|
});
|
|
|
|
const result = getConfig('rx_refresh_rate');
|
|
if (result.rxRefreshRate) {
|
|
rxRefreshRate.val(result.rxRefreshRate).change();
|
|
} else {
|
|
rxRefreshRate.change(); // start with default value
|
|
}
|
|
|
|
// Setup model for preview
|
|
tab.initModelPreview();
|
|
tab.renderModel();
|
|
|
|
// TODO: Combine two polls together
|
|
GUI.interval_add('receiver_pull_for_model_preview', tab.getReceiverData, 33, false);
|
|
|
|
// status data pulled via separate timer with static speed
|
|
GUI.interval_add('status_pull', function status_pull() {
|
|
MSP.send_message(MSPCodes.MSP_STATUS);
|
|
}, 250, true);
|
|
|
|
GUI.content_ready(callback);
|
|
}
|
|
};
|
|
|
|
receiver.getReceiverData = function () {
|
|
MSP.send_message(MSPCodes.MSP_RC, false, false);
|
|
};
|
|
|
|
receiver.initModelPreview = function () {
|
|
this.keepRendering = true;
|
|
this.model = new Model($('.model_preview'), $('.model_preview canvas'));
|
|
|
|
this.rateCurve = new RateCurve(false);
|
|
this.currentRates = this.rateCurve.getCurrentRates();
|
|
|
|
$(window).on('resize', $.bind(this.model.resize, this.model));
|
|
};
|
|
|
|
receiver.renderModel = function () {
|
|
if (this.keepRendering) { requestAnimationFrame(this.renderModel.bind(this)); }
|
|
|
|
if (!this.clock) { this.clock = new THREE.Clock(); }
|
|
|
|
if (FC.RC.channels[0] && FC.RC.channels[1] && FC.RC.channels[2]) {
|
|
const delta = this.clock.getDelta();
|
|
|
|
const roll = delta * this.rateCurve.rcCommandRawToDegreesPerSecond(FC.RC.channels[0], this.currentRates.roll_rate, this.currentRates.rc_rate, this.currentRates.rc_expo,
|
|
this.currentRates.superexpo, this.currentRates.deadband, this.currentRates.roll_rate_limit);
|
|
const pitch = delta * this.rateCurve.rcCommandRawToDegreesPerSecond(FC.RC.channels[1], this.currentRates.pitch_rate, this.currentRates.rc_rate_pitch,
|
|
this.currentRates.rc_pitch_expo, this.currentRates.superexpo, this.currentRates.deadband, this.currentRates.pitch_rate_limit);
|
|
const yaw = delta * this.rateCurve.rcCommandRawToDegreesPerSecond(FC.RC.channels[2], this.currentRates.yaw_rate, this.currentRates.rc_rate_yaw,
|
|
this.currentRates.rc_yaw_expo, this.currentRates.superexpo, this.currentRates.yawDeadband, this.currentRates.yaw_rate_limit);
|
|
|
|
this.model.rotateBy(-degToRad(pitch), -degToRad(yaw), -degToRad(roll));
|
|
}
|
|
};
|
|
|
|
receiver.cleanup = function (callback) {
|
|
$(window).off('resize', this.resize);
|
|
if (this.model) {
|
|
$(window).off('resize', $.proxy(this.model.resize, this.model));
|
|
this.model.dispose();
|
|
}
|
|
|
|
this.keepRendering = false;
|
|
|
|
if (callback) callback();
|
|
};
|
|
|
|
receiver.refresh = function (callback) {
|
|
const self = this;
|
|
|
|
GUI.tab_switch_cleanup(function () {
|
|
self.initialize();
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
});
|
|
};
|
|
|
|
receiver.updateRcInterpolationParameters = function () {
|
|
if ($('select[name="rcInterpolation-select"]').val() === '3') {
|
|
$('.tab-receiver .rc-interpolation-manual').show();
|
|
} else {
|
|
$('.tab-receiver .rc-interpolation-manual').hide();
|
|
}
|
|
};
|
|
|
|
function updateInterpolationView() {
|
|
const smoothingOnOff = ((semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) ?
|
|
FC.RX_CONFIG.rcSmoothingMode : FC.RX_CONFIG.rcSmoothingType);
|
|
|
|
$('.tab-receiver .rcInterpolation').hide();
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').show();
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').show();
|
|
$('.tab-receiver .rcSmoothing-feedforward-type').show();
|
|
$('.tab-receiver .rcSmoothing-setpoint-type').show();
|
|
$('.tab-receiver .rcSmoothing-feedforward-manual').show();
|
|
$('.tab-receiver .rcSmoothing-setpoint-manual').show();
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_42)) {
|
|
if (FC.RX_CONFIG.rcSmoothingFeedforwardCutoff === 0 || FC.RX_CONFIG.rcSmoothingSetpointCutoff === 0) {
|
|
$('.tab-receiver .rcSmoothing-auto-factor').show();
|
|
}
|
|
}
|
|
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
|
|
$('.tab-receiver .rcSmoothing-feedforward-type').hide();
|
|
$('.tab-receiver .rcSmoothing-setpoint-type').hide();
|
|
$('.tab-receiver .rc-smoothing-channels').hide();
|
|
$('.tab-receiver input[name="rcSmoothingAutoFactor-number"]').attr("max", "250");
|
|
$('.tab-receiver .rcSmoothingType').hide();
|
|
$('.tab-receiver .rcSmoothingOff').text(i18n.getMessage('off'));
|
|
$('.tab-receiver .rcSmoothingOn').text(i18n.getMessage('on'));
|
|
} else {
|
|
$('.tab-receiver .rcSmoothingMode').hide();
|
|
}
|
|
|
|
if (smoothingOnOff === 0) {
|
|
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').hide();
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').hide();
|
|
$('.tab-receiver .rcSmoothing-feedforward-manual').hide();
|
|
$('.tab-receiver .rcSmoothing-setpoint-manual').hide();
|
|
$('.tab-receiver .rcSmoothing-auto-factor').hide();
|
|
} else {
|
|
$('.tab-receiver .rcInterpolation').show();
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').hide();
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').hide();
|
|
$('.tab-receiver .rcSmoothing-feedforward-type').hide();
|
|
$('.tab-receiver .rcSmoothing-setpoint-type').hide();
|
|
$('.tab-receiver .rcSmoothing-feedforward-manual').hide();
|
|
$('.tab-receiver .rcSmoothing-setpoint-manual').hide();
|
|
$('.tab-receiver .rcSmoothing-auto-factor').hide();
|
|
}
|
|
}
|
|
if (FC.RX_CONFIG.rcSmoothingFeedforwardCutoff === 0) {
|
|
$('.tab-receiver .rcSmoothing-feedforward-cutoff').hide();
|
|
}
|
|
if (FC.RX_CONFIG.rcSmoothingSetpointCutoff === 0) {
|
|
$('.tab-receiver .rcSmoothing-setpoint-cutoff').hide();
|
|
}
|
|
}
|
|
|
|
window.TABS.receiver = receiver;
|
|
export {
|
|
receiver,
|
|
};
|