'use strict'; var sdcardTimer; TABS.onboard_logging = { blockSize: 128, BLOCK_SIZE: 4096, VCP_BLOCK_SIZE_3_0: 512, VCP_BLOCK_SIZE: 4096 }; TABS.onboard_logging.initialize = function (callback) { var self = this, saveCancelled, eraseCancelled; if (GUI.active_tab !== 'onboard_logging') { GUI.active_tab = 'onboard_logging'; } if (CONFIGURATOR.connectionValid) { MSP.send_message(MSPCodes.MSP_FEATURE_CONFIG, false, false, function() { MSP.send_message(MSPCodes.MSP_DATAFLASH_SUMMARY, false, false, function() { MSP.send_message(MSPCodes.MSP_SDCARD_SUMMARY, false, false, function() { MSP.send_message(MSPCodes.MSP_BLACKBOX_CONFIG, false, false, function() { MSP.send_message(MSPCodes.MSP_ADVANCED_CONFIG, false, false, function() { MSP.send_message(MSPCodes.MSP_NAME, false, false, load_html); }); }); }); }); }); } function gcd(a, b) { if (b === 0) return a; return gcd(b, a % b); } function save_to_eeprom() { MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, reboot); } function reboot() { GUI.log(chrome.i18n.getMessage('configurationEepromSaved')); GUI.tab_switch_cleanup(function() { MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false, reinitialize); }); } function reinitialize() { GUI.log(chrome.i18n.getMessage('deviceRebooting')); if (BOARD.find_board_definition(CONFIG.boardIdentifier).vcp) { // VCP-based flight controls may crash old drivers, we catch and reconnect $('a.connect').click(); GUI.timeout_add('start_connection',function start_connection() { $('a.connect').click(); },2000); } else { GUI.timeout_add('waiting_for_bootup', function waiting_for_bootup() { MSP.send_message(MSPCodes.MSP_STATUS, false, false, function() { GUI.log(chrome.i18n.getMessage('deviceReady')); TABS.onboard_logging.initialize(false, $('#content').scrollTop()); }); },1500); // 1500 ms seems to be just the right amount of delay to prevent data request timeouts } } function load_html() { $('#content').load("./tabs/onboard_logging.html", function() { // translate to user-selected language localize(); var dataflashPresent = DATAFLASH.totalSize > 0, blackboxSupport; /* * Pre-1.11.0 firmware supported DATAFLASH API (on targets with SPI flash) but not the BLACKBOX config API. * * The best we can do on those targets is check the BLACKBOX feature bit to identify support for Blackbox instead. */ if ((BLACKBOX.supported || DATAFLASH.supported) && (semver.gte(CONFIG.apiVersion, "1.33.0") || FEATURE_CONFIG.features.isEnabled('BLACKBOX'))) { blackboxSupport = 'yes'; } else { blackboxSupport = 'no'; } $(".tab-onboard_logging") .addClass("serial-supported") .toggleClass("dataflash-supported", DATAFLASH.supported) .toggleClass("dataflash-present", dataflashPresent) .toggleClass("sdcard-supported", SDCARD.supported) .toggleClass("blackbox-config-supported", BLACKBOX.supported) .toggleClass("blackbox-supported", blackboxSupport === 'yes') .toggleClass("blackbox-maybe-supported", blackboxSupport === 'maybe') .toggleClass("blackbox-unsupported", blackboxSupport === 'no'); if (dataflashPresent) { // UI hooks $('.tab-onboard_logging a.erase-flash').click(ask_to_erase_flash); $('.tab-onboard_logging a.erase-flash-confirm').click(flash_erase); $('.tab-onboard_logging a.erase-flash-cancel').click(flash_erase_cancel); $('.tab-onboard_logging a.save-flash').click(flash_save_begin); $('.tab-onboard_logging a.save-flash-cancel').click(flash_save_cancel); $('.tab-onboard_logging a.save-flash-dismiss').click(dismiss_saving_dialog); } var deviceSelect = $(".blackboxDevice select"); var loggingRatesSelect = $(".blackboxRate select"); if (BLACKBOX.supported) { $(".tab-onboard_logging a.save-settings").click(function() { if (semver.gte(CONFIG.apiVersion, "1.36.0")) { BLACKBOX.blackboxPDenom = parseInt(loggingRatesSelect.val(), 10); } else { var rate = loggingRatesSelect.val().split('/'); BLACKBOX.blackboxRateNum = parseInt(rate[0], 10); BLACKBOX.blackboxRateDenom = parseInt(rate[1], 10); } BLACKBOX.blackboxDevice = parseInt(deviceSelect.val(), 10); MSP.send_message(MSPCodes.MSP_SET_BLACKBOX_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_BLACKBOX_CONFIG), false, save_to_eeprom); }); } populateLoggingRates(loggingRatesSelect); populateDevices(deviceSelect); deviceSelect.change(function() { if ($(this).val() === "0") { $("div.blackboxRate").hide(); } else { $("div.blackboxRate").show(); } }).change(); update_html(); GUI.content_ready(callback); }); } function populateDevices(deviceSelect) { deviceSelect.empty(); if (semver.gte(CONFIG.apiVersion, "1.33.0")) { deviceSelect.append(''); if (DATAFLASH.supported) { deviceSelect.append(''); } if (SDCARD.supported) { deviceSelect.append(''); } deviceSelect.append(''); } else { deviceSelect.append(''); if (DATAFLASH.ready) { deviceSelect.append(''); } if (SDCARD.supported) { deviceSelect.append(''); } } deviceSelect.val(BLACKBOX.blackboxDevice); } function populateLoggingRates(loggingRatesSelect) { // Offer a reasonable choice of logging rates (if people want weird steps they can use CLI) var loggingRates = []; var pidRate = 8000 / PID_ADVANCED_CONFIG.gyro_sync_denom / PID_ADVANCED_CONFIG.pid_process_denom; if (semver.gte(CONFIG.apiVersion, "1.36.0")) { loggingRates = [ {text: "Disabled", hz: 0, p_denom: 0}, {text: "500 Hz", hz: 500, p_denom: 16}, {text: "1 kHz", hz: 1000, p_denom: 32}, {text: "1.5 kHz", hz: 1500, p_denom: 48}, {text: "2 kHz", hz: 2000, p_denom: 64}, {text: "4 kHz", hz: 4000, p_denom: 128}, {text: "8 kHz", hz: 8000, p_denom: 256}, {text: "16 kHz", hz: 16000, p_denom: 512}, {text: "32 kHz", hz: 32000, p_denom: 1024}, ]; $.each(loggingRates, function(index, item) { if (pidRate >= item.hz || item.hz == 0) { loggingRatesSelect.append(new Option(item.text, item.p_denom)); } }); loggingRatesSelect.val(BLACKBOX.blackboxPDenom); } else { loggingRates = [ {num: 1, denom: 1}, {num: 1, denom: 2}, {num: 1, denom: 3}, {num: 1, denom: 4}, {num: 1, denom: 5}, {num: 1, denom: 6}, {num: 1, denom: 7}, {num: 1, denom: 8}, {num: 1, denom: 16}, {num: 1, denom: 32} ]; for (var i = 0; i < loggingRates.length; i++) { var loggingRate = Math.round(pidRate / loggingRates[i].denom); var loggingRateUnit = " Hz"; if (loggingRate !== Infinity) { if (gcd(loggingRate, 1000) === 1000) { loggingRate /= 1000; loggingRateUnit = " KHz"; } } loggingRatesSelect.append(''); } loggingRatesSelect.val(BLACKBOX.blackboxRateNum + '/' + BLACKBOX.blackboxRateDenom); } } function formatFilesizeKilobytes(kilobytes) { if (kilobytes < 1024) { return Math.round(kilobytes) + "kB"; } var megabytes = kilobytes / 1024, gigabytes; if (megabytes < 900) { return megabytes.toFixed(1) + "MB"; } else { gigabytes = megabytes / 1024; return gigabytes.toFixed(1) + "GB"; } } function formatFilesizeBytes(bytes) { if (bytes < 1024) { return bytes + "B"; } return formatFilesizeKilobytes(bytes / 1024); } function update_bar_width(bar, value, total, label, valuesAreKilobytes) { if (value > 0) { bar.css({ width: (value / total * 100) + "%", display: 'block' }); $("div", bar).text((label ? label + " " : "") + (valuesAreKilobytes ? formatFilesizeKilobytes(value) : formatFilesizeBytes(value))); } else { bar.css({ display: 'none' }); } } function update_html() { update_bar_width($(".tab-onboard_logging .dataflash-used"), DATAFLASH.usedSize, DATAFLASH.totalSize, "Used space", false); update_bar_width($(".tab-onboard_logging .dataflash-free"), DATAFLASH.totalSize - DATAFLASH.usedSize, DATAFLASH.totalSize, "Free space", false); update_bar_width($(".tab-onboard_logging .sdcard-other"), SDCARD.totalSizeKB - SDCARD.freeSizeKB, SDCARD.totalSizeKB, "Unavailable space", true); update_bar_width($(".tab-onboard_logging .sdcard-free"), SDCARD.freeSizeKB, SDCARD.totalSizeKB, "Free space for logs", true); $(".btn a.erase-flash, .btn a.save-flash").toggleClass("disabled", DATAFLASH.usedSize === 0); $(".tab-onboard_logging") .toggleClass("sdcard-error", SDCARD.state === MSP.SDCARD_STATE_FATAL) .toggleClass("sdcard-initializing", SDCARD.state === MSP.SDCARD_STATE_CARD_INIT || SDCARD.state === MSP.SDCARD_STATE_FS_INIT) .toggleClass("sdcard-ready", SDCARD.state === MSP.SDCARD_STATE_READY); switch (SDCARD.state) { case MSP.SDCARD_STATE_NOT_PRESENT: $(".sdcard-status").text("No card inserted"); break; case MSP.SDCARD_STATE_FATAL: $(".sdcard-status").html("Fatal error
Reboot to retry"); break; case MSP.SDCARD_STATE_READY: $(".sdcard-status").text("Card ready"); break; case MSP.SDCARD_STATE_CARD_INIT: $(".sdcard-status").text("Card starting..."); break; case MSP.SDCARD_STATE_FS_INIT: $(".sdcard-status").text("Filesystem starting..."); break; default: $(".sdcard-status").text("Unknown state " + SDCARD.state); } if (SDCARD.supported && !sdcardTimer) { // Poll for changes in SD card status sdcardTimer = setTimeout(function() { sdcardTimer = false; if (CONFIGURATOR.connectionValid) { MSP.send_message(MSPCodes.MSP_SDCARD_SUMMARY, false, false, function() { update_html(); }); } }, 2000); } } // IO related methods function flash_save_cancel() { saveCancelled = true; } function show_saving_dialog() { $(".dataflash-saving progress").attr("value", 0); saveCancelled = false; $(".dataflash-saving").removeClass("done"); $(".dataflash-saving")[0].showModal(); } function dismiss_saving_dialog() { $(".dataflash-saving")[0].close(); } function mark_saving_dialog_done(startTime, totalBytes, totalBytesCompressed) { var totalTime = (new Date().getTime() - startTime) / 1000; console.log('Received ' + totalBytes + ' bytes in ' + totalTime.toFixed(2) + 's (' + (totalBytes / totalTime / 1024).toFixed(2) + 'kB / s) with block size ' + self.blockSize + '.'); if (totalBytesCompressed) { console.log('Compressed into', totalBytesCompressed, 'bytes with mean compression factor of', totalBytes / totalBytesCompressed); } $(".dataflash-saving").addClass("done"); } function flash_update_summary(onDone) { MSP.send_message(MSPCodes.MSP_DATAFLASH_SUMMARY, false, false, function() { update_html(); if (onDone) { onDone(); } }); } function flash_save_begin() { if (GUI.connected_to) { if (BOARD.find_board_definition(CONFIG.boardIdentifier).vcp) { if (semver.gte(CONFIG.apiVersion, "1.31.0")) { self.blockSize = self.VCP_BLOCK_SIZE; } else { self.blockSize = self.VCP_BLOCK_SIZE_3_0; } } else { self.blockSize = self.BLOCK_SIZE; } // Begin by refreshing the occupied size in case it changed while the tab was open flash_update_summary(function() { var maxBytes = DATAFLASH.usedSize; prepare_file(function(fileWriter) { var nextAddress = 0; var totalBytesCompressed = 0; show_saving_dialog(); function onChunkRead(chunkAddress, chunkDataView, bytesCompressed) { if (chunkDataView !== null) { // Did we receive any data? if (chunkDataView.byteLength > 0) { nextAddress += chunkDataView.byteLength; totalBytesCompressed += bytesCompressed; $(".dataflash-saving progress").attr("value", nextAddress / maxBytes * 100); var blob = new Blob([chunkDataView]); fileWriter.onwriteend = function(e) { if (saveCancelled || nextAddress >= maxBytes) { if (saveCancelled) { dismiss_saving_dialog(); } else { mark_saving_dialog_done(startTime, nextAddress, totalBytesCompressed); } } else { mspHelper.dataflashRead(nextAddress, self.blockSize, onChunkRead); } }; fileWriter.write(blob); } else { // A zero-byte block indicates end-of-file, so we're done mark_saving_dialog_done(startTime, nextAddress, totalBytesCompressed); } } else { // There was an error with the received block (address didn't match the one we asked for), retry mspHelper.dataflashRead(nextAddress, self.blockSize, onChunkRead); } } var startTime = new Date().getTime(); // Fetch the initial block mspHelper.dataflashRead(nextAddress, self.blockSize, onChunkRead); }); }); } } function prepare_file(onComplete) { var prefix = 'BLACKBOX_LOG'; var suffix = 'BBL'; var filename = generateFilename(prefix, suffix); chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts: [{extensions: [suffix]}]}, function(fileEntry) { var error = chrome.runtime.lastError; if (error) { console.error(error.message); if (error.message !== "User cancelled") { GUI.log(chrome.i18n.getMessage('dataflashFileWriteFailed')); } return; } // echo/console log path specified chrome.fileSystem.getDisplayPath(fileEntry, function(path) { console.log('Dataflash dump file path: ' + path); }); fileEntry.createWriter(function (fileWriter) { fileWriter.onerror = function (e) { console.error(e); // stop logging if the procedure was/is still running }; onComplete(fileWriter); }, function (e) { // File is not readable or does not exist! console.error(e); GUI.log(chrome.i18n.getMessage('dataflashFileWriteFailed')); }); }); } function ask_to_erase_flash() { eraseCancelled = false; $(".dataflash-confirm-erase").removeClass('erasing'); $(".dataflash-confirm-erase")[0].showModal(); } function poll_for_erase_completion() { flash_update_summary(function() { if (CONFIGURATOR.connectionValid && !eraseCancelled) { if (DATAFLASH.ready) { $(".dataflash-confirm-erase")[0].close(); } else { setTimeout(poll_for_erase_completion, 500); } } }); } function flash_erase() { $(".dataflash-confirm-erase").addClass('erasing'); MSP.send_message(MSPCodes.MSP_DATAFLASH_ERASE, false, false, poll_for_erase_completion); } function flash_erase_cancel() { eraseCancelled = true; $(".dataflash-confirm-erase")[0].close(); } }; TABS.onboard_logging.cleanup = function (callback) { if (sdcardTimer) { clearTimeout(sdcardTimer); sdcardTimer = false; } if (callback) { callback(); } };