forked from FOSS/BangleApps
Working GPS track recording - just need to fix the 'interface.html' to do the comms and do something with the data collected
parent
a59ea3eee6
commit
2bca5d4747
|
@ -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
|
||||
|
||||
|
|
21
apps.json
21
apps.json
|
@ -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",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.00: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AF/W63P54XVCigACF4IACC6QsUF44yKC44sUF5QyEC5QuWF5fPC5Yv/F/3OAYgviwNdroqCwF6wHP1V6vQwKF61eiAABGAQqBFYOkAYOr62rwADBF63V1ZOCmgvCmgvB1Wk1QEB0ml6vVGYOAF65PBDQU6F4rvH6qYB0ovXDQN66vW1hgBmhnBF5AwBAgOlSQgvR5+lC4fPFpAvECASSFd6weBABR3HSQYvpSQQvsUILWBF9XVX4OkF9bvd0ocB5/O1XOR5er0oIDF62AJoPPAYIzBF5QAFF6QoBE4OrVgKACGYIvI6GI1gvXFYN60q/D52k5ySFFwc0iEQrxfXK4TvGSQoTCwQuBAAJhDF6GIxHQ6vVGgQAESQfOTwQvZnQWBmgXDF4qSC5+kGYOr62sR4U0R6WsI4eCF5AzETwYYBwWC6AvlX4gAIR553DR5Ivhd4OCFwYvpAAwv/F7wMBF6ouLF5APHF54sMF44SNF5IsPF4gUSGQQXVAH4A/AH4A/ADY"))
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"recording":false,
|
||||
"file":0,
|
||||
"period":1
|
||||
}
|
|
@ -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()...
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name":"GPS Recorder",
|
||||
"icon":"*gpsrec",
|
||||
"src":"-gpsrec"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -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>
|
|
@ -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};
|
||||
})()
|
66
index.js
66
index.js
|
@ -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)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue