diff --git a/locales/en/messages.json b/locales/en/messages.json index 7ed013d8..1e3817c9 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -4784,6 +4784,10 @@ "message": "Attention: You need to configure and save FIRST the VTX Table at the bottom before you can make use of the $t(vtxSelectedMode.message) fields.", "description": "Message to show when the VTX is not supported in the VTX tab" }, + "vtxMessageVerifyTable": { + "message": "Attention: The values of the VTX Table have been loaded, but not yet stored on the flight controller. You must verify and modify the values to be sure that they are valid and legal in your country and then press the $t(vtxButtonSave.message) button to store them on the flight controller.", + "description": "Message to show when the VTX Table has been loaded from a external source" + }, "vtxFrequencyChannel": { "message": "Enter frequency directly", "description": "Text of one of the fields of the VTX tab" @@ -4972,6 +4976,31 @@ "message": "This table represents all the frequencies that can be used for your VTX. You can have several bands and for each band you must configure:
- $t(vtxTableBandTitleName.message): Name that you want to assign to this band, like BOSCAM_A, FATSHARK or RACEBAND.
- $t(vtxTableBandTitleLetter.message): Short letter that references the band.
- $t(vtxTableBandTitleFactory.message): This indicates if it is a factory band. If enabled Betaflight sends to the VTX a band and channel number. The VTX will then use its built-in frequency table and the frequencies configured here are only to show the value in the OSD and other places. If it is not enabled, then Betaflight will send to the VTX the real frequency configured here.
- Frequencies: Frequencies for this band.

Remember that not all frequencies are legal at your country. You must put a value of zero to each frequency index that you are not allowed to use to disable it.", "description": "Help for the table of bands-channels that appears in the VTX tab" }, + + "vtxSavedFileOk": { + "message": "VTX Config file saved", + "description": "Message in the GUI log when the VTX Config file is saved" + }, + "vtxSavedFileKo": { + "message": "Error while saving the VTX Config file", + "description": "Message in the GUI log when the VTX Config file is saved" + }, + "vtxLoadFileOk": { + "message": "VTX Config file loaded", + "description": "Message in the GUI log when the VTX Config file is loaded" + }, + "vtxLoadFileKo": { + "message": "Error while loading the VTX Config file", + "description": "Message in the GUI log when the VTX Config file is loaded" + }, + "vtxButtonSaveFile": { + "message": "Save to file", + "description": "Save to file button in the VTX tab" + }, + "vtxButtonLoadFile": { + "message": "Load from file", + "description": "Load to file button in the VTX tab" + }, "vtxButtonSave": { "message": "Save", "description": "Save button in the VTX tab" diff --git a/src/js/msp/MSPHelper.js b/src/js/msp/MSPHelper.js index eab429a6..233c1a64 100644 --- a/src/js/msp/MSPHelper.js +++ b/src/js/msp/MSPHelper.js @@ -2102,8 +2102,12 @@ MspHelper.prototype.crunch = function(code) { buffer.push8(VTXTABLE_BAND.vtxtable_band_name.charCodeAt(i)); } - buffer.push8(VTXTABLE_BAND.vtxtable_band_letter.charCodeAt(0)) - .push8(VTXTABLE_BAND.vtxtable_band_is_factory_band ? 1 : 0); + if (VTXTABLE_BAND.vtxtable_band_letter != '') { + buffer.push8(VTXTABLE_BAND.vtxtable_band_letter.charCodeAt(0)) + } else { + buffer.push8(' '.charCodeAt(0)); + } + buffer.push8(VTXTABLE_BAND.vtxtable_band_is_factory_band ? 1 : 0); buffer.push8(VTXTABLE_BAND.vtxtable_band_frequencies.length); for (let i = 0; i < VTXTABLE_BAND.vtxtable_band_frequencies.length; i++) { diff --git a/src/js/tabs/vtx.js b/src/js/tabs/vtx.js index ccd520a4..1c378f1b 100644 --- a/src/js/tabs/vtx.js +++ b/src/js/tabs/vtx.js @@ -2,6 +2,7 @@ TABS.vtx = { supported: false, + vtxTableSavePending: false, MAX_POWERLEVEL_VALUES: 8, MAX_BAND_VALUES: 8, MAX_BAND_CHANNELS_VALUES: 8, @@ -91,6 +92,42 @@ TABS.vtx.initialize = function (callback) { } } + // Emulates the MSP read from a vtxConfig object (JSON) + function read_vtx_config_json(vtxConfig, vtxcallback_after_read) { + + // Bands and channels + VTX_CONFIG.vtx_table_bands = vtxConfig.vtx_table.bands_list.length; + + + let maxChannels = 0; + TABS.vtx.VTXTABLE_BAND_LIST = []; + for (let i = 1; i <= 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); + } + + VTX_CONFIG.vtx_table_channels = maxChannels; + + // Power levels + VTX_CONFIG.vtx_table_powerlevels = vtxConfig.vtx_table.powerlevels_list.length; + + TABS.vtx.VTXTABLE_POWERLEVEL_LIST = []; + for (let i = 1; i <= 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() { @@ -121,6 +158,7 @@ TABS.vtx.initialize = function (callback) { $(".vtx_not_supported").toggle(!vtxSupported); $(".vtx_table_available").toggle(vtxSupported && VTX_CONFIG.vtx_table_available); $(".vtx_table_not_configured").toggle(vtxTableNotConfigured); + $(".vtx_table_save_pending").toggle(TABS.vtx.vtxTableSavePending); // Insert actual values in the fields // Values of the selected mode @@ -429,64 +467,130 @@ TABS.vtx.initialize = function (callback) { return powerMinMax; } - // Save function + // Save and other button functions + $('a.save_file').click(function () { + save_json(); + }); + + $('a.load_file').click(function () { + load_json(); + }); + $('a.save').click(function () { save_vtx(); }); } + function save_json() { + let suggestedName = 'vtxtable'; + let suffix = 'json'; + + var filename = generateFilename(suggestedName, suffix); + + let accepts = [{ + description: suffix.toUpperCase() + ' files', extensions: [suffix], + }]; + + chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts: accepts}, function(entry) { + + if (chrome.runtime.lastError) { + console.error(chrome.runtime.lastError.message); + 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() { + + // we get here at the end of the truncate method, change to the new end + writer.onwriteend = function() { + console.log('Write VTX file end'); + GUI.log(i18n.getMessage('vtxSavedFileOk')); + } + + dump_html_to_msp(); + let vtxConfig = createVtxConfigInfo(); + let text = JSON.stringify(vtxConfig, null, 4); + let data = new Blob([text], { type: "application/json" }); + writer.write(data); + + }; + + writer.truncate(0); + + }, function (){ + console.error('Failed to get VTX file writer'); + GUI.log(i18n.getMessage('vtxSavedFileKo')); + }); + }); + } + + function load_json() { + + let suffix = 'json'; + + let accepts = [{ + description: suffix.toUpperCase() + ' files', extensions: [suffix], + }]; + + chrome.fileSystem.chooseEntry({type: 'openFile', accepts: accepts}, function(entry) { + + if (chrome.runtime.lastError) { + console.error(chrome.runtime.lastError.message); + return; + } + + if (!entry) { + console.log('No file selected'); + return; + } + + entry.file(function(file) { + + let reader = new FileReader(); + + reader.onload = function(e) { + + let text = e.target.result; + try { + let vtxConfig = JSON.parse(text); + read_vtx_config_json(vtxConfig, load_html); + + TABS.vtx.vtxTableSavePending = true; + + console.log('Load VTX file end'); + GUI.log(i18n.getMessage('vtxLoadFileOk')); + + } 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')); + }); + }); + } + // Save all the values from the tab to MSP function save_vtx() { - // General config - let frequencyEnabled = $("#vtx_frequency_channel").prop('checked'); - if (frequencyEnabled) { - VTX_CONFIG.vtx_frequency = parseInt($("#vtx_frequency").val()); - VTX_CONFIG.vtx_band = 0; - VTX_CONFIG.vtx_channel = 0; - } else { - VTX_CONFIG.vtx_band = parseInt($("#vtx_band").val()); - VTX_CONFIG.vtx_channel = parseInt($("#vtx_channel").val()); - VTX_CONFIG.vtx_frequency = 0; - if (semver.lt(CONFIG.apiVersion, "1.42.0")) { - if (VTX_CONFIG.vtx_band > 0 || VTX_CONFIG.vtx_channel > 0) { - VTX_CONFIG.vtx_frequency = (band - 1) * 8 + (channel - 1); - } - } - } - VTX_CONFIG.vtx_power = parseInt($("#vtx_power").val()); - VTX_CONFIG.vtx_pit_mode = $("#vtx_pit_mode").prop('checked'); - VTX_CONFIG.vtx_low_power_disarm = parseInt($("#vtx_low_power_disarm").val()); - VTX_CONFIG.vtx_table_clear = true; - - // Power levels - VTX_CONFIG.vtx_table_powerlevels = parseInt($("#vtx_table_powerlevels").val()); - - TABS.vtx.VTXTABLE_POWERLEVEL_LIST = []; - for (let i = 1; i <= 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 - VTX_CONFIG.vtx_table_bands = parseInt($("#vtx_table_bands").val()); - VTX_CONFIG.vtx_table_channels = parseInt($("#vtx_table_channels").val()); - TABS.vtx.VTXTABLE_BAND_LIST = []; - for (let i = 1; i <= 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 = $("#vtx_table_band_factory_" + i).prop('checked'); - - TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies = []; - for (let j = 1; j <= 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())); - } - } + dump_html_to_msp(); // Start MSP saving save_vtx_config(); @@ -540,6 +644,8 @@ TABS.vtx.initialize = function (callback) { function save_completed() { GUI.log(i18n.getMessage('configurationEepromSaved')); + TABS.vtx.vtxTableSavePending = false; + var oldText = $("#save_button").text(); $("#save_button").html(i18n.getMessage('vtxButtonSaved')); setTimeout(function () { @@ -549,11 +655,95 @@ TABS.vtx.initialize = function (callback) { TABS.vtx.initialize(); } } + + function dump_html_to_msp() { + + // General config + let frequencyEnabled = $("#vtx_frequency_channel").prop('checked'); + if (frequencyEnabled) { + VTX_CONFIG.vtx_frequency = parseInt($("#vtx_frequency").val()); + VTX_CONFIG.vtx_band = 0; + VTX_CONFIG.vtx_channel = 0; + } else { + VTX_CONFIG.vtx_band = parseInt($("#vtx_band").val()); + VTX_CONFIG.vtx_channel = parseInt($("#vtx_channel").val()); + VTX_CONFIG.vtx_frequency = 0; + if (semver.lt(CONFIG.apiVersion, "1.42.0")) { + if (VTX_CONFIG.vtx_band > 0 || VTX_CONFIG.vtx_channel > 0) { + VTX_CONFIG.vtx_frequency = (band - 1) * 8 + (channel - 1); + } + } + } + VTX_CONFIG.vtx_power = parseInt($("#vtx_power").val()); + VTX_CONFIG.vtx_pit_mode = $("#vtx_pit_mode").prop('checked'); + VTX_CONFIG.vtx_low_power_disarm = parseInt($("#vtx_low_power_disarm").val()); + VTX_CONFIG.vtx_table_clear = true; + + // Power levels + VTX_CONFIG.vtx_table_powerlevels = parseInt($("#vtx_table_powerlevels").val()); + + TABS.vtx.VTXTABLE_POWERLEVEL_LIST = []; + for (let i = 1; i <= 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 + VTX_CONFIG.vtx_table_bands = parseInt($("#vtx_table_bands").val()); + VTX_CONFIG.vtx_table_channels = parseInt($("#vtx_table_channels").val()); + TABS.vtx.VTXTABLE_BAND_LIST = []; + for (let i = 1; i <= 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 = $("#vtx_table_band_factory_" + i).prop('checked'); + + TABS.vtx.VTXTABLE_BAND_LIST[i - 1].vtxtable_band_frequencies = []; + for (let j = 1; j <= 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() { + + let vtxConfig = {}; + + vtxConfig.description = "Betaflight VTX Config file"; + vtxConfig.version = "1.0"; + + vtxConfig.vtx_table = {}; + + vtxConfig.vtx_table.bands_list = []; + for (let i = 1; i <= 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 <= 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; + } + }; 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 = []; diff --git a/src/tabs/vtx.html b/src/tabs/vtx.html index 05ad3977..de4c7c0c 100644 --- a/src/tabs/vtx.html +++ b/src/tabs/vtx.html @@ -190,6 +190,10 @@
+
+
+
+
@@ -269,6 +273,12 @@
+
+ +
+
+ +
@@ -282,7 +292,7 @@
- +
@@ -319,7 +329,7 @@
- +