mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-21 15:25:22 +03:00
Convert dataflash tab to a new Blackbox tab with SD card support
This commit is contained in:
parent
b0d0f40c41
commit
4e12db9c5e
13 changed files with 1089 additions and 591 deletions
|
@ -103,10 +103,10 @@
|
|||
"message": "CLI"
|
||||
},
|
||||
"tabLogging": {
|
||||
"message": "Logging"
|
||||
"message": "Tethered Logging"
|
||||
},
|
||||
"tabDataflash": {
|
||||
"message": "Dataflash"
|
||||
"tabOnboardLogging": {
|
||||
"message": "Blackbox"
|
||||
},
|
||||
"tabAdjustments": {
|
||||
"message": "Adjustments"
|
||||
|
@ -988,10 +988,30 @@
|
|||
"message": "Automatically loaded previous log file: <strong>$1</strong>"
|
||||
},
|
||||
|
||||
"dataflashNote": {
|
||||
"message": "Blackbox flight logs can be recorded to your flight controller's onboard dataflash chip."
|
||||
"blackboxNotSupported": {
|
||||
"message": "Your flight controller's firmware does not support Blackbox logging."
|
||||
},
|
||||
"dataflashNotSupportedNote": {
|
||||
"blackboxMaybeSupported": {
|
||||
"message": "Your flight controller's firmware is too old to support this tab, or the Blackbox feature is disabled on the Configuration tab."
|
||||
},
|
||||
"blackboxConfiguration": {
|
||||
"message": "Blackbox configuration"
|
||||
},
|
||||
"blackboxButtonSave": {
|
||||
"message": "Save and reboot"
|
||||
},
|
||||
|
||||
"serialLoggingSupportedNote": {
|
||||
"message": "You can log to an external logging device (such as an OpenLog or compatible clone) by using a serial port. Configure the port on the Ports tab."
|
||||
},
|
||||
"sdcardNote": {
|
||||
"message": "Flight logs can be recorded to your flight controller's onboard SD card slot."
|
||||
},
|
||||
|
||||
"dataflashNote": {
|
||||
"message": "Flight logs can be recorded to your flight controller's onboard dataflash chip."
|
||||
},
|
||||
"dataflashNotPresentNote": {
|
||||
"message": "Your flight controller does not have a compatible dataflash chip available."
|
||||
},
|
||||
"dataflashFirmwareUpgradeRequired": {
|
||||
|
|
83
images/icons/cf_icon_sdcard.svg
Normal file
83
images/icons/cf_icon_sdcard.svg
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="81.504448mm"
|
||||
height="109.72666mm"
|
||||
viewBox="0 0 288.79529 388.79528"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="cf_icon_sdcard.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#9e9e9e"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="62.562956"
|
||||
inkscape:cy="45.52524"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
fit-margin-top="1.1"
|
||||
fit-margin-left="1.1"
|
||||
fit-margin-right="1.1"
|
||||
fit-margin-bottom="1.1"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1076"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3336"
|
||||
originx="-168.45952"
|
||||
originy="-373.45953" />
|
||||
<sodipodi:guide
|
||||
position="241.54049,446.54052"
|
||||
orientation="0,1"
|
||||
id="guide3343" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-168.45951,-290.10742)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
transform="translate(-168.45951,-290.10742)">
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 172.85715,294.50506 240,0 40,50 0,330 -280,0 0,-230 10,0 0,-50 -10,0 z"
|
||||
id="path3341"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -174,7 +174,23 @@ var _3D = {
|
|||
|
||||
var DATAFLASH = {
|
||||
ready: false,
|
||||
supported: false,
|
||||
sectors: 0,
|
||||
totalSize: 0,
|
||||
usedSize: 0
|
||||
};
|
||||
|
||||
var SDCARD = {
|
||||
supported: false,
|
||||
state: 0,
|
||||
filesystemLastError: 0,
|
||||
freeSizeKB: 0,
|
||||
totalSizeKB: 0,
|
||||
};
|
||||
|
||||
var BLACKBOX = {
|
||||
supported: false,
|
||||
blackboxDevice: 0,
|
||||
blackboxRateNum: 1,
|
||||
blackboxRateDenom: 1
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ var GUI_control = function () {
|
|||
'gps',
|
||||
'led_strip',
|
||||
'logging',
|
||||
'dataflash',
|
||||
'onboard_logging',
|
||||
'modes',
|
||||
'motors',
|
||||
'pid_tuning',
|
||||
|
|
47
js/msp.js
47
js/msp.js
|
@ -30,6 +30,10 @@ var MSP_codes = {
|
|||
MSP_LOOP_TIME: 73,
|
||||
MSP_SET_LOOP_TIME: 74,
|
||||
|
||||
MSP_SDCARD_SUMMARY: 79,
|
||||
MSP_BLACKBOX_CONFIG: 80,
|
||||
MSP_SET_BLACKBOX_CONFIG: 81,
|
||||
|
||||
// Multiwii MSP commands
|
||||
MSP_IDENT: 100,
|
||||
MSP_STATUS: 101,
|
||||
|
@ -811,13 +815,17 @@ var MSP = {
|
|||
break;
|
||||
case MSP_codes.MSP_DATAFLASH_SUMMARY:
|
||||
if (data.byteLength >= 13) {
|
||||
DATAFLASH.ready = (data.getUint8(0) & 1) != 0;
|
||||
var
|
||||
flags = data.getUint8(0);
|
||||
DATAFLASH.ready = (flags & 1) != 0;
|
||||
DATAFLASH.supported = (flags & 2) != 0 || DATAFLASH.ready;
|
||||
DATAFLASH.sectors = data.getUint32(1, 1);
|
||||
DATAFLASH.totalSize = data.getUint32(5, 1);
|
||||
DATAFLASH.usedSize = data.getUint32(9, 1);
|
||||
} else {
|
||||
// Firmware version too old to support MSP_DATAFLASH_SUMMARY
|
||||
DATAFLASH.ready = false;
|
||||
DATAFLASH.supported = false;
|
||||
DATAFLASH.sectors = 0;
|
||||
DATAFLASH.totalSize = 0;
|
||||
DATAFLASH.usedSize = 0;
|
||||
|
@ -829,6 +837,24 @@ var MSP = {
|
|||
case MSP_codes.MSP_DATAFLASH_ERASE:
|
||||
console.log("Data flash erase begun...");
|
||||
break;
|
||||
case MSP_codes.MSP_SDCARD_SUMMARY:
|
||||
var flags = data.getUint8(0);
|
||||
|
||||
SDCARD.supported = (flags & 0x01) != 0;
|
||||
SDCARD.state = data.getUint8(1);
|
||||
SDCARD.filesystemLastError = data.getUint8(2);
|
||||
SDCARD.freeSizeKB = data.getUint32(3, 1);
|
||||
SDCARD.totalSizeKB = data.getUint32(7, 1);
|
||||
break;
|
||||
case MSP_codes.MSP_BLACKBOX_CONFIG:
|
||||
BLACKBOX.supported = (data.getUint8(0) & 1) != 0;
|
||||
BLACKBOX.blackboxDevice = data.getUint8(1);
|
||||
BLACKBOX.blackboxRateNum = data.getUint8(2);
|
||||
BLACKBOX.blackboxRateDenom = data.getUint8(3);
|
||||
break;
|
||||
case MSP_codes.MSP_SET_BLACKBOX_CONFIG:
|
||||
console.log("Blackbox config saved");
|
||||
break;
|
||||
case MSP_codes.MSP_SET_MODE_RANGE:
|
||||
console.log('Mode range saved');
|
||||
break;
|
||||
|
@ -1176,6 +1202,19 @@ MSP.setRawRx = function(channels) {
|
|||
MSP.send_message(MSP_codes.MSP_SET_RAW_RC, buffer, false);
|
||||
}
|
||||
|
||||
MSP.sendBlackboxConfiguration = function(onDataCallback) {
|
||||
var
|
||||
message = [
|
||||
BLACKBOX.blackboxDevice & 0xFF,
|
||||
BLACKBOX.blackboxRateNum & 0xFF,
|
||||
BLACKBOX.blackboxRateDenom & 0xFF
|
||||
];
|
||||
|
||||
MSP.send_message(MSP_codes.MSP_SET_BLACKBOX_CONFIG, message, false, function(response) {
|
||||
onDataCallback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
|
@ -1438,3 +1477,9 @@ MSP.serialPortFunctionsToMask = function(functions) {
|
|||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
MSP.SDCARD_STATE_NOT_PRESENT = 0;
|
||||
MSP.SDCARD_STATE_FATAL = 1;
|
||||
MSP.SDCARD_STATE_CARD_INIT = 2;
|
||||
MSP.SDCARD_STATE_FS_INIT = 3;
|
||||
MSP.SDCARD_STATE_READY = 4;
|
||||
|
|
12
main.html
12
main.html
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
@ -21,7 +21,7 @@
|
|||
<link type="text/css" rel="stylesheet" href="./tabs/sensors.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/cli.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/logging.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/dataflash.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/onboard_logging.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/firmware_flasher.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/adjustments.css" media="all" />
|
||||
<link type="text/css" rel="stylesheet" href="./tabs/auxiliary.css" media="all" />
|
||||
|
@ -73,7 +73,7 @@
|
|||
<script type="text/javascript" src="./tabs/sensors.js"></script>
|
||||
<script type="text/javascript" src="./tabs/cli.js"></script>
|
||||
<script type="text/javascript" src="./tabs/logging.js"></script>
|
||||
<script type="text/javascript" src="./tabs/dataflash.js"></script>
|
||||
<script type="text/javascript" src="./tabs/onboard_logging.js"></script>
|
||||
<script type="text/javascript" src="./tabs/firmware_flasher.js"></script>
|
||||
<title></title>
|
||||
</head>
|
||||
|
@ -188,9 +188,9 @@
|
|||
title="LED Strip"></a></li>
|
||||
<li class="tab_sensors"><a href="#" i18n="tabRawSensorData" class="tabicon ic_sensors"
|
||||
title="Sensors"></a></li>
|
||||
<li class="tab_logging"><a href="#" i18n="tabLogging" class="tabicon ic_log" title="Logging"></a></li>
|
||||
<li class="tab_dataflash"><a href="#" i18n="tabDataflash" class="tabicon ic_data"
|
||||
title="Dataflash"></a></li>
|
||||
<li class="tab_logging"><a href="#" i18n="tabLogging" class="tabicon ic_log" title="Tethered Logging"></a></li>
|
||||
<li class="tab_onboard_logging"><a href="#" i18n="tabOnboardLogging" class="tabicon ic_data"
|
||||
title="Onboard Logging"></a></li>
|
||||
<li class="tab_cli"><a href="#" i18n="tabCLI" class="tabicon ic_cli" title="CLI"></a></li>
|
||||
<!-- spare icons
|
||||
<li class=""><a href="#"class="tabicon ic_mission">Mission (spare icon)</a></li>
|
||||
|
|
4
main.js
4
main.js
|
@ -157,8 +157,8 @@ $(document).ready(function () {
|
|||
case 'logging':
|
||||
TABS.logging.initialize(content_ready);
|
||||
break;
|
||||
case 'dataflash':
|
||||
TABS.dataflash.initialize(content_ready);
|
||||
case 'onboard_logging':
|
||||
TABS.onboard_logging.initialize(content_ready);
|
||||
break;
|
||||
case 'cli':
|
||||
TABS.cli.initialize(content_ready);
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
.tab-dataflash .info {
|
||||
margin: 0 0 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-dataflash .info .progressLabel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
/* text-shadow: 1px 0px 2px rgba(0, 0, 0, 0.9);*/
|
||||
}
|
||||
|
||||
.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 .dataflash-progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-dataflash .dataflash-contents {
|
||||
margin-top: 5px;
|
||||
border: 1px solid silver;
|
||||
background-color: #eee;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.tab-dataflash .dataflash-contents li {
|
||||
height: 26px;
|
||||
position: relative;
|
||||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tab-dataflash .dataflash-contents li div {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
margin-top: 4px;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tab-dataflash .dataflash-used {
|
||||
background-color: #59AA29;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tab-dataflash progress::-webkit-progress-bar {
|
||||
height: 24px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.tab-dataflash progress::-webkit-progress-value {
|
||||
background-color: #bcf;
|
||||
}
|
||||
|
||||
.tab-dataflash dialog {
|
||||
width: 40em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.tab-dataflash dialog .buttons {
|
||||
position: static;
|
||||
margin-top: 2em;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tab-dataflash .buttons a {
|
||||
margin-top: 9px;
|
||||
margin-bottom: 0px;
|
||||
margin-right: 10px;
|
||||
background-color: #59aa29;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #4c8829;
|
||||
color: #fff;
|
||||
float: left;
|
||||
font-family: 'open_sansbold', Arial;
|
||||
font-size: 12px;
|
||||
text-shadow: 0px 1px rgba(0, 0, 0, 0.25);
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.2s;
|
||||
padding: 0px;
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.tab-dataflash .buttons a:hover {
|
||||
background-color: #6ac435;
|
||||
border: 1px solid #4d9324;
|
||||
text-shadow: 0px 1px rgba(0, 0, 0, 0.25);
|
||||
color: #fff;
|
||||
transition: all ease 0.2s;
|
||||
}
|
||||
|
||||
.tab-dataflash .buttons a:active {
|
||||
background-color: #4d9324;
|
||||
transition: all ease 0.0s;
|
||||
box-shadow: inset 0px 1px 5px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.tab-dataflash dialog h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.dataflash-confirm-erase .dataflash-erase-progress {
|
||||
height: 125px;
|
||||
display: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.dataflash-confirm-erase.erasing .dataflash-erase-progress {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dataflash-confirm-erase.erasing h3, .dataflash-confirm-erase.erasing .erase-flash-confirm, .dataflash-confirm-erase.erasing .dataflash-confirm-erase-note
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-dataflash progress {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.dataflash-saving .dataflash-saving-after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dataflash-saving.done .dataflash-saving-before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dataflash-saving.done .dataflash-saving-after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.require-dataflash {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-dataflash.supported .require-dataflash {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.require-no-dataflash {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-dataflash.supported .require-no-dataflash {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1055px) , only screen and (max-device-width: 1055px) {
|
||||
.tab-dataflash table thead tr:first-child {
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<div class="tab-dataflash toolbar_fixed_bottom">
|
||||
<div class="content_wrapper">
|
||||
<div class="tab_title" i18n="tabDataflash"></div>
|
||||
<div class="cf_doc_version_bt">
|
||||
<a id="button-documentation" href="https://github.com/cleanflight/cleanflight/releases" target="_blank"></a>
|
||||
</div>
|
||||
<div class="require-dataflash">
|
||||
<div class="note" style="margin-bottom: 20px;">
|
||||
<div class="note_spacer">
|
||||
<p i18n="dataflashNote"></p>
|
||||
</div>
|
||||
</div>
|
||||
<dialog class="dataflash-confirm-erase">
|
||||
<h3 i18n="dataflashConfirmEraseTitle"></h3>
|
||||
<div class="dataflash-confirm-erase-note" i18n="dataflashConfirmEraseNote"></div>
|
||||
<div class="dataflash-erase-progress">
|
||||
<div class="data-loading">
|
||||
<p>Erase in progress, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a href="#" class="erase-flash-cancel" i18n="dataflashButtonEraseCancel"></a> <a href="#"
|
||||
class="erase-flash-confirm" i18n="dataflashButtonEraseConfirm"></a>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="dataflash-saving">
|
||||
<h3 i18n="dataflashSavingTitle"></h3>
|
||||
<div class="dataflash-saving-before">
|
||||
<div i18n="dataflashSavingNote"></div>
|
||||
<progress value="0" min="0" max="100"></progress>
|
||||
<div class="buttons">
|
||||
<a href="#" class="save-flash-cancel" i18n="dataflashButtonSaveCancel"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataflash-saving-after">
|
||||
<div i18n="dataflashSavingNoteAfter"></div>
|
||||
<div class="buttons">
|
||||
<a href="#" class="save-flash-dismiss" i18n="dataflashButtonSaveDismiss"></a>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<h3>Dataflash contents</h3>
|
||||
<ul class="dataflash-contents">
|
||||
<li class="dataflash-used">
|
||||
<div class="legend">Used space</div>
|
||||
</li>
|
||||
<li class="dataflash-free">
|
||||
<div class="legend">Free space</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="require-no-dataflash note">
|
||||
<div class="note_spacer">
|
||||
<p i18n="require-no-dataflash"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content_toolbar">
|
||||
<div class="btn erase_btn">
|
||||
<a class="erase-flash" href="#" i18n="dataflashButtonErase"></a>
|
||||
</div>
|
||||
<div class="btn save_btn">
|
||||
<a class="save-flash" href="#" i18n="dataflashButtonSaveFile"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,288 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
TABS.dataflash = {
|
||||
available: false
|
||||
};
|
||||
TABS.dataflash.initialize = function (callback) {
|
||||
var
|
||||
self = this,
|
||||
saveCancelled, eraseCancelled;
|
||||
|
||||
if (GUI.active_tab != 'dataflash') {
|
||||
GUI.active_tab = 'dataflash';
|
||||
googleAnalytics.sendAppView('dataflash');
|
||||
}
|
||||
|
||||
var
|
||||
requested_properties = [],
|
||||
samples = 0,
|
||||
requests = 0,
|
||||
log_buffer = [];
|
||||
|
||||
if (CONFIGURATOR.connectionValid) {
|
||||
TABS.dataflash.available = semver.gte(CONFIG.apiVersion, "1.6.0");
|
||||
|
||||
if (!TABS.dataflash.available) {
|
||||
load_html();
|
||||
return;
|
||||
}
|
||||
|
||||
MSP.send_message(MSP_codes.MSP_DATAFLASH_SUMMARY, false, false, load_html);
|
||||
}
|
||||
|
||||
function load_html() {
|
||||
$('#content').load("./tabs/dataflash.html", function() {
|
||||
create_html();
|
||||
});
|
||||
}
|
||||
|
||||
function formatFilesize(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + "B";
|
||||
}
|
||||
|
||||
var kilobytes = bytes / 1024;
|
||||
|
||||
if (kilobytes < 1024) {
|
||||
return Math.round(kilobytes) + "kB";
|
||||
}
|
||||
|
||||
var megabytes = kilobytes / 1024;
|
||||
|
||||
return megabytes.toFixed(1) + "MB";
|
||||
}
|
||||
|
||||
function update_html() {
|
||||
if (DATAFLASH.usedSize > 0) {
|
||||
$(".tab-dataflash .dataflash-used").css({
|
||||
width: (DATAFLASH.usedSize / DATAFLASH.totalSize * 100) + "%",
|
||||
display: 'block'
|
||||
});
|
||||
|
||||
$(".tab-dataflash .dataflash-used div").text('Used space ' + formatFilesize(DATAFLASH.usedSize));
|
||||
} else {
|
||||
$(".tab-dataflash .dataflash-used").css({
|
||||
display: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
if (DATAFLASH.totalSize - DATAFLASH.usedSize > 0) {
|
||||
$(".tab-dataflash .dataflash-free").css({
|
||||
width: ((DATAFLASH.totalSize - DATAFLASH.usedSize) / DATAFLASH.totalSize * 100) + "%",
|
||||
display: 'block'
|
||||
});
|
||||
$(".tab-dataflash .dataflash-free div").text('Free space ' + formatFilesize(DATAFLASH.totalSize - DATAFLASH.usedSize));
|
||||
} else {
|
||||
$(".tab-dataflash .dataflash-free").css({
|
||||
display: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
$(".btn a.erase-flash, .btn a.save-flash").toggleClass("disabled", DATAFLASH.usedSize == 0);
|
||||
}
|
||||
|
||||
function create_html() {
|
||||
|
||||
// translate to user-selected language
|
||||
localize();
|
||||
|
||||
|
||||
if (TABS.dataflash.available) {
|
||||
var supportsDataflash = DATAFLASH.totalSize > 0;
|
||||
|
||||
$(".tab-dataflash").toggleClass("supported", supportsDataflash);
|
||||
|
||||
if (supportsDataflash) {
|
||||
// UI hooks
|
||||
$('.tab-dataflash a.erase-flash').click(ask_to_erase_flash);
|
||||
|
||||
$('.tab-dataflash a.erase-flash-confirm').click(flash_erase);
|
||||
$('.tab-dataflash a.erase-flash-cancel').click(flash_erase_cancel);
|
||||
|
||||
$('.tab-dataflash a.save-flash').click(flash_save_begin);
|
||||
$('.tab-dataflash a.save-flash-cancel').click(flash_save_cancel);
|
||||
$('.tab-dataflash a.save-flash-dismiss').click(dismiss_saving_dialog);
|
||||
|
||||
update_html();
|
||||
} else {
|
||||
$(".tab-dataflash .note_spacer").html(chrome.i18n.getMessage('dataflashNotSupportedNote'));
|
||||
}
|
||||
} else {
|
||||
$(".tab-dataflash").removeClass("supported");
|
||||
$(".tab-dataflash .note").html(chrome.i18n.getMessage('dataflashFirmwareUpgradeRequired'));
|
||||
}
|
||||
|
||||
|
||||
GUI.content_ready(callback);
|
||||
}
|
||||
|
||||
// IO related methods
|
||||
function zeroPad(value, width) {
|
||||
value = "" + value;
|
||||
|
||||
while (value.length < width) {
|
||||
value = "0" + value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
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() {
|
||||
$(".dataflash-saving").addClass("done");
|
||||
}
|
||||
|
||||
function flash_update_summary(onDone) {
|
||||
MSP.send_message(MSP_codes.MSP_DATAFLASH_SUMMARY, false, false, function() {
|
||||
update_html();
|
||||
|
||||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function flash_save_begin() {
|
||||
if (GUI.connected_to) {
|
||||
// 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;
|
||||
|
||||
show_saving_dialog();
|
||||
|
||||
function onChunkRead(chunkAddress, chunkDataView) {
|
||||
if (chunkDataView != null) {
|
||||
// Did we receive any data?
|
||||
if (chunkDataView.byteLength > 0) {
|
||||
nextAddress += chunkDataView.byteLength;
|
||||
|
||||
$(".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();
|
||||
}
|
||||
} else {
|
||||
MSP.dataflashRead(nextAddress, onChunkRead);
|
||||
}
|
||||
};
|
||||
|
||||
fileWriter.write(blob);
|
||||
} else {
|
||||
// A zero-byte block indicates end-of-file, so we're done
|
||||
mark_saving_dialog_done();
|
||||
}
|
||||
} else {
|
||||
// There was an error with the received block (address didn't match the one we asked for), retry
|
||||
MSP.dataflashRead(nextAddress, onChunkRead);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the initial block
|
||||
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) {
|
||||
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 (!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(MSP_codes.MSP_DATAFLASH_ERASE, false, false, poll_for_erase_completion);
|
||||
}
|
||||
|
||||
function flash_erase_cancel() {
|
||||
eraseCancelled = true;
|
||||
$(".dataflash-confirm-erase")[0].close();
|
||||
}
|
||||
};
|
||||
|
||||
TABS.dataflash.cleanup = function (callback) {
|
||||
if (callback) callback();
|
||||
};
|
298
tabs/onboard_logging.css
Normal file
298
tabs/onboard_logging.css
Normal file
|
@ -0,0 +1,298 @@
|
|||
.tab-onboard_logging .info {
|
||||
margin: 0 0 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .info .progressLabel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
/* text-shadow: 1px 0px 2px rgba(0, 0, 0, 0.9);*/
|
||||
}
|
||||
|
||||
.tab-onboard_logging .properties {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-info {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-info dt {
|
||||
float: left;
|
||||
width: 12em;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-info dd {
|
||||
display: block;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .speed {
|
||||
margin-top: 5px;
|
||||
width: 80px;
|
||||
border: 1px solid silver;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .info {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .info dt {
|
||||
float: left;
|
||||
width: 120px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .info dd {
|
||||
display: block;
|
||||
margin-left: 130px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .buttons {
|
||||
width: calc(100% - 20px);
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-contents,
|
||||
.tab-onboard_logging .sdcard-contents {
|
||||
margin-top: 15px;
|
||||
margin-bottom:26px;
|
||||
border: 1px solid silver;
|
||||
background-color: #eee;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-contents li,
|
||||
.tab-onboard_logging .sdcard-contents li {
|
||||
height: 26px;
|
||||
position: relative;
|
||||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-contents li div,
|
||||
.tab-onboard_logging .sdcard-contents li div {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
margin-top: 4px;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .dataflash-used,
|
||||
.tab-onboard_logging .sdcard-other {
|
||||
background-color: #59AA29;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging progress::-webkit-progress-bar {
|
||||
height: 24px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.tab-onboard_logging progress::-webkit-progress-value {
|
||||
background-color: #bcf;
|
||||
}
|
||||
|
||||
.tab-onboard_logging dialog {
|
||||
width: 40em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging dialog .buttons {
|
||||
position: static;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.tab-onboard_logging dialog h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.dataflash-confirm-erase .dataflash-erase-progress {
|
||||
height: 125px;
|
||||
display: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.dataflash-confirm-erase.erasing .dataflash-erase-progress {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dataflash-confirm-erase.erasing h3, .dataflash-confirm-erase.erasing .erase-flash-confirm, .dataflash-confirm-erase.erasing .dataflash-confirm-erase-note
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-onboard_logging progress {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.dataflash-saving .dataflash-saving-after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dataflash-saving.done .dataflash-saving-before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dataflash-saving.done .dataflash-saving-after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.require-dataflash-present,
|
||||
.require-dataflash-supported,
|
||||
.require-sdcard-ready,
|
||||
.require-sdcard-supported,
|
||||
.require-blackbox-supported,
|
||||
.require-blackbox-maybe-supported,
|
||||
.require-blackbox-unsupported,
|
||||
.require-blackbox-config-supported,
|
||||
.tab-onboard_logging.dataflash-present .require-dataflash-not-present,
|
||||
.tab-onboard_logging.dataflash-supported .require-dataflash-unsupported,
|
||||
.tab-onboard_logging.sdcard-supported .require-sdcard-unsupported,
|
||||
.tab-onboard_logging.blackbox-config-supported .require-blackbox-config-unsupported {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-onboard_logging.dataflash-present .require-dataflash-present,
|
||||
.tab-onboard_logging.dataflash-supported .require-dataflash-supported,
|
||||
.tab-onboard_logging.sdcard-ready .require-sdcard-ready,
|
||||
.tab-onboard_logging.sdcard-supported .require-sdcard-supported,
|
||||
.tab-onboard_logging.blackbox-supported .require-blackbox-supported,
|
||||
.tab-onboard_logging.blackbox-maybe-supported .require-blackbox-maybe-supported,
|
||||
.tab-onboard_logging.blackbox-unsupported .require-blackbox-unsupported,
|
||||
.tab-onboard_logging.blackbox-config-supported .require-blackbox-config-supported {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.require-no-dataflash {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-onboard_logging.supported .require-no-dataflash {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1055px) , only screen and (max-device-width: 1055px) {
|
||||
.tab-onboard_logging table thead tr:first-child {
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-onboard_logging .line {
|
||||
clear: left;
|
||||
}
|
||||
.tab-onboard_logging .blackboxRate select,
|
||||
.tab-onboard_logging .blackboxDevice select {
|
||||
float: left;
|
||||
width: 180px;
|
||||
height: 20px;
|
||||
margin: 0 10px 5px 0;
|
||||
border: 1px solid silver;
|
||||
}
|
||||
.tab-onboard_logging .blackboxRate span,
|
||||
.tab-onboard_logging .blackboxDevice span {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .sdcard {
|
||||
padding:10px;
|
||||
float:left;
|
||||
}
|
||||
.tab-onboard_logging .sdcard-status {
|
||||
padding-top: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.tab-onboard_logging.sdcard-error .sdcard-icon {
|
||||
background-color: #e60000;
|
||||
border: 1px solid #fe0000;
|
||||
}
|
||||
.tab-onboard_logging.sdcard-initializing .sdcard-icon {
|
||||
background-color: #64a5f6;
|
||||
border: 1px solid #68a7ff;
|
||||
}
|
||||
.tab-onboard_logging.sdcard-ready .sdcard-icon {
|
||||
background-color: #56ac1d;
|
||||
border: 1px solid #5bbb1b;
|
||||
}
|
||||
.tab-onboard_logging .sdcard-icon {
|
||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.35);
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
background-image: url(/images/icons/cf_icon_sdcard.svg);
|
||||
background-position: 21px 20px;
|
||||
background-size: 50px 50px;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
background-color: #808080;
|
||||
border: 1px solid #888888;
|
||||
|
||||
border-radius: 45px;
|
||||
}
|
||||
|
||||
.tab-onboard_logging .regular-button {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 10px;
|
||||
background-color: #59aa29;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #4c8829;
|
||||
color: #fff;
|
||||
font-family: 'open_sansbold', Arial;
|
||||
font-size: 12px;
|
||||
text-shadow: 0px 1px rgba(0, 0, 0, 0.25);
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.2s;
|
||||
padding: 0px;
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.tab-onboard_logging .regular-button:hover {
|
||||
background-color: #6ac435;
|
||||
transition: all ease 0.2s;
|
||||
}
|
||||
.tab-onboard_logging .regular-button:active {
|
||||
background-color: #4d9324;
|
||||
transition: all ease 0.0s;
|
||||
box-shadow: inset 0px 1px 5px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.tab-onboard_logging .regular-button.disabled {
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
background-color: #AFAFAF;
|
||||
border: 1px solid #AFAFAF;
|
||||
pointer-events: none;
|
||||
text-shadow: none;
|
||||
opacity: 0.5;
|
||||
}
|
141
tabs/onboard_logging.html
Normal file
141
tabs/onboard_logging.html
Normal file
|
@ -0,0 +1,141 @@
|
|||
<div class="tab-onboard_logging toolbar_fixed_bottom">
|
||||
<div class="content_wrapper">
|
||||
<div class="tab_title" i18n="tabOnboardLogging"></div>
|
||||
<div class="cf_doc_version_bt">
|
||||
<a id="button-documentation" href="https://github.com/cleanflight/cleanflight/releases" target="_blank"></a>
|
||||
</div>
|
||||
<div class="require-blackbox-unsupported note">
|
||||
<div class="note_spacer">
|
||||
<p i18n="blackboxNotSupported"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="require-blackbox-maybe-supported note">
|
||||
<div class="note_spacer">
|
||||
<p i18n="blackboxMaybeSupported"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="require-blackbox-supported">
|
||||
<div class="gui_box grey require-blackbox-config-supported">
|
||||
<div class="gui_box_titlebar">
|
||||
<div class="spacer_box_title" i18n="blackboxConfiguration"></div>
|
||||
</div>
|
||||
<div class="spacer_box">
|
||||
<div class="line blackboxDevice">
|
||||
<select name="blackbox_device">
|
||||
</select>
|
||||
<span>Blackbox logging device</span>
|
||||
</div>
|
||||
<div class="line blackboxRate">
|
||||
<select name="blackbox_rate">
|
||||
</select>
|
||||
<span>Portion of flight loop iterations to log (logging rate)</span>
|
||||
</div>
|
||||
<div class="line">
|
||||
<a href="#" class="save-settings regular-button" i18n="blackboxButtonSave"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gui_box grey">
|
||||
<div class="gui_box_titlebar" align="left">
|
||||
<div class="spacer_box_title">
|
||||
Outboard serial logging device
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer_box">
|
||||
<p i18n="serialLoggingSupportedNote"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gui_box grey require-dataflash-supported">
|
||||
<div class="gui_box_titlebar" align="left">
|
||||
<div class="spacer_box_title">
|
||||
Onboard dataflash chip
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer_box">
|
||||
<div class="require-dataflash-supported">
|
||||
<p i18n="dataflashNote"></p>
|
||||
|
||||
<dialog class="dataflash-confirm-erase">
|
||||
<h3 i18n="dataflashConfirmEraseTitle"></h3>
|
||||
<div class="dataflash-confirm-erase-note" i18n="dataflashConfirmEraseNote"></div>
|
||||
<div class="dataflash-erase-progress">
|
||||
<div class="data-loading">
|
||||
<p>Erase in progress, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a href="#" class="erase-flash-confirm regular-button" i18n="dataflashButtonEraseConfirm"></a>
|
||||
<a href="#" class="erase-flash-cancel regular-button" i18n="dataflashButtonEraseCancel"></a>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="dataflash-saving">
|
||||
<h3 i18n="dataflashSavingTitle"></h3>
|
||||
<div class="dataflash-saving-before">
|
||||
<div i18n="dataflashSavingNote"></div>
|
||||
<progress value="0" min="0" max="100"></progress>
|
||||
<div class="buttons">
|
||||
<a href="#" class="save-flash-cancel regular-button" i18n="dataflashButtonSaveCancel"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataflash-saving-after">
|
||||
<div i18n="dataflashSavingNoteAfter"></div>
|
||||
<div class="buttons">
|
||||
<a href="#" class="save-flash-dismiss regular-button" i18n="dataflashButtonSaveDismiss"></a>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<ul class="dataflash-contents">
|
||||
<li class="dataflash-used">
|
||||
<div class="legend"></div>
|
||||
</li>
|
||||
<li class="dataflash-free">
|
||||
<div class="legend"></div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<a class="regular-button erase-flash" href="#" i18n="dataflashButtonErase"></a>
|
||||
<a class="regular-button save-flash" href="#" i18n="dataflashButtonSaveFile"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="require-dataflash-not-present" i18n="dataflashNotPresentNote"></p>
|
||||
<p class="require-dataflash-unsupported" i18n="dataflashFirmwareUpgradeRequired"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="require-sdcard-supported">
|
||||
<div class="gui_box grey">
|
||||
<div class="gui_box_titlebar" align="left">
|
||||
<div class="spacer_box_title">
|
||||
Onboard SD card
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer_box">
|
||||
<div class="sdcard">
|
||||
<div class="sdcard-icon"></div>
|
||||
<div class="sdcard-status"></div>
|
||||
</div>
|
||||
|
||||
<p i18n="sdcardNote"></p>
|
||||
|
||||
<div class="require-sdcard-ready">
|
||||
<ul class="sdcard-contents">
|
||||
<li class="sdcard-other">
|
||||
<div class="legend"></div>
|
||||
</li>
|
||||
<li class="sdcard-free">
|
||||
<div class="legend"></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
470
tabs/onboard_logging.js
Normal file
470
tabs/onboard_logging.js
Normal file
|
@ -0,0 +1,470 @@
|
|||
'use strict';
|
||||
|
||||
var
|
||||
sdcardTimer;
|
||||
|
||||
TABS.onboard_logging = {
|
||||
available: false
|
||||
};
|
||||
TABS.onboard_logging.initialize = function (callback) {
|
||||
var
|
||||
self = this,
|
||||
saveCancelled, eraseCancelled;
|
||||
|
||||
if (GUI.active_tab != 'onboard_logging') {
|
||||
GUI.active_tab = 'onboard_logging';
|
||||
googleAnalytics.sendAppView('onboard_logging');
|
||||
}
|
||||
|
||||
if (CONFIGURATOR.connectionValid) {
|
||||
// Blackbox was introduced in 1.5.0, dataflash API was introduced in 1.8.0, BLACKBOX/SDCARD MSP APIs in 1.11.0
|
||||
TABS.onboard_logging.available = semver.gte(CONFIG.flightControllerVersion, "1.5.0");
|
||||
|
||||
if (!TABS.onboard_logging.available) {
|
||||
load_html();
|
||||
return;
|
||||
}
|
||||
|
||||
MSP.send_message(MSP_codes.MSP_BF_CONFIG, false, false, function() {
|
||||
if (semver.gte(CONFIG.flightControllerVersion, "1.8.0")) {
|
||||
MSP.send_message(MSP_codes.MSP_DATAFLASH_SUMMARY, false, false, function() {
|
||||
if (semver.gte(CONFIG.flightControllerVersion, "1.11.0")) {
|
||||
MSP.send_message(MSP_codes.MSP_SDCARD_SUMMARY, false, false, function() {
|
||||
MSP.send_message(MSP_codes.MSP_BLACKBOX_CONFIG, false, false, load_html);
|
||||
});
|
||||
} else {
|
||||
load_html();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
load_html();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function gcd(a, b) {
|
||||
if (b == 0)
|
||||
return a;
|
||||
|
||||
return gcd(b, a % b);
|
||||
}
|
||||
|
||||
function save_to_eeprom() {
|
||||
MSP.send_message(MSP_codes.MSP_EEPROM_WRITE, false, false, reboot);
|
||||
}
|
||||
|
||||
function reboot() {
|
||||
GUI.log(chrome.i18n.getMessage('configurationEepromSaved'));
|
||||
|
||||
GUI.tab_switch_cleanup(function() {
|
||||
MSP.send_message(MSP_codes.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(MSP_codes.MSP_IDENT, 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.flightControllerVersion, "1.5.0") && semver.lte(CONFIG.flightControllerVersion, "1.10.0") && bit_check(BF_CONFIG.features, 19)) {
|
||||
blackboxSupport = 'yes';
|
||||
} else if (semver.gte(CONFIG.flightControllerVersion, "1.5.0") && semver.lte(CONFIG.flightControllerVersion, "1.10.0")) {
|
||||
blackboxSupport = 'maybe';
|
||||
} 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);
|
||||
}
|
||||
|
||||
if (BLACKBOX.supported) {
|
||||
$(".tab-onboard_logging a.save-settings").click(function() {
|
||||
var rate = $(".blackboxRate select").val().split('/');
|
||||
|
||||
BLACKBOX.blackboxRateNum = parseInt(rate[0], 10);
|
||||
BLACKBOX.blackboxRateDenom = parseInt(rate[1], 10);
|
||||
BLACKBOX.blackboxDevice = parseInt($(".blackboxDevice select").val(), 10);
|
||||
|
||||
MSP.sendBlackboxConfiguration(save_to_eeprom);
|
||||
});
|
||||
}
|
||||
|
||||
populateLoggingRates();
|
||||
populateDevices();
|
||||
|
||||
update_html();
|
||||
|
||||
GUI.content_ready(callback);
|
||||
});
|
||||
}
|
||||
|
||||
function populateDevices() {
|
||||
var
|
||||
deviceSelect = $(".blackboxDevice select").empty();
|
||||
|
||||
deviceSelect.append('<option value="0">Serial port</option>');
|
||||
if (DATAFLASH.ready) {
|
||||
deviceSelect.append('<option value="1">On-board dataflash chip</option>');
|
||||
}
|
||||
if (SDCARD.supported) {
|
||||
deviceSelect.append('<option value="2">On-board SD card slot</option>');
|
||||
}
|
||||
|
||||
deviceSelect.val(BLACKBOX.blackboxDevice);
|
||||
}
|
||||
|
||||
function populateLoggingRates() {
|
||||
var
|
||||
userRateGCD = gcd(BLACKBOX.blackboxRateNum, BLACKBOX.blackboxRateDenom),
|
||||
userRate = {num: BLACKBOX.blackboxRateNum / userRateGCD, denom: BLACKBOX.blackboxRateDenom / userRateGCD};
|
||||
|
||||
// Offer a reasonable choice of logging rates (if people want weird steps they can use CLI)
|
||||
var
|
||||
loggingRates = [
|
||||
{num: 1, denom: 32},
|
||||
{num: 1, denom: 16},
|
||||
{num: 1, denom: 8},
|
||||
{num: 1, denom: 5},
|
||||
{num: 1, denom: 4},
|
||||
{num: 1, denom: 3},
|
||||
{num: 1, denom: 2},
|
||||
{num: 2, denom: 3},
|
||||
{num: 3, denom: 4},
|
||||
{num: 4, denom: 5},
|
||||
{num: 7, denom: 8},
|
||||
{num: 1, denom: 1},
|
||||
],
|
||||
loggingRatesSelect = $(".blackboxRate select");
|
||||
|
||||
var
|
||||
addedCurrentValue = false;
|
||||
|
||||
for (var i = 0; i < loggingRates.length; i++) {
|
||||
if (!addedCurrentValue && userRate.num / userRate.denom <= loggingRates[i].num / loggingRates[i].denom) {
|
||||
if (userRate.num / userRate.denom < loggingRates[i].num / loggingRates[i].denom) {
|
||||
loggingRatesSelect.append('<option value="' + userRate.num + '/' + userRate.denom + '">'
|
||||
+ userRate.num + '/' + userRate.denom + ' (' + Math.round(userRate.num / userRate.denom * 100) + '%)</option>');
|
||||
}
|
||||
addedCurrentValue = true;
|
||||
}
|
||||
|
||||
loggingRatesSelect.append('<option value="' + loggingRates[i].num + '/' + loggingRates[i].denom + '">'
|
||||
+ loggingRates[i].num + '/' + loggingRates[i].denom + ' (' + Math.round(loggingRates[i].num / loggingRates[i].denom * 100) + '%)</option>');
|
||||
|
||||
}
|
||||
loggingRatesSelect.val(userRate.num + '/' + userRate.denom);
|
||||
}
|
||||
|
||||
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<br>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(MSP_codes.MSP_SDCARD_SUMMARY, false, false, function() {
|
||||
update_html();
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// IO related methods
|
||||
function zeroPad(value, width) {
|
||||
value = "" + value;
|
||||
|
||||
while (value.length < width) {
|
||||
value = "0" + value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
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() {
|
||||
$(".dataflash-saving").addClass("done");
|
||||
}
|
||||
|
||||
function flash_update_summary(onDone) {
|
||||
MSP.send_message(MSP_codes.MSP_DATAFLASH_SUMMARY, false, false, function() {
|
||||
update_html();
|
||||
|
||||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function flash_save_begin() {
|
||||
if (GUI.connected_to) {
|
||||
// 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;
|
||||
|
||||
show_saving_dialog();
|
||||
|
||||
function onChunkRead(chunkAddress, chunkDataView) {
|
||||
if (chunkDataView != null) {
|
||||
// Did we receive any data?
|
||||
if (chunkDataView.byteLength > 0) {
|
||||
nextAddress += chunkDataView.byteLength;
|
||||
|
||||
$(".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();
|
||||
}
|
||||
} else {
|
||||
MSP.dataflashRead(nextAddress, onChunkRead);
|
||||
}
|
||||
};
|
||||
|
||||
fileWriter.write(blob);
|
||||
} else {
|
||||
// A zero-byte block indicates end-of-file, so we're done
|
||||
mark_saving_dialog_done();
|
||||
}
|
||||
} else {
|
||||
// There was an error with the received block (address didn't match the one we asked for), retry
|
||||
MSP.dataflashRead(nextAddress, onChunkRead);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the initial block
|
||||
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) {
|
||||
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(MSP_codes.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();
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue