diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f38f80d8..7efa5fbd 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -102,7 +102,9 @@ "tabLogging": { "message": "Logging" }, - + "tabDataflash": { + "message": "Dataflash" + }, "tabAdjustments": { "message": "Adjustments" }, @@ -843,6 +845,13 @@ "message": "Automatically loaded previous log file: $1" }, + "dataflashButtonSaveFile": { + "message": "Save flash to file..." + }, + "dataflashButtonErase": { + "message": "Erase flash" + }, + "firmwareFlasherReleaseSummaryHead": { "message": "Release info" }, diff --git a/js/data_storage.js b/js/data_storage.js index 7d3f4bcb..1c4e1fbb 100755 --- a/js/data_storage.js +++ b/js/data_storage.js @@ -149,3 +149,8 @@ var MISC = { vbatmaxcellvoltage: 0, vbatwarningcellvoltage: 0 }; + +var DATAFLASH = { + sectors: 0, + totalSize: 0 +}; \ No newline at end of file diff --git a/js/gui.js b/js/gui.js index f6007a9b..89fab7d7 100644 --- a/js/gui.js +++ b/js/gui.js @@ -25,6 +25,7 @@ var GUI_control = function () { 'gps', 'led_strip', 'logging', + 'dataflash', 'modes', 'motors', 'pid_tuning', diff --git a/js/msp.js b/js/msp.js index 309dc55c..bb03ecd5 100644 --- a/js/msp.js +++ b/js/msp.js @@ -22,6 +22,9 @@ var MSP_codes = { MSP_SONAR: 58, MSP_PID_CONTROLLER: 59, MSP_SET_PID_CONTROLLER: 60, + MSP_DATAFLASH_SUMMARY: 70, + MSP_DATAFLASH_READ: 71, + MSP_DATAFLASH_ERASE: 72, // Multiwii MSP commands MSP_IDENT: 100, @@ -666,8 +669,16 @@ var MSP = { case MSP_codes.MSP_SET_LED_STRIP_CONFIG: console.log('Led strip config saved'); break; - - + case MSP_codes.MSP_DATAFLASH_SUMMARY: + DATAFLASH.sectors = data.getUint32(0, 1); + DATAFLASH.totalSize = data.getUint32(4, 1); + break; + case MSP_codes.MSP_DATAFLASH_READ: + // No-op, let callback handle it + break; + case MSP_codes.MSP_DATAFLASH_ERASE: + console.log("Data flash erased"); + break; case MSP_codes.MSP_SET_MODE_RANGE: console.log('Mode range saved'); break; @@ -793,6 +804,9 @@ var MSP = { } }; +/** + * Encode the request body for the MSP request with the given code and return it as an array of bytes. + */ MSP.crunch = function (code) { var buffer = []; @@ -950,6 +964,27 @@ MSP.crunch = function (code) { return buffer; }; +/** + * Send a request to read a block of data from the dataflash at the given address and pass that address and a dataview + * of the returned data to the given callback (or null for the data if an error occured). + */ +MSP.dataflashRead = function(address, onDataCallback) { + MSP.send_message(MSP_codes.MSP_DATAFLASH_READ, [address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF, (address >> 24) & 0xFF], + false, function(response) { + var chunkAddress = response.data.getUint32(0, 1); + + // Verify that the address of the memory returned matches what the caller asked for + if (chunkAddress == address) { + /* Strip that address off the front of the reply and deliver it separately so the caller doesn't have to + * figure out the reply format: + */ + onDataCallback(address, new DataView(response.data.buffer, response.data.byteOffset + 4, response.data.buffer.byteLength - 4)); + } else { + // Report error + onDataCallback(address, null); + } + }); +} ; MSP.sendModeRanges = function(onCompleteCallback) { var nextFunction = send_next_mode_range; diff --git a/main.html b/main.html index f6ace554..79bf742a 100755 --- a/main.html +++ b/main.html @@ -22,6 +22,7 @@ + @@ -68,6 +69,7 @@ + @@ -146,6 +148,7 @@
  • +
  • diff --git a/main.js b/main.js index 374f4cdc..fa3d54b6 100755 --- a/main.js +++ b/main.js @@ -153,6 +153,9 @@ $(document).ready(function () { case 'logging': TABS.logging.initialize(content_ready); break; + case 'dataflash': + TABS.dataflash.initialize(content_ready); + break; case 'cli': TABS.cli.initialize(content_ready); break; diff --git a/tabs/dataflash.css b/tabs/dataflash.css new file mode 100644 index 00000000..669151b8 --- /dev/null +++ b/tabs/dataflash.css @@ -0,0 +1,81 @@ +.tab-dataflash { +} +.tab-dataflash .dataflash-info dd{ + +} + +.tab-dataflash .note { + padding: 5px; + border: 1px dashed silver; +} +.tab-dataflash .properties { + margin-top: 10px; +} +.tab-dataflash .dataflash-info { + overflow:hidden; +} +.tab-dataflash .dataflash-info dt { + float: left; + width: 12em; + height: 20px; + line-height: 20px; + + font-weight: bold; +} +.tab-dataflash .dataflash-info dd { + display: block; + height: 20px; + line-height: 20px; +} +.tab-dataflash .speed { + margin-top: 5px; + width: 80px; + + border: 1px solid silver; +} +.tab-dataflash .info { + margin-top: 10px; +} +.tab-dataflash .info dt { + float: left; + width: 120px; + height: 20px; + line-height: 20px; + + font-weight: bold; +} +.tab-dataflash .info dd { + display: block; + margin-left: 130px; + height: 20px; + line-height: 20px; +} +.tab-dataflash .buttons { + width: calc(100% - 20px); + + position: absolute; + bottom: 10px; +} +.tab-dataflash .buttons a { + display: block; + float: right; + + margin-left: 10px; + + height: 28px; + line-height: 28px; + + padding: 0 15px 0 15px; + + text-align: center; + font-weight: bold; + + border: 1px solid silver; + background-color: #ececec; +} +.tab-dataflash .buttons a:hover { + background-color: #dedcdc; +} +.tab-dataflash .buttons .back { + display: none; +} \ No newline at end of file diff --git a/tabs/dataflash.html b/tabs/dataflash.html new file mode 100644 index 00000000..8b227371 --- /dev/null +++ b/tabs/dataflash.html @@ -0,0 +1,14 @@ +
    +

    Dataflash

    +
    +
    Capacity (bytes)
    +
    +
    Capacity (sectors)
    +
    +
    + +
    + + +
    +
    \ No newline at end of file diff --git a/tabs/dataflash.js b/tabs/dataflash.js new file mode 100644 index 00000000..38d846d3 --- /dev/null +++ b/tabs/dataflash.js @@ -0,0 +1,130 @@ +'use strict'; + +TABS.dataflash = {}; +TABS.dataflash.initialize = function (callback) { + var self = this; + + if (GUI.active_tab != 'dataflash') { + GUI.active_tab = 'dataflash'; + googleAnalytics.sendAppView('dataflash'); + } + + var requested_properties = [], + samples = 0, + requests = 0, + log_buffer = []; + + if (CONFIGURATOR.connectionValid) { + MSP.send_message(MSP_codes.MSP_DATAFLASH_SUMMARY, false, false, function() { + $('#content').load("./tabs/dataflash.html", process_html); + }); + } + + function process_html() { + // translate to user-selected language + localize(); + + $(".tab-dataflash .dataflash-capacity").text(DATAFLASH.totalSize); + $(".tab-dataflash .dataflash-sectors").text(DATAFLASH.sectors); + + // UI hooks + $('.tab-dataflash a.erase_flash').click(erase_flash); + + $('.tab-dataflash a.save_to_file').click(stream_flash_to_file); + + if (callback) callback(); + } + + // IO related methods + function zeroPad(value, width) { + value = "" + value; + + while (value.length < width) { + value = "0" + value; + } + + return value; + } + + function stream_flash_to_file() { + if (GUI.connected_to) { + prepare_file(function(fileWriter) { + var + nextAddress = 0; + + function onChunkRead(chunkAddress, chunkDataView) { + // If we didn't get a zero-byte chunk (indicating end-of-file), request more + if (chunkDataView.byteLength > 0) { + var blob = new Blob([chunkDataView]); + + fileWriter.write(blob); + + nextAddress += chunkDataView.byteLength; + MSP.dataflashRead(nextAddress, onChunkRead); + } + } + + MSP.dataflashRead(nextAddress, onChunkRead); + }); + } + } + + function prepare_file(onComplete) { + var + date = new Date(), + filename = 'blackbox_log_' + date.getFullYear() + '-' + zeroPad(date.getMonth() + 1, 2) + '-' + + zeroPad(date.getDate(), 2) + '_' + zeroPad(date.getHours(), 2) + zeroPad(date.getMinutes(), 2) + + zeroPad(date.getSeconds(), 2); + + chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, + accepts: [{extensions: ['TXT']}]}, function(fileEntry) { + if (!fileEntry) { + console.log('No file selected'); + return; + } + + // echo/console log path specified + chrome.fileSystem.getDisplayPath(fileEntry, function(path) { + console.log('Dataflash dump file path: ' + path); + }); + + prepare_writer(fileEntry, onComplete); + }); + } + + function prepare_writer(fileEntry, onComplete) { + fileEntry.createWriter(function (fileWriter) { + fileWriter.onerror = function (e) { + console.error(e); + + // stop logging if the procedure was/is still running + }; + + fileWriter.onwriteend = function () { + }; + + onComplete(fileWriter); + }, function (e) { + // File is not readable or does not exist! + console.error(e); + }); + } + + function erase_flash() { + /* var dialog = $("lol"); + + $("body").append(dialog); + + dialog[0].showModal(); + + TODO modal dialog to confirm erase */ + + MSP.send_message(MSP_codes.MSP_DATAFLASH_ERASE, false, false, function(data) { + + }); + } +}; + +TABS.dataflash.cleanup = function (callback) { + if (callback) callback(); +}; \ No newline at end of file