mirror of https://github.com/espruino/BangleApps
recorder 0.02: Use 'recorder.log..' rather than 'record.log..'
+ Fix interface.htmlpull/877/head
parent
3a5bb4aae0
commit
47ba763a9d
|
@ -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",
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Use 'recorder.log..' rather than 'record.log..'
|
||||
Fix interface.html
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue