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": "
- $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 ",
+ "description": "Message in the GUI log when the VTX Config file is saved"
+ },
+ "vtxSavedFileKo": {
+ "message": " while saving the VTX Config file",
+ "description": "Message in the GUI log when the VTX Config file is saved"
+ },
+ "vtxLoadFileOk": {
+ "message": "VTX Config file ",
+ "description": "Message in the GUI log when the VTX Config file is loaded"
+ },
+ "vtxLoadFileKo": {
+ "message": " 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 @@