recorder 0.02: Use 'recorder.log..' rather than 'record.log..'

+      Fix interface.html
pull/877/head
Gordon Williams 2021-11-02 20:06:27 +00:00
parent 3a5bb4aae0
commit 47ba763a9d
5 changed files with 183 additions and 103 deletions

View File

@ -624,7 +624,7 @@
"id": "recorder",
"name": "Recorder (BETA)",
"shortName": "Recorder",
"version": "0.01",
"version": "0.02",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget",

View File

@ -1 +1,3 @@
0.01: New App!
0.02: Use 'recorder.log..' rather than 'record.log..'
Fix interface.html

View File

@ -13,7 +13,7 @@ function loadSettings() {
var changed = false;
if (!settings.file) {
changed = true;
settings.file = "record.log0.csv";
settings.file = "recorder.log0.csv";
}
if (!Array.isArray(settings.record)) {
settings.record = ["gps"];
@ -31,7 +31,7 @@ function updateSettings() {
}
function getTrackNumber(filename) {
return parseInt(filename.match(/^record\.log(.*)\.csv$/)[1]||0);
return parseInt(filename.match(/^recorder\.log(.*)\.csv$/)[1]||0);
}
function showMainMenu() {
@ -73,7 +73,7 @@ function showMainMenu() {
step: 1,
onchange: v => {
settings.recording = false; // stop recording if we change anything
settings.file = "record.log"+v+".csv";
settings.file = "recorder.log"+v+".csv";
updateSettings();
}
},
@ -105,7 +105,7 @@ function viewTracks() {
'': { 'title': 'Tracks' }
};
var found = false;
require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{
require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{
found = true;
menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false);
});

View File

@ -3,7 +3,6 @@
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<b>THIS IS NOT CURRENTLY IMPLEMENTED</b>
<div id="tracks"></div>
<script src="../../core/lib/interface.js"></script>
@ -14,14 +13,35 @@ function saveKML(track,title) {
var kml = `<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Placemark>
<name>${title}</name>
<LineString>
<coordinates>
${track.map(pt=>[pt.lon, pt.lat, pt.alt].join(",")).join("\n ")}
</coordinates>
</LineString>
</Placemark>
<Schema id="schema">
${track[0].Heartrate!==undefined ? `<gx:SimpleArrayField name="heartrate" type="int">
<displayName>Heart Rate</displayName>
</gx:SimpleArrayField>`:``}
${track[0].Steps!==undefined ? `<gx:SimpleArrayField name="steps" type="int">
<displayName>Step Count</displayName>`:``}
</gx:SimpleArrayField>
</Schema>
<Folder>
<name>Tracks</name>
<Placemark>
<name>${title}</name>
<gx:Track>
${track.map(pt=>` <when>${pt.Time.toISOString()}</when>\n`).join("")}
${track.map(pt=>` <gx:coord>${pt.Longitude} ${pt.Latitude} ${pt.Altitude}</gx:coord>\n`).join("")}
<ExtendedData>
<SchemaData schemaUrl="#schema">
${track[0].Heartrate!==undefined ? `<gx:SimpleArrayData name="heartrate">
${track.map(pt=>` <gx:value>${0|pt.Heartrate}</gx:value>\n`).join("")}
</gx:SimpleArrayData>`:``}
${track[0].Steps!==undefined ? `<gx:SimpleArrayData name="steps">
${track.map(pt=>` <gx:value>${0|pt.Steps}</gx:value>\n`).join("")}
</gx:SimpleArrayData>`:``}
</SchemaData>
</ExtendedData>
</gx:Track>
</Placemark>
</Folder>
</Document>
</kml>`;
var a = document.createElement("a"),
@ -39,6 +59,7 @@ function saveKML(track,title) {
function saveGPX(track, title) {
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<gpx creator="Bangle.js" version="1.1">
<metadata>
<time>${track[0].date.toISOString()}</time>
@ -48,11 +69,19 @@ function saveGPX(track, title) {
<trkseg>`;
track.forEach(pt=>{
gpx += `
<trkpt lat="${pt.lat}" lon="${pt.lon}">
<ele>${pt.alt}</ele>
<time>${pt.date.toISOString()}</time>
<trkpt lat="${pt.Latitude}" lon="${pt.Longitude}">
<ele>${pt.Altitude}</ele>
<time>${pt.Time.toISOString()}</time>
<extensions>
<gpxtpx:TrackPointExtension>
${pt.Heartrate ? `<gpxtpx:hr>${pt.Heartrate}</gpxtpx:hr>`:``}
${""/*<gpxtpx:distance>...</gpxtpx:distance>*/}
${""/* <gpxtpx:cad>65</gpxtpx:cad> */}
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>`;
});
// https://www8.garmin.com/xmlschemas/TrackPointExtensionv1.xsd
gpx += `
</trkseg>
</trk>
@ -70,104 +99,153 @@ function saveGPX(track, title) {
}, 0);
}
function trackLineToObject(l, hasTrackNumber) {
function saveCSV(track, title) {
var headers = Object.keys(track[0]);
var csv = headers.join(",")+"\n";
track.forEach(t=>{
csv += headers.map(k=>{
if (t[k] instanceof Date) return t[k].toISOString();
return t[k];
}).join(",")+"\n";
});
Util.saveCSV(title, csv);
}
function trackLineToObject(headers, l) {
var t = l.trim().split(",");
var n = hasTrackNumber ? 1 : 0;
var o = {
date : new Date(parseInt(t[n+0])),
lat : parseFloat(t[n+1]),
lon : parseFloat(t[n+2]),
alt : parseFloat(t[n+3])
};
if (hasTrackNumber)
o.number = t[0];
var o = {};
headers.forEach((header,i) => o[header] = t[i]);
if (o.Time) o.Time = new Date(o.Time*1000);
return o;
}
function downloadTrack(trackid, callback) {
function downloadTrack(filename, callback) {
Util.showModal("Downloading Track...");
Util.readStorageFile(`.gpsrc${trackid.toString(36)}`,data=>{
Util.readStorageFile(filename,data=>{
Util.hideModal();
var track = data.trim().split("\n").map(l=>trackLineToObject(l,false));
var lines = data.trim().split("\n");
var headers = lines.shift().split(",");
var track = lines.map(l=>trackLineToObject(headers, l));
callback(track);
});
}
function getTrackList() {
Util.showModal("Loading Tracks...");
Util.showModal("Loading Track List...");
domTracks.innerHTML = "";
Puck.write(`\x10(function() {
Bluetooth.println("");
for (var n=0;n<36;n++) {
var f = require("Storage").open(".gpsrc"+n.toString(36),"r");
var l = f.readLine();
Bluetooth.println((l!==undefined) ? (n + "," + l.trim()) : "");
}
})()\n`,tracklist=>{
var trackLines = tracklist.trim().split("\n").filter(l=>l!="");
var html = `<div class="container">
<div class="columns">\n`;
trackLines.forEach(l => {
var track = trackLineToObject(l, true /*has track number*/);
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">Track ${track.number}</div>
<div class="card-subtitle text-gray">${track.date.toString().substr(0,24)}</div>
</div>
<div class="card-image">
<iframe
width="100%"
height="250"
frameborder="0" style="border:0"
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyBxTcwrrVOh2piz7EmIs1Xn4FsRxJWeVH4&q=${track.lat},${track.lon}&zoom=10" allowfullscreen>
</iframe>
</div>
<div class="card-body"></div>
<div class="card-footer">
<button class="btn btn-primary" trackid="${track.number}" task="downloadkml">Download KML</button>
<button class="btn btn-primary" trackid="${track.number}" task="downloadgpx">Download GPX</button>
<button class="btn btn-default" trackid="${track.number}" task="delete">Delete</button>
</div>
</div>
`;
});
if (trackLines.length==0) {
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">No tracks</div>
<div class="card-subtitle text-gray">No GPS tracks found</div>
</div>
</div>
`;
}
html += `
</div>
</div>`;
domTracks.innerHTML = html;
Util.hideModal();
var buttons = domTracks.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var trackid = parseInt(button.getAttribute("trackid"));
var task = button.getAttribute("task");
if (task=="delete") {
Util.showModal("Deleting Track...");
Util.eraseStorageFile(`.gpsrc${trackid.toString(36)}`,()=>{
Util.hideModal();
getTrackList();
Puck.eval(`require("Storage").list(/^recorder\\.log.*\\.csv$/,{sf:1})`,files=>{
var trackList = [];
var promise = Promise.resolve();
/* For each file ask Bangle.js for info. Since we now start recording even
before we have a GPS trace, we get the Bangle to do a *quick* search for us
to see if it found any data first. */
files.forEach(filename => {
promise = promise.then(()=>new Promise(resolve => {
var trackNo = filename.match(/^recorder\.log(.*)\.csv$/)[1];
Util.showModal(`Loading Track ${trackNo}...`);
Puck.eval(`(function(fn) {
var f = require("Storage").open(fn,"r");
var headers = f.readLine();
var data = f.readLine();
var lIdx = headers.split(",").indexOf("Latitude");
if (lIdx >= 0) {
var tries = 100;
var l = data;
while (l && l.split(",")[lIdx]=="" && tries++)
l = f.readLine();
if (l) data = l;
}
return {headers:headers,l:data};
})(${JSON.stringify(filename)})`, trackInfo=>{
console.log(filename," => ",trackInfo);
trackInfo.headers = trackInfo.headers.split(",");
trackList.push({
filename : filename,
number : trackNo,
info : trackInfo
});
resolve();
});
}));
});
// ================================================
// When 'promise' completes we now have all the info in trackList
promise.then(() => {
var html = `<div class="container">
<div class="columns">\n`;
trackList.forEach(track => {
var trackData = trackLineToObject(track.info.headers, track.info.l);
console.log("track", track);
console.log("trackData", trackData);
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">Track ${track.number}</div>
<div class="card-subtitle text-gray">${trackData.Time.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</div>
</div>
${trackData.Latitude ? `
<div class="card-image">
<iframe
width="100%"
height="250"
frameborder="0" style="border:0"
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyBxTcwrrVOh2piz7EmIs1Xn4FsRxJWeVH4&q=${track.lat},${track.lon}&zoom=10" allowfullscreen>
</iframe>
</div><div class="card-body"></div>` : `<div class="card-body">No GPS info</div>`}
<div class="card-footer">
${trackData.Latitude ? `
<button class="btn btn-primary" filename="${track.filename}" trackid="${track.number}" task="downloadkml">Download KML</button>
<button class="btn btn-primary" filename="${track.filename}" trackid="${track.number}" task="downloadgpx">Download GPX</button>
` : ``}
<button class="btn btn-primary" filename="${track.filename}" trackid="${track.number}" task="downloadcsv">Download CSV</button>
<button class="btn btn-default" filename="${track.filename}" trackid="${track.number}" task="delete">Delete</button>
</div>
</div>
`;
});
if (trackList.length==0) {
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">No tracks</div>
<div class="card-subtitle text-gray">No GPS tracks found</div>
</div>
</div>
`;
}
if (task=="downloadkml") {
downloadTrack(trackid, track => saveKML(track, `Bangle.js Track ${trackid}`));
}
if (task=="downloadgpx") {
downloadTrack(trackid, track => saveGPX(track, `Bangle.js Track ${trackid}`));
}
});
}
})
html += `
</div>
</div>`;
domTracks.innerHTML = html;
Util.hideModal();
var buttons = domTracks.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var filename = button.getAttribute("filename");
var trackid = parseInt(button.getAttribute("trackid"));
if (!filename || trackid===undefined) return;
var task = button.getAttribute("task");
if (task=="delete") {
Util.showModal("Deleting Track...");
Util.eraseStorageFile(filename,()=>{
Util.hideModal();
getTrackList();
});
}
if (task=="downloadkml") {
downloadTrack(filename, track => saveKML(track, `Bangle.js Track ${trackid}`));
}
if (task=="downloadgpx") {
downloadTrack(filename, track => saveGPX(track, `Bangle.js Track ${trackid}`));
}
if (task=="downloadcsv") {
downloadTrack(filename, track => saveCSV(track, `Bangle.js Track ${trackid}`));
}
});
}
});
});
}
function onInit() {

View File

@ -7,7 +7,7 @@
function loadSettings() {
var settings = require("Storage").readJSON("recorder.json",1)||{};
settings.period = settings.period||10;
if (!settings.file || !settings.file.startsWith("record.log"))
if (!settings.file || !settings.file.startsWith("recorder.log"))
settings.recording = false;
return settings;
}