1
0
Fork 0

Working GPS track recording - just need to fix the 'interface.html' to do the comms and do something with the data collected

master
Gordon Williams 2020-02-07 17:16:45 +00:00
parent a59ea3eee6
commit 2bca5d4747
11 changed files with 325 additions and 6 deletions

View File

@ -193,6 +193,10 @@ about the app.
// iframe, and it must post back an 'app' structure
// like this one with 'storage','name' and 'id' set up
"interface": "interface.html", // if supplied, apps/interface.html is loaded in an
// iframe, and it may interact with the connected Bangle
// to retrieve information from it
"allow_emulator":true, // if 'app.js' will run in the emulator, set to true to
// add an icon to allow your app to be tested

View File

@ -173,7 +173,7 @@
"icon": "gpstime.png",
"version":"0.01",
"description": "Update the Bangle.js's clock based on the time from the GPS receiver",
"tags": "tool",
"tags": "tool,gps",
"storage": [
{"name":"+gpstime","url":"gpstime.json"},
{"name":"-gpstime","url":"gpstime.js"},
@ -185,7 +185,7 @@
"icon": "openlocation.png",
"version":"0.01",
"description": "Convert your current GPS location to a series of characters",
"tags": "tool,outdoors",
"tags": "tool,outdoors,gps",
"storage": [
{"name":"+openloc","url":"openlocation.json"},
{"name":"-openloc","url":"openlocation.js","evaluate":true}
@ -196,13 +196,28 @@
"icon": "speedo.png",
"version":"0.01",
"description": "Show the current speed according to the GPS",
"tags": "tool,outdoors",
"tags": "tool,outdoors,gps",
"storage": [
{"name":"+speedo","url":"speedo.json"},
{"name":"-speedo","url":"speedo.js"},
{"name":"*speedo","url":"speedo-icon.js","evaluate":true}
]
},
{ "id": "gpsrec",
"name": "GPS Recorder",
"icon": "app.png",
"version":"0.01",
"interface": "interface.html",
"description": "Application that allows you to record a GPS track. Can run in background",
"tags": "tool,outdoors,gps",
"storage": [
{"name":"+gpsrec","url":"app.json"},
{"name":"-gpsrec","url":"app.js"},
{"name":"@gpsrec","url":"app-settings.json","evaluate":true},
{"name":"*gpsrec","url":"app-icon.js","evaluate":true},
{"name":"=gpsrec","url":"widget.js"}
]
},
{ "id": "slevel",
"name": "Spirit Level",
"icon": "spiritlevel.png",

1
apps/gpsrec/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.00: New App!

1
apps/gpsrec/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AF/W63P54XVCigACF4IACC6QsUF44yKC44sUF5QyEC5QuWF5fPC5Yv/F/3OAYgviwNdroqCwF6wHP1V6vQwKF61eiAABGAQqBFYOkAYOr62rwADBF63V1ZOCmgvCmgvB1Wk1QEB0ml6vVGYOAF65PBDQU6F4rvH6qYB0ovXDQN66vW1hgBmhnBF5AwBAgOlSQgvR5+lC4fPFpAvECASSFd6weBABR3HSQYvpSQQvsUILWBF9XVX4OkF9bvd0ocB5/O1XOR5er0oIDF62AJoPPAYIzBF5QAFF6QoBE4OrVgKACGYIvI6GI1gvXFYN60q/D52k5ySFFwc0iEQrxfXK4TvGSQoTCwQuBAAJhDF6GIxHQ6vVGgQAESQfOTwQvZnQWBmgXDF4qSC5+kGYOr62sR4U0R6WsI4eCF5AzETwYYBwWC6AvlX4gAIR553DR5Ivhd4OCFwYvpAAwv/F7wMBF6ouLF5APHF54sMF44SNF5IsPF4gUSGQQXVAH4A/AH4A/ADY"))

View File

@ -0,0 +1,5 @@
{
"recording":false,
"file":0,
"period":1
}

115
apps/gpsrec/app.js Normal file
View File

@ -0,0 +1,115 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var settings = require("Storage").readJSON("@gpsrec")||{};
function getFN(n) {
return ".gpsrc"+n.toString(36);
}
function updateSettings() {
require("Storage").write("@gpsrec", settings);
if (WIDGETS["gpsrec"])
WIDGETS["gpsrec"].reload();
}
function showMainMenu() {
const mainmenu = {
'': { 'title': 'GPS Record' },
'RECORD': {
value: !!settings.recording,
format: v=>v?"On":"Off",
onchange: v => {
settings.recording = v;
updateSettings();
}
},
'File #': {
value: settings.file|0,
min: 0,
max: 35,
step: 1,
onchange: v => {
settings.recording = false;
settings.file = v;
updateSettings();
}
},
'Time Period': {
value: settings.period||1,
min: 1,
max: 60,
step: 1,
onchange: v => {
settings.recording = false;
settings.period = v;
updateSettings();
}
},
'View Tracks': viewTracks,
'< Back': ()=>{load();}
};
return E.showMenu(mainmenu);
}
function viewTracks() {
const menu = {
'': { 'title': 'GPS Tracks' }
};
var found = false;
for (var n=0;n<36;n++) {
var f = require("Storage").open(getFN(n),"r");
if (f.readLine()!==undefined) {
menu["Track "+n] = viewTrack.bind(null,n);
found = true;
}
}
if (!found)
menu["No Tracks found"] = function(){};
menu['< Back'] = showMainMenu;
return E.showMenu(menu);
}
function viewTrack(n) {
const menu = {
'': { 'title': 'GPS Track '+n }
};
var trackCount = 0;
var trackTime;
var f = require("Storage").open(getFN(n),"r");
var l = f.readLine();
if (l!==undefined) {
var c = l.split(",");
trackTime = new Date(0|c[0]);
}
while (l!==undefined) {
trackCount++;
// TODO: min/max/length of track?
l = f.readLine();
}
if (trackTime)
menu[" "+trackTime.toISOString().substr(0,16).replace("T"," ")] = function(){};
menu[trackCount+" records"] = function(){};
// TODO: option to draw it? Just scan through, project using min/max
menu['Erase'] = function() {
E.showPrompt("Delete Track?").then(function(v) {
if (v) {
settings.recording = false;
updateSettings();
var f = require("Storage").open(getFN(n),"r");
f.erase();
viewTracks();
} else
viewTrack(n);
});
};
menu['< Back'] = viewTracks;
print(menu);
return E.showMenu(menu);
}
showMainMenu();
// f = require("Storage").open(".gpsrc"+n,"r");
// f.readLine()...

5
apps/gpsrec/app.json Normal file
View File

@ -0,0 +1,5 @@
{
"name":"GPS Recorder",
"icon":"*gpsrec",
"src":"-gpsrec"
}

BIN
apps/gpsrec/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,37 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p>Hello!</p>
<script>
var __id = 0, __idlookup = [];
var Puck = {
eval : function(data,callback) {
__id++;
__idlookup[__id] = callback;
window.postMessage({type:"eval",data:data,id:__id});
},write : function(data,callback) {
__id++;
__idlookup[__id] = callback;
window.postMessage({type:"write",data:data,id:__id});
}
};
window.addEventListener("message", function(event) {
var msg = event.data;
if (msg.type=="evalrsp" || msg.type=="writersp") {
var cb = __idlookup[msg.id];
delete __idlookup[msg.id];
cb(msg.data);
}
}, false);
Puck.eval("E.getTemperature()",function(d) {
console.log("GOT: "+d);
});
</script>
</body>
</html>

76
apps/gpsrec/widget.js Normal file
View File

@ -0,0 +1,76 @@
(() => {
// add the width
var xpos = WIDGETPOS.tl;
WIDGETPOS.tl += 24;/* the widget width plus some extra pixel to keep distance to others */;
var settings = {};
var hasFix = false;
var fixToggle = false; // toggles once for each reading
var gpsTrack; // file for GPS track
var periodCtr = 0;
// draw your widget at xpos
function draw() {
g.reset();
g.setFont("4x6");
g.setFontAlign(0,0);
g.clearRect(xpos,0,xpos+23,23);
if (!settings.recording) {
g.setColor("#606060");
} else {
g.setColor("#ff0000");
if (hasFix) {
if (fixToggle) {
g.fillCircle(xpos+11,11,9);
g.setColor("#000000");
} else
g.drawCircle(xpos+11,11,9);
} else {
g.setColor(fixToggle ? "#ff0000" : "#7f0000");
g.drawString("NO",xpos+12,5);
g.drawString("FIX",xpos+12,19);
}
}
g.drawString("GPS",xpos+12,12);
}
function onGPS(fix) {
hasFix = fix.fix;
fixToggle = !fixToggle;
draw();
if (hasFix) {
periodCtr--;
if (periodCtr<=0) {
periodCtr = settings.period;
if (gpsTrack) gpsTrack.write([
fix.time.getTime()|0,
fix.lat.toFixed(5),
fix.lon.toFixed(5),
fix.alt
].join(",")+"\n");
}
}
}
// Called by the GPS app to reload settings and decide what's
function reload() {
settings = require("Storage").readJSON("@gpsrec")||{};
settings.period = settings.period||1;
settings.file |= 0;
Bangle.removeListener('GPS',onGPS);
if (settings.recording) {
Bangle.on('GPS',onGPS);
Bangle.setGPSPower(1);
var n = settings.file.toString(36);
gpsTrack = require("Storage").open(".gpsrc"+n,"a");
} else {
Bangle.setGPSPower(0);
gpsTrack = undefined;
}
draw();
}
reload();
// add the widget
WIDGETS["gpsrec"]={draw:draw,reload:reload};
})()

View File

@ -86,6 +86,8 @@ function showPrompt(title, text) {
});
}
function handleCustomApp(app) {
// Pops up an IFRAME that allows an app to be customised
if (!app.custom) throw new Error("App doesn't have custom HTML");
return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active">
<a href="#close" class="modal-overlay " aria-label="Close"></a>
@ -119,6 +121,58 @@ function handleCustomApp(app) {
}, false);
});
}
function handleAppInterface(app) {
// IFRAME interface window that can be used to get data from the app
if (!app.interface) throw new Error("App doesn't have interface HTML");
return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active">
<a href="#close" class="modal-overlay " aria-label="Close"></a>
<div class="modal-container" style="height:100%">
<div class="modal-header">
<a href="#close" class="btn btn-clear float-right" aria-label="Close"></a>
<div class="modal-title h5">${escapeHtml(app.name)}</div>
</div>
<div class="modal-body" style="height:100%">
<div class="content" style="height:100%">
<iframe style="width:100%;height:100%;border:0px;">
</div>
</div>
</div>
</div>`);
document.body.append(modal);
htmlToArray(modal.getElementsByTagName("a")).forEach(button => {
button.addEventListener("click",event => {
event.preventDefault();
modal.remove();
//reject("Window closed");
});
});
var iframe = modal.getElementsByTagName("iframe")[0];
var iwin = iframe.contentWindow;
iwin.addEventListener("message", function(event) {
var msg = event.data;
if (msg.type=="eval") {
Puck.eval(msg.data, function(result) {
iwin.postMessage({
type : "evalrsp",
data : result,
id : msg.id
});
});
} else if (msg.type=="write") {
Puck.write(msg.data, function() {
iwin.postMessage({
type : "writersp",
id : msg.id
});
});
}
}, false);
iframe.src = `apps/${app.id}/${app.interface}`
});
}
// =========================================== Top Navigation
function showTab(tabname) {
htmlToArray(document.querySelectorAll("#tab-navigate .tab-item")).forEach(tab => {
@ -162,6 +216,7 @@ function refreshLibrary() {
<p class="tile-subtitle">${escapeHtml(app.description)}</p>
</div>
<div class="tile-action">
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
<button class="btn btn-link btn-action btn-lg ${app.allow_emulator?"":"d-hide"}" appid="${app.id}" title="Try in Emulator"><i class="icon icon-share"></i></button>
<button class="btn btn-link btn-action btn-lg ${version.canUpdate?"":"d-hide"}" appid="${app.id}" title="Update App"><i class="icon icon-refresh"></i></button>
<button class="btn btn-link btn-action btn-lg ${!appInstalled?"":"d-hide"}" appid="${app.id}" title="Upload App"><i class="icon icon-upload"></i></button>
@ -236,6 +291,8 @@ function refreshLibrary() {
icon.classList.remove("icon-refresh");
icon.classList.add("loading");
updateApp(app);
} else if (icon.classList.contains("icon-download")) {
handleAppInterface(app);
}
});
});
@ -272,6 +329,7 @@ function updateApp(app) {
});
}
function appNameToApp(appName) {
var app = appJSON.find(app=>app.id==appName);
if (app) return app;
@ -301,9 +359,9 @@ function refreshMyApps() {
var panelbody = document.querySelector("#myappscontainer .panel-body");
var tab = document.querySelector("#tab-myappscontainer a");
tab.setAttribute("data-badge", appsInstalled.length);
panelbody.innerHTML = appsInstalled.map(appJSON => {
var app = appNameToApp(appJSON.id);
var version = getVersionInfo(app, appJSON);
panelbody.innerHTML = appsInstalled.map(appInstalled => {
var app = appNameToApp(appInstalled.id);
var version = getVersionInfo(app, appInstalled);
return `<div class="tile column col-6 col-sm-12 col-xs-12">
<div class="tile-icon">
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure>
@ -313,6 +371,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
<p class="tile-subtitle">${escapeHtml(app.description)}</p>
</div>
<div class="tile-action">
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
<button class="btn btn-link btn-action btn-lg ${version.canUpdate?'':'d-hide'}" appid="${app.id}" title="Update App"><i class="icon icon-refresh"></i></button>
<button class="btn btn-link btn-action btn-lg" appid="${app.id}" title="Remove App"><i class="icon icon-delete"></i></button>
</div>
@ -328,6 +387,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
// check icon to figure out what we should do
if (icon.classList.contains("icon-delete")) removeApp(app);
if (icon.classList.contains("icon-refresh")) updateApp(app);
if (icon.classList.contains("icon-download")) handleAppInterface(app)
});
});
}