'use strict';
TABS.vtx = {
supported: false,
vtxTableSavePending: false,
vtxTableFactoryBandsSupported: false,
MAX_POWERLEVEL_VALUES: 8,
MAX_BAND_VALUES: 8,
MAX_BAND_CHANNELS_VALUES: 8,
VTXTABLE_BAND_LIST: [],
VTXTABLE_POWERLEVEL_LIST: [],
analyticsChanges: {},
updating: true,
env: new djv(),
get _DEVICE_STATUS_UPDATE_INTERVAL_NAME() {
return "vtx_device_status_request";
},
};
TABS.vtx.isVtxDeviceStatusNotReady = function()
{
const isReady = (null !== FC.VTX_DEVICE_STATUS) && (FC.VTX_DEVICE_STATUS.deviceIsReady);
return !isReady;
};
TABS.vtx.updateVtxDeviceStatus = function()
{
MSP.send_message(MSPCodes.MSP2_GET_VTX_DEVICE_STATUS, false, false, vtxDeviceStatusReceived);
function vtxDeviceStatusReceived()
{
$("#vtx_type_description").text(TABS.vtx.getVtxTypeString());
}
};
TABS.vtx.getVtxTypeString = function()
{
let result = i18n.getMessage(`vtxType_${FC.VTX_CONFIG.vtx_type}`);
const isSmartAudio = VtxDeviceTypes.VTXDEV_SMARTAUDIO === FC.VTX_CONFIG.vtx_type;
const isVtxDeviceStatusReceived = null !== FC.VTX_DEVICE_STATUS;
if (isSmartAudio && isVtxDeviceStatusReceived) {
result += ` ${FC.VTX_DEVICE_STATUS.smartAudioVersion}`;
}
return result;
};
TABS.vtx.initialize = function (callback) {
const self = this;
if (GUI.active_tab !== 'vtx') {
GUI.active_tab = 'vtx';
}
self.analyticsChanges = {};
this.supported = semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_42);
if (!this.supported) {
load_html();
} else {
read_vtx_config(load_html);
}
function load_html() {
$('#content').load("./tabs/vtx.html", process_html);
}
function process_html() {
initDisplay();
// translate to user-selected language
i18n.localizePage();
if (GUI.isCordova()) {
UI_PHONES.initToolbar();
}
self.updating = false;
GUI.content_ready(callback);
}
// Read all the MSP data needed by the tab
function read_vtx_config(callback_after_msp) {
vtx_config();
function vtx_config() {
MSP.send_message(MSPCodes.MSP_VTX_CONFIG, false, false, vtxConfigReceived);
}
function vtxConfigReceived() {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
GUI.interval_add_condition(self._DEVICE_STATUS_UPDATE_INTERVAL_NAME,
TABS.vtx.updateVtxDeviceStatus,
1000, false,
TABS.vtx.isVtxDeviceStatusNotReady,
);
}
vtxtable_bands();
}
function vtxtable_bands() {
// Simulation of static variable
if (typeof vtxtable_bands.counter === 'undefined') {
TABS.vtx.VTXTABLE_BAND_LIST = [];
vtxtable_bands.counter = 1;
} else {
TABS.vtx.VTXTABLE_BAND_LIST.push(Object.assign({}, FC.VTXTABLE_BAND));
vtxtable_bands.counter++;
}
const buffer = [];
buffer.push8(vtxtable_bands.counter);
if (vtxtable_bands.counter <= FC.VTX_CONFIG.vtx_table_bands) {
MSP.send_message(MSPCodes.MSP_VTXTABLE_BAND, buffer, false, vtxtable_bands);
} else {
vtxtable_bands.counter = undefined;
vtxtable_powerlevels();
}
}
function vtxtable_powerlevels() {
// Simulation of static variable
if (typeof vtxtable_powerlevels.counter === 'undefined') {
TABS.vtx.VTXTABLE_POWERLEVEL_LIST = [];
vtxtable_powerlevels.counter = 1;
} else {
TABS.vtx.VTXTABLE_POWERLEVEL_LIST.push(Object.assign({}, FC.VTXTABLE_POWERLEVEL));
vtxtable_powerlevels.counter++;
}
const buffer = [];
buffer.push8(vtxtable_powerlevels.counter);
if (vtxtable_powerlevels.counter <= FC.VTX_CONFIG.vtx_table_powerlevels) {
MSP.send_message(MSPCodes.MSP_VTXTABLE_POWERLEVEL, buffer, false, vtxtable_powerlevels);
} else {
vtxtable_powerlevels.counter = undefined;
callback_after_msp();
}
}
}
// Validates the vtxConfig object against a JSON Schema
function validateVtxJson(vtxConfig, callback_valid, callback_error) {
// At minimum the version must be defined
if (!vtxConfig.version) {
console.error("Validation against schema failed, version missing");
callback_error();
}
// Load schema
const urlVtxSchema = chrome.runtime.getURL(`resources/jsonschema/vtxconfig_schema-${vtxConfig.version}.json`);
if (GUI.isCordova()) {
// FIXME On android : Fetch API cannot load : URL scheme "file" is not supported
callback_valid();
} else {
fetch(urlVtxSchema)
.then(response => response.json())
.catch(error => console.error('Error fetching VTX Schema:', error))
.then(schemaJson => {
let valid = false;
if (schemaJson !== undefined) {
// Validate
valid = (TABS.vtx.env.validate(schemaJson, vtxConfig) === undefined);
}
console.log("Validation against schema result:", valid);
valid ? callback_valid() : callback_error();
},
);
}
}
// Emulates the MSP read from a vtxConfig object (JSON)
function read_vtx_config_json(vtxConfig, vtxcallback_after_read) {
// Bands and channels
FC.VTX_CONFIG.vtx_table_bands = vtxConfig.vtx_table.bands_list.length;
let maxChannels = 0;
TABS.vtx.VTXTABLE_BAND_LIST = [];
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_bands; i++) {
TABS.vtx.VTXTABLE_BAND_LIST[i - 1] = {};
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_number = i;
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_name = vtxConfig.vtx_table.bands_list[i - 1].name;
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_letter = vtxConfig.vtx_table.bands_list[i - 1].letter;
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_is_factory_band = vtxConfig.vtx_table.bands_list[i - 1].is_factory_band;
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies = vtxConfig.vtx_table.bands_list[i - 1].frequencies;
maxChannels = Math.max(maxChannels, TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies.length);
}
FC.VTX_CONFIG.vtx_table_channels = maxChannels;
// Power levels
FC.VTX_CONFIG.vtx_table_powerlevels = vtxConfig.vtx_table.powerlevels_list.length;
TABS.vtx.VTXTABLE_POWERLEVEL_LIST = [];
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_powerlevels; i++) {
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1] = {};
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_number = i;
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_value = vtxConfig.vtx_table.powerlevels_list[i - 1].value;
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_label = vtxConfig.vtx_table.powerlevels_list[i - 1].label;
}
vtxcallback_after_read();
}
// Prepares all the UI elements, the MSP command has been executed before
function initDisplay() {
if (!TABS.vtx.supported) {
$(".tab-vtx").removeClass("supported");
return;
}
$(".tab-vtx").addClass("supported");
// Load all the dynamic elements
loadPowerLevelsTemplate();
loadBandsChannelsTemplate();
populateBandSelect();
populatePowerSelect();
$(".uppercase").keyup(function(){
this.value = this.value.toUpperCase().trim();
});
// Supported?
const vtxSupported = FC.VTX_CONFIG.vtx_type !== VtxDeviceTypes.VTXDEV_UNSUPPORTED && FC.VTX_CONFIG.vtx_type !== VtxDeviceTypes.VTXDEV_UNKNOWN;
const vtxTableNotConfigured = vtxSupported && FC.VTX_CONFIG.vtx_table_available &&
(FC.VTX_CONFIG.vtx_table_bands === 0 || FC.VTX_CONFIG.vtx_table_channels === 0 || FC.VTX_CONFIG.vtx_table_powerlevels === 0);
TABS.vtx.vtxTableFactoryBandsSupported = FC.VTX_CONFIG.vtx_type === VtxDeviceTypes.VTXDEV_SMARTAUDIO;
$(".vtx_supported").toggle(vtxSupported);
$(".vtx_not_supported").toggle(!vtxSupported);
$(".vtx_table_available").toggle(vtxSupported && FC.VTX_CONFIG.vtx_table_available);
$(".vtx_table_not_configured").toggle(vtxTableNotConfigured);
$(".vtx_table_save_pending").toggle(TABS.vtx.vtxTableSavePending);
$(".factory_band").toggle(TABS.vtx.vtxTableFactoryBandsSupported);
// Buttons
$('.clipboard_available').toggle(Clipboard.available && Clipboard.readAvailable);
// Insert actual values in the fields
// Values of the selected mode
$("#vtx_frequency").val(FC.VTX_CONFIG.vtx_frequency);
$("#vtx_band").val(FC.VTX_CONFIG.vtx_band);
$("#vtx_band").change(populateChannelSelect).change();
$("#vtx_channel").val(FC.VTX_CONFIG.vtx_channel);
if (FC.VTX_CONFIG.vtx_table_available) {
$("#vtx_channel").attr("max", FC.VTX_CONFIG.vtx_table_channels);
}
$("#vtx_power").val(FC.VTX_CONFIG.vtx_power);
$("#vtx_pit_mode").prop('checked', FC.VTX_CONFIG.vtx_pit_mode);
$("#vtx_pit_mode_frequency").val(FC.VTX_CONFIG.vtx_pit_mode_frequency);
$("#vtx_low_power_disarm").val(FC.VTX_CONFIG.vtx_low_power_disarm);
// Values of the current values
const yesMessage = i18n.getMessage("yes");
const noMessage = i18n.getMessage("no");
$("#vtx_device_ready_description").text(FC.VTX_CONFIG.vtx_device_ready ? yesMessage : noMessage);
$("#vtx_type_description").text(self.getVtxTypeString());
$("#vtx_channel_description").text(FC.VTX_CONFIG.vtx_channel);
$("#vtx_frequency_description").text(FC.VTX_CONFIG.vtx_frequency);
$("#vtx_pit_mode_description").text(FC.VTX_CONFIG.vtx_pit_mode ? yesMessage : noMessage);
$("#vtx_pit_mode_frequency_description").text(FC.VTX_CONFIG.vtx_pit_mode_frequency);
$("#vtx_low_power_disarm_description").text(i18n.getMessage(`vtxLowPowerDisarmOption_${FC.VTX_CONFIG.vtx_low_power_disarm}`));
if (FC.VTX_CONFIG.vtx_band === 0) {
$("#vtx_band_description").text(i18n.getMessage("vtxBand_0"));
} else {
if (FC.VTX_CONFIG.vtx_table_available && TABS.vtx.VTXTABLE_BAND_LIST[FC.VTX_CONFIG.vtx_band - 1]) {
let bandName = TABS.vtx.VTXTABLE_BAND_LIST[FC.VTX_CONFIG.vtx_band - 1].vtxtable_band_name;
if (bandName.trim() === '') {
bandName = FC.VTX_CONFIG.vtx_band;
}
$("#vtx_band_description").text(bandName);
} else {
$("#vtx_band_description").text(FC.VTX_CONFIG.vtx_band);
}
}
if (FC.VTX_CONFIG.vtx_power === 0) {
$("#vtx_power_description").text(i18n.getMessage("vtxPower_0"));
} else {
if (FC.VTX_CONFIG.vtx_table_available) {
let powerLevel = TABS.vtx.VTXTABLE_POWERLEVEL_LIST[FC.VTX_CONFIG.vtx_power - 1].vtxtable_powerlevel_label;
if (powerLevel.trim() === '') {
powerLevel = FC.VTX_CONFIG.vtx_power;
}
$("#vtx_power_description").text(powerLevel);
} else {
const levelText = i18n.getMessage('vtxPower_X', {powerLevel: FC.VTX_CONFIG.vtx_power});
$("#vtx_power_description").text(levelText);
}
}
$("#vtx_table_powerlevels").val(FC.VTX_CONFIG.vtx_table_powerlevels);
// Populate power levels
for (let i = 1; i <= TABS.vtx.VTXTABLE_POWERLEVEL_LIST.length; i++) {
$(`#vtx_table_powerlevels_${i}`).val(TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_value);
$(`#vtx_table_powerlabels_${i}`).val(TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_label);
}
$("#vtx_table_bands").val(FC.VTX_CONFIG.vtx_table_bands);
$("#vtx_table_channels").val(FC.VTX_CONFIG.vtx_table_channels);
// Populate VTX Table
let hasFactoryBands = false;
for (let i = 1; i <= TABS.vtx.VTXTABLE_BAND_LIST.length; i++) {
$(`#vtx_table_band_name_${i}`).val(TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_name);
$(`#vtx_table_band_letter_${i}`).val(TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_letter);
$(`#vtx_table_band_factory_${i}`).prop("checked", TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_is_factory_band);
hasFactoryBands = hasFactoryBands || TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_is_factory_band;
for (let j = 1; j <= TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies.length; j++) {
$(`#vtx_table_band_channel_${i}_${j}`).val(TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies[j - 1]);
}
}
$(".vtx_table_factory_bands_not_supported").toggle(!TABS.vtx.vtxTableFactoryBandsSupported && hasFactoryBands);
// Actions and other
function frequencyOrBandChannel() {
const frequencyEnabled = $(this).prop('checked');
if (frequencyEnabled) {
$(".field.vtx_channel").slideUp(100, function() {
$(".field.vtx_band").slideUp(100, function() {
$(".field.vtx_frequency").slideDown(100);
});
});
} else {
$(".field.vtx_frequency").slideUp(100, function() {
$(".field.vtx_band").slideDown(100,function() {
$(".field.vtx_channel").slideDown(100);
});
});
}
}
$("#vtx_frequency_channel").prop('checked', FC.VTX_CONFIG.vtx_band === 0 && FC.VTX_CONFIG.vtx_frequency > 0).change(frequencyOrBandChannel);
if ($("#vtx_frequency_channel").prop('checked')) {
$(".field.vtx_channel").hide();
$(".field.vtx_band").hide();
$(".field.vtx_frequency").show();
} else {
$(".field.vtx_channel").show();
$(".field.vtx_band").show();
$(".field.vtx_frequency").hide();
}
function showHidePowerlevels() {
const powerlevelsValue = $(this).val();
for (let i = 1; i <= TABS.vtx.MAX_POWERLEVEL_VALUES; i++) {
$(`.vtx_table_powerlevels_table td:nth-child(${i})`).toggle(i <= powerlevelsValue);
}
}
$("#vtx_table_powerlevels").on('input', showHidePowerlevels).trigger('input');
function showHideBands() {
const bandsValue = $(this).val();
for (let i = 1; i <= TABS.vtx.MAX_BAND_VALUES; i++) {
$(`.vtx_table_bands_table tr:nth-child(${(i + 1)})`).toggle(i <= bandsValue);
}
}
$("#vtx_table_bands").on('input', showHideBands).trigger('input');
function showHideBandChannels() {
const channelsValue = $(this).val();
for (let i = 1; i <= TABS.vtx.MAX_BAND_CHANNELS_VALUES; i++) {
$(`.vtx_table_bands_table td:nth-child(${(i + 3)})`).toggle(i <= channelsValue);
}
}
$("#vtx_table_channels").on('input', showHideBandChannels).trigger('input');
$("#vtx_table").change(function() {
let fromScratch = true;
if (self.analyticsChanges['VtxTableLoadFromClipboard'] !== undefined || self.analyticsChanges['VtxTableLoadFromFile'] !== undefined) {
fromScratch = false;
}
self.analyticsChanges['VtxTableEdit'] = fromScratch ? 'modificationOnly' : 'fromTemplate';
});
/*** Helper functions */
function loadPowerLevelsTemplate() {
// Power levels title
const powerLevelsTitleEle = $(".vtx_table_powerlevels_table .vtx_table_powerlevels_title");
for (let i = 1; i <= TABS.vtx.MAX_POWERLEVEL_VALUES; i++) {
powerLevelsTitleEle.append(`
${i} | `);
}
// Power levels
const powerLevelsRowEle = $(".vtx_table_powerlevels_table .vtx_table_powerlevels_values");
const powerValuesEle = $("#tab-vtx-templates #tab-vtx-powerlevel-values td");
for (let i = 1; i <= TABS.vtx.MAX_POWERLEVEL_VALUES; i++) {
const newPowerValuesEle = powerValuesEle.clone();
$(newPowerValuesEle).find('input').attr('id', `vtx_table_powerlevels_${i}`);
powerLevelsRowEle.append(newPowerValuesEle);
}
powerLevelsRowEle.append(`${i18n.getMessage('vtxTablePowerLevelsValue')} | `);
// Power labels
const powerLabelsRowEle = $(".vtx_table_powerlevels_table .vtx_table_powerlevels_labels");
const powerLabelsEle = $("#tab-vtx-templates #tab-vtx-powerlevel-labels td");
for (let i = 1; i <= TABS.vtx.MAX_POWERLEVEL_VALUES; i++) {
const newPowerLabelsEle = powerLabelsEle.clone();
$(newPowerLabelsEle).find('input').attr('id', `vtx_table_powerlabels_${i}`);
powerLabelsRowEle.append(newPowerLabelsEle);
}
powerLabelsRowEle.append(`${i18n.getMessage('vtxTablePowerLevelsLabel')} | `);
}
function loadBandsChannelsTemplate() {
const bandsTableEle = $(".vtx_table_bands_table tbody");
// Title
const titleEle = $("#tab-vtx-templates #tab-vtx-bands-title tr");
for (let i = 1; i <= TABS.vtx.MAX_BAND_VALUES; i++) {
titleEle.append(`${i} | `);
}
bandsTableEle.append(titleEle);
// Bands
const bandEle = $("#tab-vtx-templates #tab-vtx-bands tr");
const channelEle = $("#tab-vtx-templates #tab-vtx-channels td");
for (let i = 1; i <= TABS.vtx.MAX_BAND_VALUES; i++) {
const newBandEle = bandEle.clone();
$(newBandEle).find('#vtx_table_band_name').attr('id', `vtx_table_band_name_${i}`);
$(newBandEle).find('#vtx_table_band_letter').attr('id', `vtx_table_band_letter_${i}`);
$(newBandEle).find('#vtx_table_band_factory').attr('id', `vtx_table_band_factory_${i}`);
// Channels
for (let j = 1; j <= TABS.vtx.MAX_BAND_CHANNELS_VALUES; j++) {
const newChannelEle = channelEle.clone();
$(newChannelEle).find('input').attr('id', `vtx_table_band_channel_${i}_${j}`);
newBandEle.append(newChannelEle);
}
// Append to the end an index of the band
newBandEle.append(`${i18n.getMessage("vtxBand_X", {bandName: i})} | `);
bandsTableEle.append(newBandEle);
}
}
function populateBandSelect() {
const selectBand = $(".field #vtx_band");
selectBand.append(new Option(i18n.getMessage('vtxBand_0'), 0));
if (FC.VTX_CONFIG.vtx_table_available) {
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_bands; i++) {
let bandName = TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_name;
if (bandName.trim() === '') {
bandName = i18n.getMessage('vtxBand_X', {bandName: i});
}
selectBand.append(new Option(bandName, i));
}
} else {
for (let i = 1; i <= TABS.vtx.MAX_BAND_VALUES; i++) {
selectBand.append(new Option(i18n.getMessage('vtxBand_X', {bandName: i}), i));
}
}
}
function populateChannelSelect() {
const selectChannel = $(".field #vtx_channel");
const selectedBand = $("#vtx_band").val();
selectChannel.empty();
selectChannel.append(new Option(i18n.getMessage('vtxChannel_0'), 0));
if (FC.VTX_CONFIG.vtx_table_available) {
if (TABS.vtx.VTXTABLE_BAND_LIST[selectedBand - 1]) {
for (let i = 1; i <= TABS.vtx.VTXTABLE_BAND_LIST[selectedBand - 1].vtxtable_band_frequencies.length; i++) {
const channelName = TABS.vtx.VTXTABLE_BAND_LIST[selectedBand - 1].vtxtable_band_frequencies[i - 1];
if (channelName > 0) {
selectChannel.append(new Option(i18n.getMessage('vtxChannel_X', {channelName: i}), i));
}
}
}
} else {
for (let i = 1; i <= TABS.vtx.MAX_BAND_CHANNELS_VALUES; i++) {
selectChannel.append(new Option(i18n.getMessage('vtxChannel_X', {channelName: i}), i));
}
}
}
function populatePowerSelect() {
const selectPower = $(".field #vtx_power");
if (FC.VTX_CONFIG.vtx_table_available) {
selectPower.append(new Option(i18n.getMessage('vtxPower_0'), 0));
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_powerlevels; i++) {
let powerLevel = TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_label;
if (powerLevel.trim() === '') {
powerLevel = i18n.getMessage('vtxPower_X', {powerLevel: i});
}
selectPower.append(new Option(powerLevel, i));
}
} else {
const powerMaxMinValues = getPowerValues(FC.VTX_CONFIG.vtx_type);
for (let i = powerMaxMinValues.min; i <= powerMaxMinValues.max; i++) {
if (i === 0) {
selectPower.append(new Option(i18n.getMessage('vtxPower_0'), 0));
} else {
selectPower.append(new Option(i18n.getMessage('vtxPower_X', {powerLevel: i}), i));
}
}
}
}
// Returns the power values min and max depending on the VTX Type
function getPowerValues(vtxType) {
let powerMinMax = {};
if (FC.VTX_CONFIG.vtx_table_available) {
powerMinMax = {min: 1, max: FC.VTX_CONFIG.vtx_table_powerlevels};
} else {
switch (vtxType) {
case VtxDeviceTypes.VTXDEV_UNSUPPORTED:
powerMinMax = {};
break;
case VtxDeviceTypes.VTXDEV_RTC6705:
powerMinMax = {min: 1, max: 3};
break;
case VtxDeviceTypes.VTXDEV_SMARTAUDIO:
powerMinMax = {min: 1, max: 4};
break;
case VtxDeviceTypes.VTXDEV_TRAMP:
powerMinMax = {min: 1, max: 5};
break;
case VtxDeviceTypes.VTXDEV_UNKNOWN:
default:
powerMinMax = {min: 0, max: 7};
}
}
return powerMinMax;
}
// Save and other button functions
$('a.save_file').click(function () {
save_json();
});
$('a.save_lua').click(function () {
save_lua();
});
$('a.load_file').click(function () {
load_json();
});
$('a.load_clipboard').click(function () {
load_clipboard_json();
});
$('a.save').click(function () {
if (!self.updating) {
save_vtx();
}
});
}
function save_lua() {
const suffix = 'lua';
const uid0 = FC.CONFIG.uid[0].toString(16).padStart(8, '0');
const uid1 = FC.CONFIG.uid[1].toString(16).padStart(8, '0');
const uid2 = FC.CONFIG.uid[2].toString(16).padStart(8, '0');
const filename = `${uid0}${uid1}${uid2}.${suffix}`;
const accepts = [{
description: `${suffix.toUpperCase()} files`, extensions: [suffix],
}];
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts}, function(entry) {
if (checkChromeRuntimeError()) {
return;
}
entry.createWriter(function (writer) {
writer.onerror = function(){
console.error('Failed to write VTX table lua file');
GUI.log(i18n.getMessage('vtxSavedFileKo'));
};
writer.onwriteend = function() {
dump_html_to_msp();
const vtxConfig = createVtxConfigInfo();
const text = createLuaTables(vtxConfig);
const data = new Blob([text], { type: "application/text" });
// we get here at the end of the truncate method, change to the new end
writer.onwriteend = function() {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'VtxTableLuaSave', text.length);
console.log('Write VTX table lua file end');
GUI.log(i18n.getMessage('vtxSavedFileOk'));
};
writer.write(data);
};
writer.truncate(0);
}, function (){
console.error('Failed to get VTX table lua file writer');
GUI.log(i18n.getMessage('vtxSavedFileKo'));
});
});
}
function save_json() {
const suggestedName = 'vtxtable';
const suffix = 'json';
const filename = generateFilename(suggestedName, suffix);
const accepts = [{
description: `${suffix.toUpperCase()} files`, extensions: [suffix],
}];
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts}, function(entry) {
if (checkChromeRuntimeError()) {
return;
}
if (!entry) {
console.log('No file selected');
return;
}
entry.createWriter(function (writer) {
writer.onerror = function(){
console.error('Failed to write VTX file');
GUI.log(i18n.getMessage('vtxSavedFileKo'));
};
writer.onwriteend = function() {
dump_html_to_msp();
const vtxConfig = createVtxConfigInfo();
const text = JSON.stringify(vtxConfig, null, 4);
const data = new Blob([text], { type: "application/json" });
// we get here at the end of the truncate method, change to the new end
writer.onwriteend = function() {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'VtxTableSave', text.length);
console.log(vtxConfig);
console.log('Write VTX file end');
GUI.log(i18n.getMessage('vtxSavedFileOk'));
};
writer.write(data);
};
writer.truncate(0);
}, function (){
console.error('Failed to get VTX file writer');
GUI.log(i18n.getMessage('vtxSavedFileKo'));
});
});
}
function load_json() {
const suffix = 'json';
const accepts = [{
description: `${suffix.toUpperCase()} files`, extensions: [suffix],
}];
chrome.fileSystem.chooseEntry({type: 'openFile', accepts}, function(entry) {
if (checkChromeRuntimeError()) {
return;
}
entry.file(function(file) {
const reader = new FileReader();
reader.onload = function(e) {
const text = e.target.result;
try {
const vtxConfig = JSON.parse(text);
validateVtxJson(
vtxConfig,
function() {
// JSON is valid
read_vtx_config_json(vtxConfig, load_html);
TABS.vtx.vtxTableSavePending = true;
self.analyticsChanges['VtxTableLoadFromClipboard'] = undefined;
self.analyticsChanges['VtxTableLoadFromFile'] = file.name;
console.log('Load VTX file end');
GUI.log(i18n.getMessage('vtxLoadFileOk'));
},
function() {
// JSON is NOT valid
console.error('VTX Config from file failed validation against schema');
GUI.log(i18n.getMessage('vtxLoadFileKo'));
},
);
} catch (err) {
console.error('Failed loading VTX file config');
GUI.log(i18n.getMessage('vtxLoadFileKo'));
}
};
reader.readAsText(file);
}, function() {
console.error('Failed to get VTX file reader');
GUI.log(i18n.getMessage('vtxLoadFileKo'));
});
});
}
function load_clipboard_json() {
try {
Clipboard.readText(
function(text) {
console.log('Pasted content: ', text);
const vtxConfig = JSON.parse(text);
validateVtxJson(
vtxConfig,
function() {
// JSON is valid
read_vtx_config_json(vtxConfig, load_html);
TABS.vtx.vtxTableSavePending = true;
self.analyticsChanges['VtxTableLoadFromFile'] = undefined;
self.analyticsChanges['VtxTableLoadFromClipboard'] = text.length;
console.log('Load VTX clipboard end');
GUI.log(i18n.getMessage('vtxLoadClipboardOk'));
},
function() {
// JSON is NOT valid
GUI.log(i18n.getMessage('vtxLoadClipboardKo'));
console.error('VTX Config from clipboard failed validation against schema');
},
);
}, function(err) {
GUI.log(i18n.getMessage('vtxLoadClipboardKo'));
console.error('Failed to read clipboard contents: ', err);
},
);
} catch (err) {
console.error(`Failed loading VTX file config: ${err}`);
GUI.log(i18n.getMessage('vtxLoadClipboardKo'));
}
}
// Save all the values from the tab to MSP
function save_vtx() {
self.updating = true;
dump_html_to_msp();
// Start MSP saving
save_vtx_config();
analytics.sendSaveAndChangeEvents(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, self.analyticsChanges, 'vtx');
function save_vtx_config() {
MSP.send_message(MSPCodes.MSP_SET_VTX_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_VTX_CONFIG), false, save_vtx_powerlevels);
}
function save_vtx_powerlevels() {
// Simulation of static variable
if (typeof save_vtx_powerlevels.counter === 'undefined') {
save_vtx_powerlevels.counter = 0;
} else {
save_vtx_powerlevels.counter++;
}
if (save_vtx_powerlevels.counter < FC.VTX_CONFIG.vtx_table_powerlevels) {
FC.VTXTABLE_POWERLEVEL = Object.assign({}, TABS.vtx.VTXTABLE_POWERLEVEL_LIST[save_vtx_powerlevels.counter]);
MSP.send_message(MSPCodes.MSP_SET_VTXTABLE_POWERLEVEL, mspHelper.crunch(MSPCodes.MSP_SET_VTXTABLE_POWERLEVEL), false, save_vtx_powerlevels);
} else {
save_vtx_powerlevels.counter = undefined;
save_vtx_bands();
}
}
function save_vtx_bands() {
// Simulation of static variable
if (typeof save_vtx_bands.counter === 'undefined') {
save_vtx_bands.counter = 0;
} else {
save_vtx_bands.counter++;
}
if (save_vtx_bands.counter < FC.VTX_CONFIG.vtx_table_bands) {
FC.VTXTABLE_BAND = Object.assign({}, TABS.vtx.VTXTABLE_BAND_LIST[save_vtx_bands.counter]);
MSP.send_message(MSPCodes.MSP_SET_VTXTABLE_BAND, mspHelper.crunch(MSPCodes.MSP_SET_VTXTABLE_BAND), false, save_vtx_bands);
} else {
save_vtx_bands.counter = undefined;
save_to_eeprom();
}
}
function save_to_eeprom() {
MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, save_completed);
}
function save_completed() {
GUI.log(i18n.getMessage('configurationEepromSaved'));
TABS.vtx.vtxTableSavePending = false;
const oldText = $("#save_button").text();
$("#save_button").html(i18n.getMessage('vtxButtonSaved'));
setTimeout(function () {
$("#save_button").html(oldText);
}, 2000);
TABS.vtx.initialize();
}
}
function dump_html_to_msp() {
// General config
const frequencyEnabled = $("#vtx_frequency_channel").prop('checked');
if (frequencyEnabled) {
FC.VTX_CONFIG.vtx_frequency = parseInt($("#vtx_frequency").val());
FC.VTX_CONFIG.vtx_band = 0;
FC.VTX_CONFIG.vtx_channel = 0;
} else {
FC.VTX_CONFIG.vtx_band = parseInt($("#vtx_band").val());
FC.VTX_CONFIG.vtx_channel = parseInt($("#vtx_channel").val());
FC.VTX_CONFIG.vtx_frequency = 0;
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_42)) {
if (FC.VTX_CONFIG.vtx_band > 0 || FC.VTX_CONFIG.vtx_channel > 0) {
FC.VTX_CONFIG.vtx_frequency = (band - 1) * 8 + (channel - 1);
}
}
}
FC.VTX_CONFIG.vtx_power = parseInt($("#vtx_power").val());
FC.VTX_CONFIG.vtx_pit_mode = $("#vtx_pit_mode").prop('checked');
FC.VTX_CONFIG.vtx_low_power_disarm = parseInt($("#vtx_low_power_disarm").val());
FC.VTX_CONFIG.vtx_table_clear = true;
// Power levels
FC.VTX_CONFIG.vtx_table_powerlevels = parseInt($("#vtx_table_powerlevels").val());
TABS.vtx.VTXTABLE_POWERLEVEL_LIST = [];
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_powerlevels; i++) {
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1] = {};
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_number = i;
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_value = parseInt($(`#vtx_table_powerlevels_${i}`).val());
TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_label = $(`#vtx_table_powerlabels_${i}`).val();
}
// Bands and channels
FC.VTX_CONFIG.vtx_table_bands = parseInt($("#vtx_table_bands").val());
FC.VTX_CONFIG.vtx_table_channels = parseInt($("#vtx_table_channels").val());
TABS.vtx.VTXTABLE_BAND_LIST = [];
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_bands; i++) {
TABS.vtx.VTXTABLE_BAND_LIST[i - 1] = {};
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_number = i;
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_name = $(`#vtx_table_band_name_${i}`).val();
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_letter = $(`#vtx_table_band_letter_${i}`).val();
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_is_factory_band = TABS.vtx.vtxTableFactoryBandsSupported ? $(`#vtx_table_band_factory_${i}`).prop('checked') : false;
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies = [];
for (let j = 1; j <= FC.VTX_CONFIG.vtx_table_channels; j++) {
TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies.push(parseInt($(`#vtx_table_band_channel_${i}_${j}`).val()));
}
}
}
// Copies from the MSP data to the vtxInfo object (JSON)
function createVtxConfigInfo() {
const vtxConfig = {
description: "Betaflight VTX Config file",
version: "1.0",
};
vtxConfig.vtx_table = {};
vtxConfig.vtx_table.bands_list = [];
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_bands; i++) {
vtxConfig.vtx_table.bands_list[i - 1] = {};
vtxConfig.vtx_table.bands_list[i - 1].name = TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_name;
vtxConfig.vtx_table.bands_list[i - 1].letter = TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_letter;
vtxConfig.vtx_table.bands_list[i - 1].is_factory_band = TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_is_factory_band;
vtxConfig.vtx_table.bands_list[i - 1].frequencies = TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies;
}
vtxConfig.vtx_table.powerlevels_list = [];
for (let i = 1; i <= FC.VTX_CONFIG.vtx_table_powerlevels; i++) {
vtxConfig.vtx_table.powerlevels_list[i - 1] = {};
vtxConfig.vtx_table.powerlevels_list[i - 1].value = TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_value;
vtxConfig.vtx_table.powerlevels_list[i - 1].label = TABS.vtx.VTXTABLE_POWERLEVEL_LIST[i - 1].vtxtable_powerlevel_label;
}
return vtxConfig;
}
function createLuaTables(vtxConfig) {
let bandsString = "bandTable = { [0]=\"U\"";
let frequenciesString = "frequencyTable = {\n";
const bandsList = vtxConfig.vtx_table.bands_list;
for (let index = 0, len = bandsList.length; index < len; ++index) {
bandsString += `, "${bandsList[index].letter}"`;
frequenciesString += " { ";
for (let i = 0, l = bandsList[index].frequencies.length; i < l; ++i) {
frequenciesString += `${bandsList[index].frequencies[i]}, `;
}
frequenciesString += "},\n";
}
bandsString += " },\n";
frequenciesString += " },\n";
const freqBandsString = `frequenciesPerBand = ${bandsList[1].frequencies.length},\n`;
const powerList = vtxConfig.vtx_table.powerlevels_list;
let powersString = "powerTable = { ";
for (let index = 0, len = powerList.length; index < len; ++index) {
powersString += `[${(index + 1)}]="${powerList[index].label}", `;
}
powersString += "},\n";
return `return {\n ${frequenciesString} ${freqBandsString} ${bandsString} ${powersString}}`;
}
};
TABS.vtx.cleanup = function (callback) {
// Add here things that need to be cleaned or closed before leaving the tab
this.vtxTableSavePending = false;
this.VTXTABLE_BAND_LIST = [];
this.VTXTABLE_POWERLEVEL_LIST = [];
GUI.interval_remove(this._DEVICE_STATUS_UPDATE_INTERVAL_NAME);
if (callback) {
callback();
}
};