1
0
Fork 0
mirror of https://github.com/iNavFlight/inav-configurator.git synced 2025-07-16 04:45:18 +03:00
inav-configurator/tabs/logging.js
Alberto García Hierro 0e01133fc9 Make tab loading async safe and add a small animation
Tab loading was relying on replacing the contents of '#content'
with the loading indicator, then replacing it with the loading tab
content and blocking rendering until the tab was ready by not
yielding. This is problematic for tabs that load some data
asynchronously, like PID and OSD.

Instead, put the loading indicator in front of everything else
and load new content inside '#content' next to the loading indicator
(but without showing it). Once the content and data are fully loaded
we fade out the loading indicator with a 0.4s long animation and
then we remove. This works for both synchronous and asynchonous
loading of tabs.
2020-01-21 18:19:06 +00:00

314 lines
12 KiB
JavaScript

'use strict';
TABS.logging = {};
TABS.logging.initialize = function (callback) {
var self = this;
if (GUI.active_tab != 'logging') {
GUI.active_tab = 'logging';
googleAnalytics.sendAppView('Logging');
}
var requested_properties = [],
samples = 0,
requests = 0,
log_buffer = [];
if (CONFIGURATOR.connectionValid) {
var get_motor_data = function () {
MSP.send_message(MSPCodes.MSP_MOTOR, false, false, load_html);
}
var load_html = function () {
GUI.load("./tabs/logging.html", process_html);
}
MSP.send_message(MSPCodes.MSP_RC, false, false, get_motor_data);
}
function process_html() {
// translate to user-selected language
localize();
// UI hooks
$('a.log_file').click(prepare_file);
$('a.logging').click(function () {
if (GUI.connected_to) {
if (fileEntry != null) {
var clicks = $(this).data('clicks');
if (!clicks) {
// reset some variables before start
samples = 0;
requests = 0;
log_buffer = [];
requested_properties = [];
$('.properties input:checked').each(function () {
requested_properties.push($(this).prop('name'));
});
if (requested_properties.length) {
// print header for the csv file
print_head();
var log_data_poll = function () {
if (requests) {
// save current data (only after everything is initialized)
crunch_data();
}
// request new
for (var i = 0; i < requested_properties.length; i++, requests++) {
MSP.send_message(MSPCodes[requested_properties[i]]);
}
}
helper.interval.add('log_data_poll', log_data_poll, parseInt($('select.speed').val()), true); // refresh rate goes here
helper.interval.add('write_data', function write_data() {
if (log_buffer.length) { // only execute when there is actual data to write
if (fileWriter.readyState == 0 || fileWriter.readyState == 2) {
append_to_file(log_buffer.join('\n'));
$('.samples').text(samples += log_buffer.length);
log_buffer = [];
} else {
console.log('IO having trouble keeping up with the data flow');
}
}
}, 1000);
$('.speed').prop('disabled', true);
$(this).text(chrome.i18n.getMessage('loggingStop'));
$(this).data("clicks", !clicks);
} else {
GUI.log(chrome.i18n.getMessage('loggingErrorOneProperty'));
}
} else {
helper.interval.killAll(['global_data_refresh', 'msp-load-update']);
helper.mspBalancedInterval.flush();
$('.speed').prop('disabled', false);
$(this).text(chrome.i18n.getMessage('loggingStart'));
$(this).data("clicks", !clicks);
}
} else {
GUI.log(chrome.i18n.getMessage('loggingErrorLogFile'));
}
} else {
GUI.log(chrome.i18n.getMessage('loggingErrorNotConnected'));
}
});
chrome.storage.local.get('logging_file_entry', function (result) {
if (result.logging_file_entry) {
chrome.fileSystem.restoreEntry(result.logging_file_entry, function (entry) {
fileEntry = entry;
prepare_writer(true);
});
}
});
GUI.content_ready(callback);
}
function print_head() {
var head = "timestamp";
for (var i = 0; i < requested_properties.length; i++) {
switch (requested_properties[i]) {
case 'MSP_RAW_IMU':
head += ',' + 'gyroscopeX';
head += ',' + 'gyroscopeY';
head += ',' + 'gyroscopeZ';
head += ',' + 'accelerometerX';
head += ',' + 'accelerometerY';
head += ',' + 'accelerometerZ';
head += ',' + 'magnetometerX';
head += ',' + 'magnetometerY';
head += ',' + 'magnetometerZ';
break;
case 'MSP_ATTITUDE':
head += ',' + 'kinematicsX';
head += ',' + 'kinematicsY';
head += ',' + 'kinematicsZ';
break;
case 'MSP_ALTITUDE':
head += ',' + 'altitude';
break;
case 'MSP_RAW_GPS':
head += ',' + 'gpsFix';
head += ',' + 'gpsNumSat';
head += ',' + 'gpsLat';
head += ',' + 'gpsLon';
head += ',' + 'gpsAlt';
head += ',' + 'gpsSpeed';
head += ',' + 'gpsGroundCourse';
break;
case 'MSP_ANALOG':
head += ',' + 'voltage';
head += ',' + 'amperage';
head += ',' + 'mAhdrawn';
head += ',' + 'rssi';
break;
case 'MSP_RC':
for (var chan = 0; chan < RC.active_channels; chan++) {
head += ',' + 'RC' + chan;
}
break;
case 'MSP_MOTOR':
for (var motor = 0; motor < MOTOR_DATA.length; motor++) {
head += ',' + 'Motor' + motor;
}
break;
case 'MSP_DEBUG':
for (var debug = 0; debug < SENSOR_DATA.debug.length; debug++) {
head += ',' + 'Debug' + debug;
}
break;
}
}
append_to_file(head);
}
function crunch_data() {
var sample = millitime();
for (var i = 0; i < requested_properties.length; i++) {
switch (requested_properties[i]) {
case 'MSP_RAW_IMU':
sample += ',' + SENSOR_DATA.gyroscope;
sample += ',' + SENSOR_DATA.accelerometer;
sample += ',' + SENSOR_DATA.magnetometer;
break;
case 'MSP_ATTITUDE':
sample += ',' + SENSOR_DATA.kinematics[0];
sample += ',' + SENSOR_DATA.kinematics[1];
sample += ',' + SENSOR_DATA.kinematics[2];
break;
case 'MSP_ALTITUDE':
sample += ',' + SENSOR_DATA.altitude;
break;
case 'MSP_RAW_GPS':
sample += ',' + GPS_DATA.fix;
sample += ',' + GPS_DATA.numSat;
sample += ',' + (GPS_DATA.lat / 10000000);
sample += ',' + (GPS_DATA.lon / 10000000);
sample += ',' + GPS_DATA.alt;
sample += ',' + GPS_DATA.speed;
sample += ',' + GPS_DATA.ground_course;
break;
case 'MSP_ANALOG':
sample += ',' + ANALOG.voltage;
sample += ',' + ANALOG.amperage;
sample += ',' + ANALOG.mAhdrawn;
sample += ',' + ANALOG.rssi;
break;
case 'MSP_RC':
for (var chan = 0; chan < RC.active_channels; chan++) {
sample += ',' + RC.channels[chan];
}
break;
case 'MSP_MOTOR':
sample += ',' + MOTOR_DATA;
break;
case 'MSP_DEBUG':
sample += ',' + SENSOR_DATA.debug;
break;
}
}
log_buffer.push(sample);
}
// IO related methods
var fileEntry = null,
fileWriter = null;
function prepare_file() {
// create or load the file
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: 'inav_data_log', accepts: [{extensions: ['csv']}]}, function(entry) {
if (!entry) {
console.log('No file selected');
return;
}
fileEntry = entry;
// echo/console log path specified
chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
console.log('Log file path: ' + path);
});
// change file entry from read only to read/write
chrome.fileSystem.getWritableEntry(fileEntry, function(fileEntryWritable) {
// check if file is writable
chrome.fileSystem.isWritableEntry(fileEntryWritable, function(isWritable) {
if (isWritable) {
fileEntry = fileEntryWritable;
// save entry for next use
chrome.storage.local.set({'logging_file_entry': chrome.fileSystem.retainEntry(fileEntry)});
// reset sample counter in UI
$('.samples').text(0);
prepare_writer();
} else {
console.log('File appears to be read only, sorry.');
}
});
});
});
}
function prepare_writer(retaining) {
fileEntry.createWriter(function (writer) {
fileWriter = writer;
fileWriter.onerror = function (e) {
console.error(e);
// stop logging if the procedure was/is still running
if ($('a.logging').data('clicks')) $('a.logging').click();
};
fileWriter.onwriteend = function () {
$('.size').text(bytesToSize(fileWriter.length));
};
if (retaining) {
chrome.fileSystem.getDisplayPath(fileEntry, function (path) {
GUI.log(chrome.i18n.getMessage('loggingAutomaticallyRetained', [path]));
});
}
// update log size in UI on fileWriter creation
$('.size').text(bytesToSize(fileWriter.length));
}, function (e) {
// File is not readable or does not exist!
console.error(e);
if (retaining) {
fileEntry = null;
}
});
}
function append_to_file(data) {
if (fileWriter.position < fileWriter.length) {
fileWriter.seek(fileWriter.length);
}
fileWriter.write(new Blob([data + '\n'], {type: 'text/plain'}));
}
};
TABS.logging.cleanup = function (callback) {
if (callback) callback();
};