Commit 33921f6b authored by Roman's avatar Roman

added roslog repo

parent 0b821856
# roslog2html
## Утилита для просмотра ROS логов с Nano PI
Позволяет просматривать rosout.log файлы в html формате с синхронизацией времени записей с временем GPS.
## Как работает синхронизация по времени
Phoros подписывается на топик `sensor_msgs::TimeReference`. Когда он впервые получает time reference, он записывает в лог полученное время, и в начале этой же записи указано локальное (nano pi) время получения. Вычисляется offset и применяется ко всем записям в rosout.log. Если записи о получении не найдено, в выходном html-файле будет указано, что время не синхронизировано.
This diff is collapsed.
a.nav-link {
cursor: pointer;
}
.clickable {
cursor: pointer;
}
.modal-full {
min-width: 100%;
height: 100%;
margin: 0;
}
.modal-full > .modal-content {
height: 100%;
}
/* drag-and-drop */
.upload-drop-zone {
height: 200px;
border-width: 2px;
margin-bottom: 20px;
}
.upload-drop-zone {
/*color: #ccc;*/
border-style: dashed;
border-color: #ccc;
line-height: 200px;
text-align: center
}
.upload-drop-zone.drop {
color: #222;
border-color: #222;
}
.logline {
font-size: 12px;
font-family: "Lucida Console", Monaco, monospace;
}
/* severity */
.sDEBUG {
color: #777;
}
.sINFO {
color: #333;
}
.sWARN {
color: #B27A09;
font-weight: bold;
}
.sERROR {
color: #FF0000;
font-weight: bold;
}
.sFATAL {
color: #7C0000;
font-weight: bold;
}
.sUNKNOWN {
color: #FF00FF;
font-weight: bold;
}
.sSTATUS {
color: #999;
}
This diff is collapsed.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="author" content="Roman Strakhov">
<meta name="theme-color" content="#eee" id='metaBarColor'>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="js/3rd_party/bootstrap.min.css"/>
<!-- Font Awesome -->
<link rel="stylesheet" type="text/css" href="css/fa.min.css">
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="js/3rd_party/jquery-3.3.1.min.js" ></script>
<script src="js/3rd_party/popper.min.js"></script>
<script src="js/3rd_party/bootstrap.min.js"></script>
<!-- 3rd party js -->
<script src='js/3rd_party/bootstrap-input-spinner.js'> </script>
<script src='js/3rd_party/darkreader.js'> </script>
<!-- custom scripts and styles -->
<link rel="stylesheet" type="text/css" href="css/main.css?v01">
<script src='js/init.js?v01'> </script>
<script src='js/layouts.js?v01'> </script>
<script src='js/glob.js?v01'> </script>
<script src='js/helpers.js?v01'> </script>
<script src='js/log.js?v01'> </script>
<title>ROS log</title>
</head>
<body>
<!-- navbar -->
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href='#'>
<img src="img/logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
ROS log
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" id='' onclick='loDropFile()'>Загрузить новый rosout.log</a>
</li>
</ul>
</div>
</nav>
<div id="mainLayWrap" class='container'>
<div id='mainLayout' class='centered'>
</div>
</div>
<input id='logFile' accept='.log' type='file' onchange='onUploadFileChange(null)' hidden/>
</body>
</html>
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
Glob = function () {}
// log file lines
Glob.lines = null;
// returns loading spinner to show "Loading..."
function jqLoadingSpinner() {
return $('<div class="text-center"> <div class="spinner-border" role="status" style="width: 4em; height: 4em;"> <span class="sr-only">Loading...</span> </div> </div>');
}
// places loading spinner to show "Loading..."
function showLoadingSpinner() {
$("#mainLayout").html('<br><div class="text-center"> <div class="spinner-border" role="status" style="width: 4rem; height: 4rem;"> <span class="sr-only">Loading...</span> </div> </div>');
}
// download file with plain text in it
function downloadPlainText(text, fileName) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', fileName);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
// converts file size in bytes to human readable string
function humanFileSize(bytes, si = true) {
var thresh = si ? 1000 : 1024;
if(Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = si
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1)+' '+units[u];
}
$(document).ready(function() {
initMenu();
loDropFile();
initDropAccepting();
});
function initMenu() {
$("#navbarNav").find("li.nav-item a.nav-link").attr('data-toggle', 'collapse').attr("data-target", "#navbarNav");
}
function initDropAccepting() {
$("html")
.on('dragover', function (event) {
event.preventDefault();
event.stopPropagation();
$("#dropZone").addClass('drop');
return false;
})
.on('dragleave', function (event) {
event.preventDefault();
event.stopPropagation();
$("#dropZone").removeClass('drop');
return false;
})
.on('drop', function (event) {
event.preventDefault();
event.stopPropagation();
if (event.originalEvent.dataTransfer && event.originalEvent.dataTransfer.files.length) {
onUploadFileChange(event.originalEvent.dataTransfer.files[0]);
} else {
$("#dropLabel").html('oops, something went wrong. Please use the button to upload files');
}
return false;
});
}
// on file uploaded
function onUploadFileChange(file = null) {
$("#dropZone").removeClass('drop');
if (file == null)
file = $('#logFile')[0].files[0];
if (!file || !file.size)
return;
let maxFilesize = 999 * 1e6;
if (file.size > maxFilesize) {
$("#dropLabel").html(' ' + file.name + ' is too big ('+humanFileSize(file.size)+') &#128546;');
$("#chooseFileBtn").html('Upload image (max. ' + humanFileSize(maxFilesize) + ')');
return;
}
if (file.name.indexOf(".log") == -1)
return $("#dropLabel").html(file.name + ' is not a roslog');
showLoadingSpinner();
var reader = new FileReader();
reader.addEventListener("load", function () {
onLogLoaded(reader.result);
}, false);
reader.readAsText(file);
}
// 'upload image' button clicked
function uploadFileClicked() {
$('#logFile').val(null).trigger("click");
}
// main layout with "drop image" thing
function loDropFile() {
let chooseFileBtn = $("<button class='btn btn-primary' id='chooseFileBtn'></button>")
.html("Загрузить rosout.log <i class='fa fa-upload'></i>")
.click(uploadFileClicked);
let h1 = $("<h1></h1>").html("roslog2html");
let subtitle = $("<h4></h4>").html("Утилита для просмотра rosout.log логов с Nano PI");
let dropZone = $('<div class="upload-drop-zone" id="dropZone"></div>')
.append(chooseFileBtn, "<span id='dropLabel'> или перетащите сюда</span>");
let div = $("<div class='container'></div>").append(h1, subtitle, dropZone);
$("#mainLayout").html(div);
}
// log file lines
function loDisplayLogLines(timeOffset, logLines) {
$("#mainLayout").empty().append(logLines);
}
function onLogLoaded(txt) {
Glob.lines = txt.split('\n');
let timeOffset = getTimeOffset(Glob.lines);
let logLines = jqLogLines(timeOffset, Glob.lines);
loDisplayLogLines(timeOffset, logLines);
}
// returns diff between FCU time and rosout.log time (ros-fcu)
function getTimeOffset(lines) {
for (let i = 0; i < lines.length; ++i) {
let line = lines[i];
let index = line.indexOf('fcu_time ');
if (index !== -1) {
let fcu_time = line.substr(index + 'fcu_time '.length);
let ros_time = line.substr(0, line.indexOf(' '));
if (/^\d+\.\d{2}$/.test(fcu_time) && /^\d+\.\d+$/.test(ros_time)) { // xxxxxxx.yy
let fcuTimeFloat = Number.parseFloat(fcu_time);
let rosTimeFloat = Number.parseFloat(ros_time);
return (rosTimeFloat - fcuTimeFloat);
}
}
}
return null; // no fcu time ref found
}
// returns jquery dif with log lines
function jqLogLines(timeOffset, textLines) {
let mainDiv = $("<div></div>");
let gpsDate = "?";
let h2 = $("<h2></h2>")
mainDiv.append(h2);
for (let i = 0; i < textLines.length; ++i) {
let line = textLines[i];
if (line.trim() == "")
continue;
let severityClass='sUNKNOWN';
// time
let ros_time = line.substr(0, line.indexOf(' '));
let timeSpan = $("<span></span>");
if (/^\d+\.\d+$/.test(ros_time)) {
if (null !== timeOffset) {
let fcuTime = new Date((Number.parseFloat(ros_time) - timeOffset) * 1000);
let dateString = fcuTime.getHours() + ":" + fcuTime.getMinutes() + ":" + fcuTime.getSeconds()
+ "." + fcuTime.getMilliseconds();
timeSpan.html(dateString);
gpsDate = fcuTime.toLocaleDateString();
} else {
timeSpan.html(ros_time);
}
} else {
timeSpan.html("time?").addClass('logAnalyzeError');
}
// severity
let severityMatch = line.match(/^\d+\.\d+ (DEBUG|INFO|WARN|ERROR|FATAL) /);
if (severityMatch !== null && severityMatch.length >= 2) {
let severity = severityMatch[1];
severityClass = 's' + severity;
}
// text message
let msgSpan = $("<span></span>");
let msgIndex = line.indexOf('] ');
let msg = line.substr(msgIndex + '] '.length);
msgSpan.html(msg);
// handle status messages
let isStatus = (line.indexOf('fix:') !== -1 && line.indexOf('sat:') !== -1 && line.indexOf('state=') !== -1);
if (isStatus)
severityClass = "sSTATUS";
//div
let div = $("<div class='logline'></div>").append(timeSpan, " ", msgSpan).addClass(severityClass);
mainDiv.append(div);
}
h2.html(null===timeOffset ? 'Время не синхронизировано' : 'Время с GPS, дата ' + gpsDate);
return mainDiv;
}
/// \value stringified json object or standard type
/// \returns true if succeed
function saveLocal(name, value) {
// If the platform supports localStorage, then save the value
try {
localStorage.setItem(name, value);
return true;
}
catch(e) {
// Most likely cause of errors is a very old browser that doesn't support localStorage (fail silently)
console.warn("saving error");
return false;
}
}
/// \returns loaded value or specified defaultValue in case of error
function loadLocal(name, defaultValue = null) {
// If the platform supports localStorage, then load the selection
try {
let val = localStorage.getItem(name);
return (val === null) ? defaultValue : val;
}
catch(e) {
// Either no selection in localStorage or browser does not support localStorage (fail silently)
console.warn("can\'t load from localstorage");
return defaultValue;
}
}
function getSelectionStringFromZBLLMap() {
// Gets a string that represents the current selection
var selection = {};
for (var oll in zbllMap) {
selection[oll] = {};
for (var coll in zbllMap[oll]) {
selection[oll][coll] = {};
for (var zbll in zbllMap[oll][coll]) {
selection[oll][coll][zbll] = zbllMap[oll][coll][zbll].c;
}
}
}
return JSON.stringify(selection);
}
function setZBLLMapFromSelectionString(string) {
// Applies the selection in the given string to zbllMap
var selection = JSON.parse(string);
for (var oll in selection) if (zbllMap.hasOwnProperty(oll)) {
for (var coll in selection[oll]) if (zbllMap[oll].hasOwnProperty(coll)) {
for (var zbll in selection[oll][coll]) if (zbllMap[oll][coll].hasOwnProperty(zbll)) {
zbllMap[oll][coll][zbll].c = selection[oll][coll][zbll];
}
}
}
}
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment