mirror of https://github.com/espruino/BangleApps
Merge remote-tracking branch 'origin/develop' into develop
commit
dbf68a8208
12
README.md
12
README.md
|
@ -394,6 +394,18 @@ It should also add `app.json` to `data`, to make sure it is cleaned up when the
|
|||
},
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
You can include any of [Espruino's modules](https://www.espruino.com/Modules) as
|
||||
normal with `require("modulename")`. If you want to develop your own module for your
|
||||
app(s) then you can do that too. Just add the module into the `modules` folder
|
||||
then you can use it from your app as normal.
|
||||
|
||||
You won't be able to develop apps using your own modules with the IDE,
|
||||
so instead we'd recommend you write your module to a Storage File called
|
||||
`modulename` on Bangle.js. You can then develop your app as normal on Bangle.js
|
||||
from the IDE.
|
||||
|
||||
## Coding hints
|
||||
|
||||
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
|
||||
|
|
23
apps.json
23
apps.json
|
@ -65,7 +65,7 @@
|
|||
{ "id": "locale",
|
||||
"name": "Languages",
|
||||
"icon": "locale.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "Translations for different countries",
|
||||
"tags": "tool,system,locale,translate",
|
||||
"type": "locale",
|
||||
|
@ -139,9 +139,10 @@
|
|||
{ "id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"version":"0.17",
|
||||
"version":"0.18",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||
"tags": "tool,system,android,widget",
|
||||
"readme": "README.md",
|
||||
"type":"widget",
|
||||
"dependencies": { "notify":"type" },
|
||||
"storage": [
|
||||
|
@ -362,14 +363,15 @@
|
|||
{ "id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.13",
|
||||
"version":"0.16",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
"storage": [
|
||||
{"name":"gpsrec.app.js","url":"app.js"},
|
||||
{"name":"gpsrec.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"gpsrec.wid.js","url":"widget.js"}
|
||||
{"name":"gpsrec.wid.js","url":"widget.js"},
|
||||
{"name":"gpsrec.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gpsrec.json"},
|
||||
|
@ -379,13 +381,13 @@
|
|||
{ "id": "gpsnav",
|
||||
"name": "GPS Navigation",
|
||||
"icon": "icon.png",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"readme": "README.md",
|
||||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"gpsnav.app.js","url":"app.js"},
|
||||
{"name":"gpsnav.app.js","url":"app.min.js"},
|
||||
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
|
||||
{"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
|
@ -941,12 +943,12 @@
|
|||
]
|
||||
},
|
||||
{ "id": "assistedgps",
|
||||
"name": "Assisted GPS Update",
|
||||
"name": "Assisted GPS Update (AGPS)",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Downloads assisted GPS data to Bangle.js for faster GPS startup and more accurate fixes",
|
||||
"custom": "custom.html",
|
||||
"tags": "tool,outdoors",
|
||||
"tags": "tool,outdoors,agps",
|
||||
"type": "RAM",
|
||||
"storage": [ ]
|
||||
},
|
||||
|
@ -1325,11 +1327,12 @@
|
|||
"name": "OpenStreetMap",
|
||||
"shortName":"OpenStMap",
|
||||
"icon": "app.png",
|
||||
"version":"0.03",
|
||||
"version":"0.05",
|
||||
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
|
||||
"tags": "outdoors,gps",
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
{"name":"openstmap","url":"openstmap.js"},
|
||||
{"name":"openstmap.app.js","url":"app.js"},
|
||||
{"name":"openstmap.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
|
@ -2290,7 +2293,7 @@
|
|||
"name": "File manager",
|
||||
"shortName":"FileManager",
|
||||
"icon": "icons8-filing-cabinet-48.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files",
|
||||
"tags": "tools",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -84,15 +84,13 @@
|
|||
var chunkSize = 128;
|
||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
js += "\x10E.showMessage('Uploading...','AGPS');function p(n) {g.fillRect(0,g.getHeight()-80,g.getWidth()*n,g.getHeight()-60);}";
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
var chunk = bin.substr(i,chunkSize);
|
||||
js += `\x10p(${Math.round(100*i/bin.length)/100});Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
}
|
||||
js += "\x10p(1);\n"; // finish progress bar
|
||||
return js;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New app!
|
||||
0.02: Improve handling of large amounts of files (fix #579)
|
||||
|
|
|
@ -10,12 +10,12 @@ function delete_file(fn) {
|
|||
E.showPrompt("Delete\n"+fn+"?", {buttons: {"No":false, "Yes":true}}).then(function(v) {
|
||||
if (v) {
|
||||
if (fn.charCodeAt(fn.length-1)==1) {
|
||||
var fh = STOR.open(fn.substr(0, fn.length-1), "w");
|
||||
var fh = STOR.open(fn.substr(0, fn.length-1), "r");
|
||||
fh.erase();
|
||||
}
|
||||
else STOR.erase(fn);
|
||||
}
|
||||
}).then(function() { files=get_pruned_file_list(); }).then(drawMenu);
|
||||
}).then(function() { filed=[];files=get_pruned_file_list(); }).then(drawMenu);
|
||||
}
|
||||
|
||||
function get_length(fn) {
|
||||
|
@ -90,10 +90,13 @@ function drawMenu() {
|
|||
}
|
||||
|
||||
function get_pruned_file_list() {
|
||||
var fl = STOR.list(/^[^\.]/);
|
||||
// get storagefile list
|
||||
var sf = STOR.list(/\1$/).map(s=>s.slice(0,-1));
|
||||
var sffilter = f=>!sf.includes(f.slice(0,-1)) || f.endsWith("\1");
|
||||
// get files - put '.' last
|
||||
var fl = STOR.list(/^[^\.]/).filter(sffilter);
|
||||
fl.sort();
|
||||
fl = fl.concat(STOR.list(/^\./));
|
||||
fl = fl.filter(f => (f.charCodeAt(f.length-1)>31 || f.charCodeAt(f.length-1)<2));
|
||||
fl = fl.concat(STOR.list(/^\./).filter(sffilter).sort());
|
||||
return fl;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,3 +17,4 @@
|
|||
0.16: Handle dismissing notifications on the phone
|
||||
Nicer display of alarm clock notifications
|
||||
0.17: Modified music notification for updated 'notify' library
|
||||
0.18: Added reporting of step count and HRM (new Gadgetbridges can now log this)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
Gadgetbridge
|
||||
=============
|
||||
|
||||
This widget allows your Bangle.js to communicate with the Gadgetbridge app on an Android phone.
|
||||
|
||||
Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/).
|
||||
|
||||
This app supports:
|
||||
|
||||
* Displaying Notifications
|
||||
* Song display and control
|
||||
* Call answering
|
||||
* Find My Phone / Find My Bangle
|
||||
* Activity reporting
|
||||
|
||||
You can also add [the weather widget](https://banglejs.com/apps/#weather)
|
||||
|
||||
|
||||
Notifications
|
||||
-------------
|
||||
|
||||
By default a notification at the top of the screen is displayed. If you'd like a fullscreen notification
|
||||
(which will involve leaving the current app) then install [Fullscreen Notifications](https://banglejs.com/apps/#notifyfs)
|
||||
|
||||
|
||||
Song display and control
|
||||
------------------------
|
||||
|
||||
When the Song Display notification is showing on the screen and a song is playing, you
|
||||
can swipe left or right on the screen to go to the next or previous song.
|
||||
|
||||
|
||||
Find My Phone
|
||||
-------------
|
||||
|
||||
Go to `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Find Phone`, `On`
|
||||
|
||||
If in range and connected your phone should start ringing.
|
||||
|
||||
|
||||
Find My Bangle
|
||||
-------------
|
||||
|
||||
Onyour phone `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Find Phone`, `On`
|
||||
|
||||
If in range and connected your phone should start ringing.
|
||||
|
||||
|
||||
Activity reporting
|
||||
------------------
|
||||
|
||||
You'll need a Gadgetbridge release *after* version 0.50.0 for Actvity Reporting to be enabled.
|
||||
|
||||
By default heart rate isn't reported, but it can be enabled from `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Record HRM`
|
|
@ -12,7 +12,7 @@
|
|||
function updateSetting(setting, value) {
|
||||
let settings = require('Storage').readJSON("gbridge.json", true) || {};
|
||||
settings[setting] = value
|
||||
require('Storage').write('gbridge.json', settings);
|
||||
require('Storage').writeJSON('gbridge.json', settings);
|
||||
}
|
||||
function setIcon(visible) {
|
||||
updateSetting('showIcon', visible);
|
||||
|
@ -30,6 +30,11 @@
|
|||
onchange: setIcon
|
||||
},
|
||||
"Find Phone" : function() { E.showMenu(findPhone); },
|
||||
"Record HRM" : {
|
||||
value: settings().hrm,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => updateSetting('hrm', v)
|
||||
},
|
||||
"< Back" : back,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(() => {
|
||||
// Music handling
|
||||
const state = {
|
||||
music: "stop",
|
||||
|
||||
|
@ -10,13 +11,17 @@
|
|||
|
||||
scrollPos: 0
|
||||
};
|
||||
// activity reporting
|
||||
var currentSteps = 0, lastSentSteps=0;
|
||||
var activityInterval;
|
||||
var hrmTimeout;
|
||||
|
||||
function settings() {
|
||||
let settings = require('Storage').readJSON("gbridge.json", true) || {};
|
||||
if (!("showIcon" in settings)) {
|
||||
settings.showIcon = true;
|
||||
}
|
||||
return settings
|
||||
return settings;
|
||||
}
|
||||
|
||||
function gbSend(message) {
|
||||
|
@ -144,6 +149,45 @@
|
|||
},2000);
|
||||
}
|
||||
|
||||
function handleActivityEvent(event) {
|
||||
var s = settings();
|
||||
// handle setting activity interval
|
||||
if (s.activityInterval===undefined ||
|
||||
s.activityInterval<30)
|
||||
s.activityInterval = 3*60; // 3 minutes default
|
||||
if (event.int) {
|
||||
if (event.int<30) event.int = 30; // min 30 secs
|
||||
s.activityInterval = event.int;
|
||||
require('Storage').writeJSON("gbridge.json", s);
|
||||
}
|
||||
// set up interval/HRM to handle activity data
|
||||
var interval = s.activityInterval;
|
||||
var realtime = event.hrm || event.stp;
|
||||
if (activityInterval)
|
||||
clearInterval(activityInterval);
|
||||
activityInterval = undefined;
|
||||
if (s.hrm) Bangle.setHRMPower(1);
|
||||
if (s.hrm) {
|
||||
if (realtime) {
|
||||
// if realtime reporting, leave HRM on and use that to trigger events
|
||||
hrmTimeout = undefined;
|
||||
} else {
|
||||
// else trigger it manually every so often
|
||||
hrmTimeout = 5;
|
||||
activityInterval = setInterval(function() {
|
||||
hrmTimeout = 5;
|
||||
Bangle.setHRMPower(1);
|
||||
}, interval*1000);
|
||||
}
|
||||
} else {
|
||||
// no HRM - manually push data
|
||||
if (realtime) interval=10;
|
||||
activityInterval = setInterval(function() {
|
||||
sendActivity(-1);
|
||||
}, interval*1000);
|
||||
}
|
||||
}
|
||||
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
switch (event.t) {
|
||||
|
@ -163,6 +207,9 @@
|
|||
case "find":
|
||||
handleFindEvent(event);
|
||||
break;
|
||||
case "act":
|
||||
handleActivityEvent(event);
|
||||
break;
|
||||
}
|
||||
if(_GB)setTimeout(_GB,0,event);
|
||||
};
|
||||
|
@ -201,14 +248,39 @@
|
|||
}
|
||||
}
|
||||
|
||||
WIDGETS["gbridgew"] = {area: "tl", width: 24, draw: draw, reload: reload};
|
||||
reload();
|
||||
|
||||
function sendBattery() {
|
||||
gbSend({ t: "status", bat: E.getBattery() });
|
||||
}
|
||||
|
||||
// Send a summary of activity to Gadgetbridge
|
||||
function sendActivity(hrm) {
|
||||
var steps = currentSteps - lastSentSteps;
|
||||
lastSentSteps = 0;
|
||||
gbSend({ t: "act", stp: steps, hrm:hrm });
|
||||
}
|
||||
|
||||
// Battery monitor
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
sendBattery();
|
||||
// Activity monitor
|
||||
Bangle.on("step", s => {
|
||||
if (!lastSentSteps)
|
||||
lastSentSteps = s-1;
|
||||
currentSteps = s;
|
||||
});
|
||||
Bangle.on('HRM',function(hrm) {
|
||||
var ok = hrm.confidence>80;
|
||||
if (hrmTimeout!==undefined) hrmTimeout--;
|
||||
if (ok || hrmTimeout<=0) {
|
||||
if (hrmTimeout!==undefined)
|
||||
Bangle.setHRMPower(0);
|
||||
sendActivity(hrm.confidence>20 ? hrm.bpm : -1);
|
||||
}
|
||||
});
|
||||
handleActivityEvent({}); // kicks off activity reporting
|
||||
|
||||
// Finally add widget
|
||||
WIDGETS["gbridgew"] = {area: "tl", width: 24, draw: draw, reload: reload};
|
||||
reload();
|
||||
})();
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
0.02: Add SCREENACCESS interface
|
||||
0.03: Add Waypoint Editor
|
||||
0.04: Fix great circle formula
|
||||
0.05: Use locale for speed and distance + fix Vector font sizes
|
||||
|
||||
|
|
|
@ -11,11 +11,12 @@ function flip(b,y) {
|
|||
var brg=0;
|
||||
var wpindex=0;
|
||||
const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||
var loc = require("locale");
|
||||
|
||||
function drawCompass(course) {
|
||||
if (!candraw) return;
|
||||
buf.setColor(1);
|
||||
buf.setFont("Vector",16);
|
||||
buf.setFont("Vector",24);
|
||||
var start = course-90;
|
||||
if (start<0) start+=360;
|
||||
buf.fillRect(28,45,212,49);
|
||||
|
@ -52,19 +53,13 @@ function drawCompass(course) {
|
|||
var heading = 0;
|
||||
function newHeading(m,h){
|
||||
var s = Math.abs(m - h);
|
||||
var delta = 1;
|
||||
var delta = (m>h)?1:-1;
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s<2) return h;
|
||||
if (m > h){
|
||||
if (s >= 180) { delta = -1; s = 360 - s;}
|
||||
} else if (m <= h){
|
||||
if (s < 180) delta = -1;
|
||||
else s = 360 -s;
|
||||
}
|
||||
delta = delta * (1 + Math.round(s/15));
|
||||
heading+=delta;
|
||||
if (heading<0) heading += 360;
|
||||
if (heading>360) heading -= 360;
|
||||
return heading;
|
||||
var hd = h + delta*(1 + Math.round(s/5));
|
||||
if (hd<0) hd+=360;
|
||||
if (hd>360)hd-= 360;
|
||||
return hd;
|
||||
}
|
||||
|
||||
var course =0;
|
||||
|
@ -93,27 +88,28 @@ function bearing(a,b){
|
|||
}
|
||||
|
||||
function distance(a,b){
|
||||
var dsigma = Math.acos(Math.sin(radians(a.lat))*Math.sin(radians(b.lat))+Math.cos(radians(a.lat))*Math.cos(radians(b.lat))*Math.cos(radians(a.lon-b.lon)));
|
||||
return Math.round(dsigma*6371000);
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
|
||||
}
|
||||
|
||||
var selected = false;
|
||||
|
||||
function drawN(){
|
||||
var txt = loc.speed(speed);
|
||||
buf.setColor(1);
|
||||
buf.setFont("6x8",2);
|
||||
buf.drawString("o",100,0);
|
||||
buf.setFont("6x8",1);
|
||||
buf.drawString("kph",220,40);
|
||||
buf.setFont("Vector",40);
|
||||
buf.drawString(txt.substring(txt.length-3),220,40);
|
||||
buf.setFont("Vector",48);
|
||||
var cs = course.toString();
|
||||
cs = course<10?"00"+cs : course<100 ?"0"+cs : cs;
|
||||
buf.drawString(cs,10,0);
|
||||
var txt = (speed<10) ? speed.toFixed(1) : Math.round(speed);
|
||||
buf.drawString(txt,140,4);
|
||||
buf.drawString(txt.substring(0,txt.length-3),140,4);
|
||||
flip(buf,Yoff+70);
|
||||
buf.setColor(1);
|
||||
buf.setFont("Vector",20);
|
||||
buf.setFont("Vector",24);
|
||||
var bs = brg.toString();
|
||||
bs = brg<10?"00"+bs : brg<100 ?"0"+bs : bs;
|
||||
buf.setColor(3);
|
||||
|
@ -123,10 +119,7 @@ function drawN(){
|
|||
buf.drawString(wp.name,140,0);
|
||||
buf.setColor(1);
|
||||
buf.drawString(bs,60,0);
|
||||
if (dist<1000)
|
||||
buf.drawString(dist.toString()+"m",60,30);
|
||||
else
|
||||
buf.drawString((dist/1000).toFixed(2)+"Km",60,30);
|
||||
buf.drawString(loc.distance(dist),60,30);
|
||||
flip(buf,Yoff+130);
|
||||
g.setFont("6x8",1);
|
||||
g.setColor(0,0,0);
|
||||
|
@ -165,7 +158,7 @@ function stopdraw() {
|
|||
function startTimers() {
|
||||
candraw=true;
|
||||
intervalRefSec = setInterval(function() {
|
||||
newHeading(course,heading);
|
||||
heading = newHeading(course,heading);
|
||||
if (course!=heading) drawCompass(heading);
|
||||
},200);
|
||||
}
|
||||
|
@ -227,7 +220,7 @@ function nextwp(inc){
|
|||
}
|
||||
|
||||
function doselect(){
|
||||
if (selected && waypoints[wpindex].lat===undefined && savedfix.fix) {
|
||||
if (selected && wpindex!=0 && waypoints[wpindex].lat===undefined && savedfix.fix) {
|
||||
waypoints[wpindex] ={name:"@"+wp.name, lat:savedfix.lat, lon:savedfix.lon};
|
||||
wp = waypoints[wpindex];
|
||||
require("Storage").writeJSON("waypoints.json", waypoints);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
var Yoff=40,pal2color=new Uint16Array([0,65535,2047,50712],0,2),buf=Graphics.createArrayBuffer(240,50,2,{msb:!0}),candraw=!0;function flip(a,c){g.drawImage({width:240,height:50,bpp:2,buffer:a.buffer,palette:pal2color},0,c);a.clear()}var brg=0,wpindex=0,labels="N NE E SE S SW W NW".split(" "),loc=require("locale");
|
||||
function drawCompass(a){if(candraw){buf.setColor(1);buf.setFont("Vector",24);var c=a-90;0>c&&(c+=360);buf.fillRect(28,45,212,49);var b=30,d=15-c%15;15>d?b+=d:d=0;for(var e=d;e<=180-d;e+=15){var f=c+e;0==f%90?(buf.drawString(labels[Math.floor(f/45)%8],b-8,0),buf.fillRect(b-2,25,b+2,45)):0==f%45?(buf.drawString(labels[Math.floor(f/45)%8],b-12,0),buf.fillRect(b-2,30,b+2,45)):0==f%15&&buf.fillRect(b,35,b+1,45);b+=15}0!=wpindex&&(a=brg-a,180<a&&(a-=360),-180>a&&(a+=360),a+=120,30>a&&(a=14),210<a&&(a=226),
|
||||
buf.setColor(2),buf.fillCircle(a,40,8));flip(buf,Yoff)}}var heading=0;function newHeading(a,c){var b=Math.abs(a-c),d=a>c?1:-1;180<=b&&(b=360-b,d=-d);if(2>b)return c;b=c+d*(1+Math.round(b/5));0>b&&(b+=360);360<b&&(b-=360);return b}var course=0,speed=0,satellites=0,wp,dist=0;function radians(a){return a*Math.PI/180}function degrees(a){return(180*a/Math.PI+360)%360}
|
||||
function bearing(a,c){var b=radians(c.lon-a.lon),d=radians(a.lat),e=radians(c.lat);return Math.round(degrees(Math.atan2(Math.sin(b)*Math.cos(e),Math.cos(d)*Math.sin(e)-Math.sin(d)*Math.cos(e)*Math.cos(b))))}function distance(a,c){var b=radians(a.lon-c.lon)*Math.cos(radians((a.lat+c.lat)/2)),d=radians(c.lat-a.lat);return Math.round(6371E3*Math.sqrt(b*b+d*d))}var selected=!1;
|
||||
function drawN(){var a=loc.speed(speed);buf.setColor(1);buf.setFont("6x8",2);buf.drawString("o",100,0);buf.setFont("6x8",1);buf.drawString(a.substring(a.length-3),220,40);buf.setFont("Vector",48);var c=course.toString();c=10>course?"00"+c:100>course?"0"+c:c;buf.drawString(c,10,0);buf.drawString(a.substring(0,a.length-3),140,4);flip(buf,Yoff+70);buf.setColor(1);buf.setFont("Vector",24);a=brg.toString();a=10>brg?"00"+a:100>brg?"0"+a:a;buf.setColor(3);buf.drawString("Brg: ",0,0);buf.drawString("Dist: ",
|
||||
0,30);buf.setColor(selected?1:2);buf.drawString(wp.name,140,0);buf.setColor(1);buf.drawString(a,60,0);buf.drawString(loc.distance(dist),60,30);flip(buf,Yoff+130);g.setFont("6x8",1);g.setColor(0,0,0);g.fillRect(10,230,60,239);g.setColor(1,1,1);g.drawString("Sats "+satellites.toString(),10,230)}var savedfix;
|
||||
function onGPS(a){savedfix=a;void 0!==a&&(course=isNaN(a.course)?course:Math.round(a.course),speed=isNaN(a.speed)?speed:a.speed,satellites=a.satellites);candraw&&(void 0!==a&&1==a.fix&&(dist=distance(a,wp),isNaN(dist)&&(dist=0),brg=bearing(a,wp),isNaN(brg)&&(brg=0)),drawN())}var intervalRef;function stopdraw(){candraw=!1;intervalRef&&clearInterval(intervalRef)}
|
||||
function startTimers(){candraw=!0;intervalRefSec=setInterval(function(){heading=newHeading(course,heading);course!=heading&&drawCompass(heading)},200)}function drawAll(){g.setColor(1,.5,.5);g.fillPoly([120,Yoff+50,110,Yoff+70,130,Yoff+70]);g.setColor(1,1,1);drawN();drawCompass(heading)}function startdraw(){g.clear();Bangle.drawWidgets();startTimers();drawAll()}
|
||||
function setButtons(){setWatch(nextwp.bind(null,-1),BTN1,{repeat:!0,edge:"falling"});setWatch(doselect,BTN2,{repeat:!0,edge:"falling"});setWatch(nextwp.bind(null,1),BTN3,{repeat:!0,edge:"falling"})}var SCREENACCESS={withApp:!0,request:function(){this.withApp=!1;stopdraw();clearWatch()},release:function(){this.withApp=!0;startdraw();setButtons()}};Bangle.on("lcdPower",function(a){SCREENACCESS.withApp&&(a?startdraw():stopdraw())});var waypoints=require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
|
||||
wp=waypoints[0];function nextwp(a){selected&&(wpindex+=a,wpindex>=waypoints.length&&(wpindex=0),0>wpindex&&(wpindex=waypoints.length-1),wp=waypoints[wpindex],drawN())}function doselect(){selected&&0!=wpindex&&void 0===waypoints[wpindex].lat&&savedfix.fix&&(waypoints[wpindex]={name:"@"+wp.name,lat:savedfix.lat,lon:savedfix.lon},wp=waypoints[wpindex],require("Storage").writeJSON("waypoints.json",waypoints));selected=!selected;drawN()}g.clear();Bangle.setLCDBrightness(1);Bangle.loadWidgets();Bangle.drawWidgets();
|
||||
Bangle.setGPSPower(1);drawAll();startTimers();Bangle.on("GPS",onGPS);setButtons();
|
|
@ -15,3 +15,6 @@
|
|||
0.12: Add option to plot on top of OpenStreetMap tiles (when they are installed on the watch)
|
||||
0.13: Increase GPS recording accuracy by one decimal place
|
||||
Ensure default time period is 10
|
||||
0.14: Now use the openstmap lib for map plotting
|
||||
0.15: Add plotTrack method to allow current track to be plotted on a map (#395)
|
||||
Add gpsrec app to Settings menu
|
||||
|
|
|
@ -2,7 +2,10 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
|
||||
var settings = require("Storage").readJSON("gpsrec.json",1)||{};
|
||||
var qOpenStMap = (require("Storage").list("openstmap.json")>0);
|
||||
var osm;
|
||||
try { // if it's installed, use the OpenStreetMap module
|
||||
osm = require("openstmap");
|
||||
} catch (e) {}
|
||||
|
||||
function getFN(n) {
|
||||
return ".gpsrc"+n.toString(36);
|
||||
|
@ -134,7 +137,7 @@ function viewTrack(n, info) {
|
|||
info.qOSTM = false;
|
||||
plotTrack(info);
|
||||
};
|
||||
if (qOpenStMap)
|
||||
if (osm)
|
||||
menu['Plot OpenStMap'] = function() {
|
||||
info.qOSTM = true;
|
||||
plotTrack(info);
|
||||
|
@ -161,49 +164,22 @@ function viewTrack(n, info) {
|
|||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function drawopenstmap(lat, lon, map) {
|
||||
var s = require("Storage");
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var p = Bangle.project({lat:lat,lon:lon});
|
||||
var ix = (p.x-map.center.x)*4096/map.scale + (map.imgx/2) - cx;
|
||||
var iy = (map.center.y-p.y)*4096/map.scale + (map.imgy/2) - cy;
|
||||
var tx = 0|(ix/map.tilesize);
|
||||
var ty = 0|(iy/map.tilesize);
|
||||
var ox = (tx*map.tilesize)-ix;
|
||||
var oy = (ty*map.tilesize)-iy;
|
||||
for (var x=ox,ttx=tx;x<g.getWidth();x+=map.tilesize,ttx++) {
|
||||
for (var y=oy,tty=ty;y<g.getHeight();y+=map.tilesize,tty++) {
|
||||
var img = s.read("openstmap-"+ttx+"-"+tty+".img");
|
||||
if (img) g.drawImage(img,x,y);
|
||||
else g.clearRect(x,y,x+map.tilesize-1,y+map.tilesize-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function plotTrack(info) {
|
||||
"ram"
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
function distance(lat1,long1,lat2,long2) { "ram"
|
||||
var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360);
|
||||
var y = lat2 - lat1;
|
||||
return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180;
|
||||
}
|
||||
|
||||
function distance(lat1,long1,lat2,long2){
|
||||
var x = radians(long1-long2) * Math.cos(radians((lat1+lat2)/2));
|
||||
var y = radians(lat2-lat1);
|
||||
return Math.sqrt(x*x + y*y) * 6371000;
|
||||
}
|
||||
|
||||
function getMapXY(mylat, mylon) {
|
||||
// Function to convert lat/lon to XY
|
||||
var getMapXY;
|
||||
if (info.qOSTM) {
|
||||
var q = Bangle.project({lat:mylat,lon:mylon});
|
||||
var p = Bangle.project({lat:clat,lon:clon});
|
||||
var ix = (q.x-p.x)*4096/map.scale + cx;
|
||||
var iy = cy - (q.y-p.y)*4096/map.scale;
|
||||
return {x:ix, y:iy};
|
||||
}
|
||||
else {
|
||||
var ix = 30 + Math.round((long-info.minLong)*info.lfactor*info.scale);
|
||||
getMapXY = osm.latLonToXY.bind(osm);
|
||||
} else {
|
||||
getMapXY = function(lat, lon) { "ram"
|
||||
var ix = 30 + Math.round((long - info.minLong)*info.lfactor*info.scale);
|
||||
var iy = 210 - Math.round((lat - info.minLat)*info.scale);
|
||||
return {x:ix, y:iy};
|
||||
}
|
||||
|
@ -225,13 +201,10 @@ function plotTrack(info) {
|
|||
g.setColor(1,1,1);
|
||||
g.drawString("N",2,40);
|
||||
g.setColor(1,1,1);
|
||||
}
|
||||
else {
|
||||
var map = s.readJSON("openstmap.json");
|
||||
map.center = Bangle.project({lat:map.lat,lon:map.lon});
|
||||
var clat = (info.minLat+info.maxLat)/2;
|
||||
var clon = (info.minLong+info.maxLong)/2;
|
||||
drawopenstmap(clat, clon, map);
|
||||
} else {
|
||||
osm.lat = (info.minLat+info.maxLat)/2;
|
||||
osm.lon = (info.minLong+info.maxLong)/2;
|
||||
osm.draw();
|
||||
g.setColor(0, 0, 0);
|
||||
}
|
||||
g.drawString(asTime(info.duration),10,220);
|
||||
|
@ -258,7 +231,7 @@ function plotTrack(info) {
|
|||
long = +c[2];
|
||||
mp = getMapXY(lat, long);
|
||||
g.lineTo(mp.x,mp.y);
|
||||
if (info.qOSTM) g.fillCircle(mp.x, mp.y, 1);
|
||||
if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible
|
||||
var d = distance(olat,olong,lat,long);
|
||||
if (!isNaN(d)) dist+=d;
|
||||
olat = lat;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
(function(back) {
|
||||
// just go right to our app - we need all the memory
|
||||
load("gpsrec.app.js");
|
||||
})();
|
|
@ -66,6 +66,24 @@
|
|||
WIDGETS["gpsrec"]={area:"tl",width:24,draw:draw,reload:function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
},plotTrack:function(m) { // m=instance of openstmap module
|
||||
settings = require("Storage").readJSON("gpsrec.json",1)||{};
|
||||
settings.file |= 0;
|
||||
var n = settings.file.toString(36);
|
||||
var f = require("Storage").open(".gpsrc"+n,"r");
|
||||
var l = f.readLine(f);
|
||||
if (l===undefined) return;
|
||||
var c = l.split(",");
|
||||
var mp = m.latLonToXY(+c[1], +c[2]);
|
||||
g.moveTo(mp.x,mp.y);
|
||||
l = f.readLine(f);
|
||||
while(l!==undefined) {
|
||||
c = l.split(",");
|
||||
mp = m.latLonToXY(+c[1], +c[2]);
|
||||
g.lineTo(mp.x,mp.y);
|
||||
g.fillCircle(mp.x,mp.y,2); // make the track more visible
|
||||
l = f.readLine(f);
|
||||
}
|
||||
}};
|
||||
// load settings, set correct widget width
|
||||
reload();
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
0.06: Remove translations if not required
|
||||
Ensure 'on' is always supplied for translations
|
||||
0.07: Improve handling of non-ASCII characters (fix #469)
|
||||
0.08: Added Mavigation units and en_NAV
|
||||
|
|
|
@ -96,6 +96,8 @@ exports = { name : "en_GB", currencySym:"£",
|
|||
// If we have a 2 char ISO country code, use it to get the unicode flag
|
||||
if (localeParts[1] && localeParts[1].length==2)
|
||||
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
|
||||
if (localeParts[1]=="NAV")
|
||||
icon = "⛵✈️ ";
|
||||
return `<option value="${l}">${icon}${l}</option>`
|
||||
}).join("\n");
|
||||
|
||||
|
|
|
@ -4,13 +4,15 @@ const distanceUnits = { // how many meters per X?
|
|||
"yd": 0.9144,
|
||||
"mi": 1609.34,
|
||||
"km": 1000,
|
||||
"kmi": 1000
|
||||
"kmi": 1000,
|
||||
"nm": 1852
|
||||
};
|
||||
const speedUnits = { // how many kph per X?
|
||||
"kmh": 1,
|
||||
"kph": 1,
|
||||
"km/h": 1,
|
||||
"mph": 1.60934
|
||||
"mph": 1.60934,
|
||||
"kts": 1.852
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -87,7 +89,7 @@ var locales = {
|
|||
currency_symbol: "Rs.",
|
||||
currency_first: true,
|
||||
int_curr_symbol: "INR",
|
||||
speed: 'kms',
|
||||
speed: 'kmh',
|
||||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
|
@ -99,6 +101,24 @@ var locales = {
|
|||
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||
// No translation for english...
|
||||
},
|
||||
"en_NAV": { // navigation units nautical miles and knots
|
||||
lang: "en_NAV",
|
||||
decimal_point: ".",
|
||||
thousands_sep: ",",
|
||||
currency_symbol: "£", currency_first: true,
|
||||
int_curr_symbol: "GBP",
|
||||
speed: 'kts',
|
||||
distance: { "0": "m", "1": "nm" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short)
|
||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||
month: "January,February,March,April,May,June,July,August,September,October,November,December",
|
||||
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
|
||||
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||
// No translation for english...
|
||||
},
|
||||
"de_DE": {
|
||||
lang: "de_DE",
|
||||
decimal_point: ",",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Fix marker position, color, and map scaling
|
||||
0.03: Show widgets (mainly so we can use the GPS recorder widget)
|
||||
0.04: Move map rendering to a module (fix #396)
|
||||
0.05: Show currently active gpsrec GPS trace (fix #395)
|
||||
|
|
|
@ -1,62 +1,37 @@
|
|||
var s = require("Storage");
|
||||
var map = s.readJSON("openstmap.json");
|
||||
var m = require("openstmap");
|
||||
var HASWIDGETS = true;
|
||||
var y1,y2;
|
||||
|
||||
map.center = Bangle.project({lat:map.lat,lon:map.lon});
|
||||
var lat = map.lat, lon = map.lon;
|
||||
var fix = {};
|
||||
|
||||
function redraw() {
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var p = Bangle.project({lat:lat,lon:lon});
|
||||
var ix = (p.x-map.center.x)*4096/map.scale + (map.imgx/2) - cx;
|
||||
var iy = (map.center.y-p.y)*4096/map.scale + (map.imgy/2) - cy;
|
||||
//console.log(ix,iy);
|
||||
var tx = 0|(ix/map.tilesize);
|
||||
var ty = 0|(iy/map.tilesize);
|
||||
var ox = (tx*map.tilesize)-ix;
|
||||
var oy = (ty*map.tilesize)-iy;
|
||||
g.setClipRect(0,y1,g.getWidth()-1,y2);
|
||||
for (var x=ox,ttx=tx;x<g.getWidth();x+=map.tilesize,ttx++) {
|
||||
for (var y=oy,tty=ty;y<g.getHeight();y+=map.tilesize,tty++) {
|
||||
var img = s.read("openstmap-"+ttx+"-"+tty+".img");
|
||||
if (img) g.drawImage(img,x,y);
|
||||
else {
|
||||
g.clearRect(x,y,x+map.tilesize-1,y+map.tilesize-1);
|
||||
g.drawLine(x,y,x+map.tilesize-1,y+map.tilesize-1);
|
||||
g.drawLine(x,y+map.tilesize-1,x+map.tilesize-1,y);
|
||||
}
|
||||
}
|
||||
}
|
||||
m.draw();
|
||||
drawMarker();
|
||||
if (WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) {
|
||||
g.setColor(0.75,0.2,0);
|
||||
WIDGETS["gpsrec"].plotTrack(m);
|
||||
}
|
||||
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
}
|
||||
|
||||
function drawMarker() {
|
||||
if (!fix.fix) return;
|
||||
var p = Bangle.project({lat:lat,lon:lon});
|
||||
var q = Bangle.project(fix);
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var ix = (q.x-p.x)*4096/map.scale + cx;
|
||||
var iy = cy - (q.y-p.y)*4096/map.scale;
|
||||
var p = m.latLonToXY(fix.lat, fix.lon);
|
||||
g.setColor(1,0,0);
|
||||
g.fillRect(ix-2,iy-2,ix+2,iy+2);
|
||||
g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2);
|
||||
}
|
||||
|
||||
var fix;
|
||||
Bangle.on('GPS',function(f) {
|
||||
fix=f;
|
||||
g.clearRect(0,0,240,8);
|
||||
g.clearRect(0,y1,240,y1+8);
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(0,0);
|
||||
var txt = fix.satellites+" satellites";
|
||||
if (!fix.fix)
|
||||
txt += " - NO FIX";
|
||||
g.drawString(txt,120,4);
|
||||
g.drawString(txt,120,y1 + 4);
|
||||
drawMarker();
|
||||
});
|
||||
Bangle.setGPSPower(1);
|
||||
|
@ -76,7 +51,7 @@ redraw();
|
|||
|
||||
setWatch(function() {
|
||||
if (!fix.fix) return;
|
||||
lat = fix.lat;
|
||||
lon = fix.lon;
|
||||
m.lat = fix.lat;
|
||||
m.lon = fix.lon;
|
||||
redraw();
|
||||
}, BTN2, {repeat:true});
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* OpenStreetMap plotting module.
|
||||
|
||||
Usage:
|
||||
|
||||
var m = require("openstmap");
|
||||
// m.lat/lon are now the center of the loaded map
|
||||
m.draw(); // draw centered on the middle of the loaded map
|
||||
|
||||
// plot gps position on map
|
||||
Bangle.on('GPS',function(f) {
|
||||
if (!f.fix) return;
|
||||
var p = m.latLonToXY(fix.lat, fix.lon);
|
||||
g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2);
|
||||
});
|
||||
|
||||
// recenter and redraw map!
|
||||
function center() {
|
||||
m.lat = fix.lat;
|
||||
m.lon = fix.lon;
|
||||
m.draw();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
var map = require("Storage").readJSON("openstmap.json");
|
||||
map.center = Bangle.project({lat:map.lat,lon:map.lon});
|
||||
exports.map = map;
|
||||
exports.lat = map.lat; // actual position of middle of screen
|
||||
exports.lon = map.lon; // actual position of middle of screen
|
||||
var m = exports;
|
||||
|
||||
exports.draw = function() {
|
||||
var s = require("Storage");
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var p = Bangle.project({lat:m.lat,lon:m.lon});
|
||||
var ix = (p.x-map.center.x)*4096/map.scale + (map.imgx/2) - cx;
|
||||
var iy = (map.center.y-p.y)*4096/map.scale + (map.imgy/2) - cy;
|
||||
//console.log(ix,iy);
|
||||
var tx = 0|(ix/map.tilesize);
|
||||
var ty = 0|(iy/map.tilesize);
|
||||
var ox = (tx*map.tilesize)-ix;
|
||||
var oy = (ty*map.tilesize)-iy;
|
||||
for (var x=ox,ttx=tx;x<g.getWidth();x+=map.tilesize,ttx++) {
|
||||
for (var y=oy,tty=ty;y<g.getHeight();y+=map.tilesize,tty++) {
|
||||
var img = s.read("openstmap-"+ttx+"-"+tty+".img");
|
||||
if (img) g.drawImage(img,x,y);
|
||||
else {
|
||||
g.clearRect(x,y,x+map.tilesize-1,y+map.tilesize-1);
|
||||
g.drawLine(x,y,x+map.tilesize-1,y+map.tilesize-1);
|
||||
g.drawLine(x,y+map.tilesize-1,x+map.tilesize-1,y);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.latLonToXY = function(lat, lon) {
|
||||
var p = Bangle.project({lat:m.lat,lon:m.lon});
|
||||
var q = Bangle.project({lat:lat, lon:lon});
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
return {
|
||||
x : (q.x-p.x)*4096/map.scale + cx,
|
||||
y : cy - (q.y-p.y)*4096/map.scale
|
||||
};
|
||||
};
|
2
core
2
core
|
@ -1 +1 @@
|
|||
Subproject commit 0389671ba6678a12c9f35644ffb96c190bbe0278
|
||||
Subproject commit 8bfdeebf705ced95699dcbbccfa05a99e7d3f4a9
|
|
@ -0,0 +1,9 @@
|
|||
App Modules
|
||||
===========
|
||||
|
||||
These are modules used by apps - you can use them with:
|
||||
|
||||
```
|
||||
var testmodule = require("testmodule");
|
||||
testmodule.test()
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
exports.test = function() {
|
||||
console.log("Hello world!");
|
||||
};
|
Loading…
Reference in New Issue