Merge branch 'upstream/master' into fix/improve-formatter-check

master
Rob Pilling 2024-02-05 22:30:31 +00:00
commit b2ff9dbb78
24 changed files with 461 additions and 281 deletions

View File

@ -1 +1 @@
0.01: New Widget! 0.01: New Clock Info!

View File

@ -0,0 +1 @@
0.01: New Clock!

View File

@ -0,0 +1,25 @@
# Clock Name
More info on making Clock Faces: https://www.espruino.com/Bangle.js+Clock
Describe the Clock...
## Usage
Describe how to use it
## Features
Name the function
## Controls
Name the buttons and what they are used for
## Requests
Name who should be contacted for support/update requests
## Creator
Your name

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="))

View File

@ -0,0 +1,44 @@
// timeout used to update every minute
var drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
function draw() {
// queue next draw in one minute
queueDraw();
// Work out where to draw...
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
// work out locale-friendly date/time
var date = new Date();
var timeStr = require("locale").time(date,1);
var dateStr = require("locale").date(date);
// draw time
g.setFontAlign(0,0).setFont("Vector",48);
g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
g.drawString(timeStr,x,y);
// draw date
y += 35;
g.setFontAlign(0,0).setFont("6x8");
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
g.drawString(dateStr,x,y);
}
// Clear the screen once, at startup
g.clear();
// draw immediately at first, queue update
draw();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,15 @@
{ "id": "7chname",
"name": "My clock human readable name",
"shortName":"Short Name",
"version":"0.01",
"description": "A detailed description of my clock",
"icon": "icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.app.js","url":"app.js"},
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -32,3 +32,4 @@
Allow alarm enable/disable Allow alarm enable/disable
0.31: Implement API for activity fetching 0.31: Implement API for activity fetching
0.32: Added support for loyalty cards from gadgetbridge 0.32: Added support for loyalty cards from gadgetbridge
0.33: Fix alarms created in Gadgetbridge not repeating

View File

@ -81,7 +81,12 @@
for (var j = 0; j < event.d.length; j++) { for (var j = 0; j < event.d.length; j++) {
// prevents all alarms from going off at once?? // prevents all alarms from going off at once??
var dow = event.d[j].rep; var dow = event.d[j].rep;
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW var rp = false;
if (!dow) {
dow = 127; //if no DOW selected, set alarm to all DOW
} else {
rp = true;
}
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0; var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
var a = require("sched").newDefaultAlarm(); var a = require("sched").newDefaultAlarm();
a.id = "gb"+j; a.id = "gb"+j;
@ -89,6 +94,7 @@
a.on = event.d[j].on !== undefined ? event.d[j].on : true; a.on = event.d[j].on !== undefined ? event.d[j].on : true;
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000; a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
a.rp = rp;
a.last = last; a.last = last;
alarms.push(a); alarms.push(a);
} }

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.32", "version": "0.33",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge", "tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -2,7 +2,7 @@
"name": "BTHome Temperature and Pressure", "name": "BTHome Temperature and Pressure",
"shortName":"BTHome T", "shortName":"BTHome T",
"version":"0.02", "version":"0.02",
"description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard", "description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
"icon": "app.png", "icon": "app.png",
"tags": "bthome,bluetooth,temperature", "tags": "bthome,bluetooth,temperature",
"supports" : ["BANGLEJS2"], "supports" : ["BANGLEJS2"],

View File

@ -2,7 +2,7 @@
"id": "ha", "id": "ha",
"name": "Home Assistant", "name": "Home Assistant",
"version": "0.10", "version": "0.10",
"description": "Integrates your Bangle.js into Home Assistant.", "description": "Integrates your Bangle.js into Home Assistant using Android Integration/Gadgetbridge",
"icon": "ha.png", "icon": "ha.png",
"type": "app", "type": "app",
"tags": "tool,clkinfo,bluetooth", "tags": "tool,clkinfo,bluetooth",

View File

@ -3,7 +3,7 @@
"name": "Home Assistant Sensors", "name": "Home Assistant Sensors",
"shortName": "HA sensors", "shortName": "HA sensors",
"version": "0.02", "version": "0.02",
"description": "Send sensor values to Home Assistant using the Android Integration.", "description": "Send sensor values to Home Assistant using Android Integration/Gadgetbridge",
"icon": "ha.png", "icon": "ha.png",
"type": "bootloader", "type": "bootloader",
"tags": "tool,sensors", "tags": "tool,sensors",

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Add more control styles

View File

@ -10,8 +10,21 @@ in the future this app will be able to support other types of remote (see below)
## Usage ## Usage
Run the app, and ensure you're not connected to your watch via Bluetooth Run the app, then choose the type of controls you want and ensure you're not connected
(a warning will pop up if so). to your watch via Bluetooth (a warning will pop up if so).
Linear mode controls A/B axes individually, and allows you to vary the speed of the
motors based on the distance you drag from the centre. Other modes just use on/off
buttons.
| Mode | up | down | left | right |
|------------|------|------|------|-------|
| **Linear** | +A | -A | -B | +B |
| **Normal** | +A | -A | -B | +B |
| **Tank** | -A+B | +A-B | +A+B | -A-B |
| **Merged** | -A-B | +A+B | +A-B | -A+B |
In all cases pressing the C/D buttons will turn on C/D outputs
Now press the arrow keys on the screen to control the robot. Now press the arrow keys on the screen to control the robot.

View File

@ -1,5 +1,4 @@
var lego = require("mouldking"); var lego = require("mouldking");
lego.start();
E.on('kill', () => { E.on('kill', () => {
// return to normal Bluetooth advertising // return to normal Bluetooth advertising
NRF.setAdvertising({},{showName:true}); NRF.setAdvertising({},{showName:true});
@ -12,59 +11,133 @@ var controlState = "";
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
var R = Bangle.appRect; var R = Bangle.appRect;
// we'll divide up into 3x3
function getBoxCoords(x,y) {
return {
x : R.x + R.w*x/3,
y : R.y + R.h*y/3
};
}
function draw() { function startLegoButtons(controls) {
g.reset().clearRect(R); // we'll divide up into 3x3
var c, ninety = Math.PI/2; function getBoxCoords(x,y) {
var colOn = "#f00", colOff = g.theme.fg; return {
c = getBoxCoords(1.5, 0.5); x : R.x + R.w*x/3,
g.setColor(controlState=="up"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:0}); y : R.y + R.h*y/3
c = getBoxCoords(2.5, 1.5); };
g.setColor(controlState=="right"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:ninety});
c = getBoxCoords(0.5, 1.5);
g.setColor(controlState=="left"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:-ninety});
c = getBoxCoords(1.5, 1.5);
g.setColor(controlState=="down"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:ninety*2});
if (NRF.getSecurityStatus().connected) {
c = getBoxCoords(1.5, 2.5);
g.setFontAlign(0,0).setFont("6x8").drawString("WARNING:\nBluetooth Connected\nYou must disconnect\nbefore LEGO will work",c.x,c.y);
} }
}
draw();
NRF.on('connect', draw);
NRF.on('disconnect', draw);
function setControlState(s) { function draw() {
controlState = s; g.reset().clearRect(R);
var c = {}; var c, ninety = Math.PI/2;
var speed = 3; var colOn = "#f00", colOff = g.theme.fg;
if (s=="up") c={a:-speed,b:-speed}; c = getBoxCoords(1.5, 0.5);
if (s=="down") c={a:speed,b:speed}; g.setColor(controlState=="up"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:0});
if (s=="left") c={a:speed,b:-speed}; c = getBoxCoords(2.5, 1.5);
if (s=="right") c={a:-speed,b:speed}; g.setColor(controlState=="right"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:ninety});
c = getBoxCoords(0.5, 1.5);
g.setColor(controlState=="left"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:-ninety});
c = getBoxCoords(1.5, 1.5);
g.setColor(controlState=="down"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:ninety*2});
if (NRF.getSecurityStatus().connected) {
c = getBoxCoords(1.5, 2.5);
g.setFontAlign(0,0).setFont("6x8").drawString("WARNING:\nBluetooth Connected\nYou must disconnect\nbefore LEGO will work",c.x,c.y);
}
g.setFont("6x8:3").setFontAlign(0,0);
c = getBoxCoords(0.5, 0.5);
g.setColor(controlState=="c"?colOn:colOff).drawString("C",c.x,c.y);
c = getBoxCoords(2.5, 0.5);
g.setColor(controlState=="d"?colOn:colOff).drawString("D",c.x,c.y);
}
function setControlState(s) {
controlState = s;
var c = {};
if (s in controls)
c = controls[s];
draw();
lego.set(c);
}
lego.start();
Bangle.setUI({mode:"custom", drag : e => {
var x = Math.floor(E.clip((e.x - R.x) * 3 / R.w,0,2.99));
var y = Math.floor(E.clip((e.y - R.y) * 3 / R.h,0,2.99));
if (!e.b) {
setControlState("");
return;
}
if (y==0) { // top row
if (x==0) setControlState("c");
if (x==1) setControlState("up");
if (x==2) setControlState("d");
} else if (y==1) {
if (x==0) setControlState("left");
if (x==1) setControlState("down");
if (x==2) setControlState("right");
}
}});
draw(); draw();
lego.set(c); NRF.on('connect', draw);
NRF.on('disconnect', draw);
} }
Bangle.on('drag',e => { function startLegoLinear() {
var x = Math.floor(E.clip((e.x - R.x) * 3 / R.w,0,2.99)); var mx = R.x+R.w/2;
var y = Math.floor(E.clip((e.y - R.y) * 3 / R.h,0,2.99)); var my = R.y+R.h/2;
if (!e.b) { var x=0,y=0;
setControlState(""); var scale = 10;
return;
} function draw() {
if (y==0) { // top row g.reset().clearRect(R);
if (x==1) setControlState("up"); for (var i=3;i<60;i+=10)
} else if (y==1) { g.drawCircle(mx,my,i);
if (x==0) setControlState("left"); g.setColor("#F00");
if (x==1) setControlState("down"); var px = E.clip(mx + x*scale, R.x+20, R.x2-20);
if (x==2) setControlState("right"); var py = E.clip(my + y*scale, R.y+20, R.y2-20);
g.fillCircle(px, py, 20);
} }
lego.start();
Bangle.setUI({mode:"custom", drag : e => {
x = Math.round((e.x - mx) / scale);
y = Math.round((e.y - my) / scale);
if (!e.b) {
x=0; y=0;
}
lego.set({a:x, b:y});
draw();
}});
draw();
NRF.on('connect', draw);
NRF.on('disconnect', draw);
}
// Mappings of button to output
const CONTROLS = {
normal : {
up : {a: 7,b: 0},
down : {a:-7,b: 0},
left : {a: 0,b:-7},
right: {a: 0,b: 7},
c : {c:7},
d : {d:7}
}, tank : {
up : {a:-7,b:7},
down : {a: 7,b:-7},
left : {a: 7,b:7},
right: {a:-7,b:-7},
c : {c:7},
d : {d:7}
}, merged : {
up : {a: 7,b: 7},
down : {a:-7,b:-7},
left : {a: 7,b:-7},
right: {a:-7,b: 7},
c : {c:7},
d : {d:7}
}
};
E.showMenu({ "" : {title:"LEGO Remote", back:()=>load()},
"Linear" : () => startLegoLinear(),
"Normal" : () => startLego(CONTROLS.normal),
"Tank" : () => startLego(CONTROLS.tank),
"Marged" : () => startLego(CONTROLS.merged),
}); });

View File

@ -1,7 +1,7 @@
{ "id": "legoremote", { "id": "legoremote",
"name": "LEGO Remote control", "name": "LEGO Remote control",
"shortName":"LEGO Remote", "shortName":"LEGO Remote",
"version":"0.01", "version":"0.02",
"description": "Use your Bangle.js to control LEGO models. See the README for compatibility", "description": "Use your Bangle.js to control LEGO models. See the README for compatibility",
"icon": "app.png", "icon": "app.png",
"tags": "toy,lego,bluetooth", "tags": "toy,lego,bluetooth",

View File

@ -33,3 +33,5 @@
0.26: Ensure that when redrawing, we always cancel any in-progress track draw 0.26: Ensure that when redrawing, we always cancel any in-progress track draw
0.27: Display message if no map is installed 0.27: Display message if no map is installed
0.28: Fix rounding errors 0.28: Fix rounding errors
0.29: Keep exit at bottom of menu
Speed up latLonToXY for track rendering

View File

@ -172,7 +172,6 @@ function showMenu() {
var menu = { var menu = {
"":{title:/*LANG*/"Map"}, "":{title:/*LANG*/"Map"},
"< Back": ()=> showMap(), "< Back": ()=> showMap(),
/*LANG*/"Exit": () => load(),
}; };
// If we have a GPS fix, add a menu item to center it // If we have a GPS fix, add a menu item to center it
if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{ if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{
@ -180,7 +179,6 @@ function showMenu() {
m.lon = fix.lon; m.lon = fix.lon;
showMap(); showMap();
}; };
menu = Object.assign(menu, { menu = Object.assign(menu, {
/*LANG*/"Zoom In": () =>{ /*LANG*/"Zoom In": () =>{
m.scale /= 2; m.scale /= 2;
@ -234,6 +232,7 @@ function showMenu() {
} }
}; };
} }
menu[/*LANG*/"Exit"] = () => load();
E.showMenu(menu); E.showMenu(menu);
} }

View File

@ -2,7 +2,7 @@
"id": "openstmap", "id": "openstmap",
"name": "OpenStreetMap", "name": "OpenStreetMap",
"shortName": "OpenStMap", "shortName": "OpenStMap",
"version": "0.28", "version": "0.29",
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps", "description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",

View File

@ -38,17 +38,17 @@ if (m.map) {
m.lat = m.map.lat; // position of middle of screen m.lat = m.map.lat; // position of middle of screen
m.lon = m.map.lon; // position of middle of screen m.lon = m.map.lon; // position of middle of screen
} }
var CX = g.getWidth()/2;
var CY = g.getHeight()/2;
// return number of tiles drawn // return number of tiles drawn
exports.draw = function() { exports.draw = function() {
var cx = g.getWidth()/2;
var cy = g.getHeight()/2;
var p = Bangle.project({lat:m.lat,lon:m.lon}); var p = Bangle.project({lat:m.lat,lon:m.lon});
let count = 0; let count = 0;
m.maps.forEach((map,idx) => { m.maps.forEach((map,idx) => {
var d = map.scale/m.scale; var d = map.scale/m.scale;
var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx; var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - CX;
var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy; var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - CY;
var o = {}; var o = {};
var s = map.tilesize; var s = map.tilesize;
if (d!=1) { // if the two are different, add scaling if (d!=1) { // if the two are different, add scaling
@ -85,14 +85,12 @@ exports.draw = function() {
}; };
/// Convert lat/lon to pixels on the screen /// Convert lat/lon to pixels on the screen
exports.latLonToXY = function(lat, lon) { exports.latLonToXY = function(lat, lon) { "ram"
var p = Bangle.project({lat:m.lat,lon:m.lon}); var p = Bangle.project({lat:m.lat,lon:m.lon}),
var q = Bangle.project({lat:lat, lon:lon}); q = Bangle.project({lat:lat, lon:lon});
var cx = g.getWidth()/2;
var cy = g.getHeight()/2;
return { return {
x : Math.round((q.x-p.x)/m.scale + cx), x : Math.round((q.x-p.x)/m.scale + CX),
y : Math.round(cy - (q.y-p.y)/m.scale) y : Math.round(CY - (q.y-p.y)/m.scale)
}; };
}; };

View File

@ -45,3 +45,4 @@
0.36: When recording with 1 second periods, log time with one decimal. 0.36: When recording with 1 second periods, log time with one decimal.
0.37: 1 second periods + gps log => log when gps event is received, not with 0.37: 1 second periods + gps log => log when gps event is received, not with
setInterval. setInterval.
0.38: Tweaks to speed up track rendering

View File

@ -213,229 +213,229 @@ function viewTrack(filename, info) {
}); });
}; };
menu['< Back'] = () => { viewTracks(); }; menu['< Back'] = () => { viewTracks(); };
return E.showMenu(menu);
}
function plotTrack(info) { "ram" function plotTrack(info) { "ram"
function distance(lat1,long1,lat2,long2) { "ram" function distance(lat1,long1,lat2,long2) { "ram"
var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360);
var y = lat2 - lat1; var y = lat2 - lat1;
return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180;
} }
// Function to convert lat/lon to XY // Function to convert lat/lon to XY
var getMapXY; var XY;
if (info.qOSTM) { if (info.qOSTM) {
// scale map to view full track // scale map to view full track
const max = Bangle.project({lat: info.maxLat, lon: info.maxLong}); const max = Bangle.project({lat: info.maxLat, lon: info.maxLong});
const min = Bangle.project({lat: info.minLat, lon: info.minLong}); const min = Bangle.project({lat: info.minLat, lon: info.minLong});
const scaleX = (max.x-min.x)/Bangle.appRect.w; const scaleX = (max.x-min.x)/Bangle.appRect.w;
const scaleY = (max.y-min.y)/Bangle.appRect.h; const scaleY = (max.y-min.y)/Bangle.appRect.h;
osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin
getMapXY = osm.latLonToXY.bind(osm); XY = osm.latLonToXY.bind(osm);
} else { } else {
getMapXY = function(lat, lon) { "ram" XY = function(lat, lon) { "ram"
return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale),
y:cy + Math.round((info.lat - lat)*info.scale)}; y:cy + Math.round((info.lat - lat)*info.scale)};
}; };
} }
E.showMenu(); // remove menu E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn); E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn);
g.flip(); // on buffered screens, draw a not saying we're busy g.flip(); // on buffered screens, draw a not saying we're busy
g.clear(1); g.clear(1);
var s = require("Storage"); var s = require("Storage");
var W = g.getWidth(); var G = g;
var H = g.getHeight(); var W = g.getWidth();
var cx = W/2; var H = g.getHeight();
var cy = 24 + (H-24)/2; var cx = W/2;
if (!info.qOSTM) { var cy = 24 + (H-24)/2;
g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); if (!info.qOSTM) {
g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]);
} else { g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50);
osm.lat = info.lat; } else {
osm.lon = info.lon; osm.lat = info.lat;
osm.draw(); osm.lon = info.lon;
g.setColor("#000"); osm.draw();
g.setColor("#000");
}
var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude");
g.drawString(asTime(info.duration),10,220);
var f = require("Storage").open(info.filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var ox=0;
var oy=0;
var olat,olong,dist=0;
var i=0, c = l.split(",");
// skip until we find our first data
while(l!==undefined && c[latIdx]=="") {
c = l.split(",");
l = f.readLine(f);
}
// now start plotting
var lat = +c[latIdx];
var long = +c[lonIdx];
var mp = XY(lat, long);
g.moveTo(mp.x,mp.y);
g.setColor("#0f0");
g.fillCircle(mp.x,mp.y,5);
if (info.qOSTM) g.setColor("#f09");
else g.setColor(g.theme.fg);
l = f.readLine(f);
g.flip(); // force update
while(l!==undefined) {
c = l.split(",");l = f.readLine(f);
if (c[latIdx]=="")continue;
lat = +c[latIdx];
long = +c[lonIdx];
mp = XY(lat, long);
G.lineTo(mp.x,mp.y);
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;
olong = long;
ox = mp.x;
oy = mp.y;
if (++i > 100) { G.flip();i=0; }
}
g.setColor("#f00");
g.fillCircle(ox,oy,5);
if (info.qOSTM) g.setColor("#000");
else g.setColor(g.theme.fg);
g.drawString(require("locale").distance(dist,2),g.getWidth() / 2, g.getHeight() - 20);
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.fn, info);
}, isBTN3?BTN3:BTN1);
Bangle.drawWidgets();
g.flip();
}
function plotGraph(info, style) { "ram"
E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Calculating...",/*LANG*/"Track "+info.fn);
var filename = info.filename;
var infn = new Float32Array(80);
var infc = new Uint16Array(80);
var title;
var lt = 0; // last time
var tn = 0; // count for each time period
var strt, dur = info.duration;
var f = require("Storage").open(filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var nl = 0, c, i;
var factor = 1; // multiplier used for values when graphing
var timeIdx = info.fields.indexOf("Time");
if (l!==undefined) {
c = l.split(",");
strt = c[timeIdx];
}
if (style=="Heartrate") {
title = /*LANG*/"Heartrate (bpm)";
var hrmIdx = info.fields.indexOf("Heartrate");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[hrmIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[hrmIdx];
infc[i]++;
} }
} else if (style=="Altitude") {
title = /*LANG*/"Altitude (m)";
var altIdx = info.fields.indexOf("Barometer Altitude");
if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[altIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[altIdx];
infc[i]++;
}
} else if (style=="Speed") {
// use locate to work out units
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
let units = localeStr.replace(/[0-9.]*/,"");
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
// title
title = /*LANG*/"Speed"+` (${units})`;
var latIdx = info.fields.indexOf("Latitude"); var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude"); var lonIdx = info.fields.indexOf("Longitude");
g.drawString(asTime(info.duration),10,220);
var f = require("Storage").open(info.filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var ox=0;
var oy=0;
var olat,olong,dist=0;
var i=0, c = l.split(",");
// skip until we find our first data // skip until we find our first data
while(l!==undefined && c[latIdx]=="") { while(l!==undefined && c[latIdx]=="") {
c = l.split(","); c = l.split(",");
l = f.readLine(f); l = f.readLine(f);
} }
// now start plotting // now iterate
var lat = +c[latIdx]; var p,lp = Bangle.project({lat:c[1],lon:c[2]});
var long = +c[lonIdx]; var t,dx,dy,d,lt = c[timeIdx];
var mp = getMapXY(lat, long);
g.moveTo(mp.x,mp.y);
g.setColor("#0f0");
g.fillCircle(mp.x,mp.y,5);
if (info.qOSTM) g.setColor("#f09");
else g.setColor(g.theme.fg);
l = f.readLine(f);
g.flip(); // force update
while(l!==undefined) { while(l!==undefined) {
c = l.split(",");l = f.readLine(f); ++nl;c=l.split(",");
if (c[latIdx]=="")continue; l = f.readLine(f);
lat = +c[latIdx]; if (c[latIdx] == "") {
long = +c[lonIdx]; continue;
mp = getMapXY(lat, long); }
g.lineTo(mp.x,mp.y); t = c[timeIdx];
if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible i = Math.round(80*(t - strt)/dur);
var d = distance(olat,olong,lat,long); p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
if (!isNaN(d)) dist+=d; dx = p.x-lp.x;
olat = lat; dy = p.y-lp.y;
olong = long; d = Math.sqrt(dx*dx+dy*dy);
ox = mp.x; if (t!=lt) {
oy = mp.y; infn[i]+=d / (t-lt); // speed
if (++i > 100) { g.flip();i=0; }
}
g.setColor("#f00");
g.fillCircle(ox,oy,5);
if (info.qOSTM) g.setColor("#000");
else g.setColor(g.theme.fg);
g.drawString(require("locale").distance(dist,2),g.getWidth() / 2, g.getHeight() - 20);
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.fn, info);
}, isBTN3?BTN3:BTN1);
Bangle.drawWidgets();
g.flip();
}
function plotGraph(info, style) { "ram"
E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Calculating...",/*LANG*/"Track "+info.fn);
var filename = info.filename;
var infn = new Float32Array(80);
var infc = new Uint16Array(80);
var title;
var lt = 0; // last time
var tn = 0; // count for each time period
var strt, dur = info.duration;
var f = require("Storage").open(filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var nl = 0, c, i;
var factor = 1; // multiplier used for values when graphing
var timeIdx = info.fields.indexOf("Time");
if (l!==undefined) {
c = l.split(",");
strt = c[timeIdx];
}
if (style=="Heartrate") {
title = /*LANG*/"Heartrate (bpm)";
var hrmIdx = info.fields.indexOf("Heartrate");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[hrmIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[hrmIdx];
infc[i]++; infc[i]++;
} }
} else if (style=="Altitude") { lp = p;
title = /*LANG*/"Altitude (m)"; lt = t;
var altIdx = info.fields.indexOf("Barometer Altitude");
if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[altIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[altIdx];
infc[i]++;
}
} else if (style=="Speed") {
// use locate to work out units
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
let units = localeStr.replace(/[0-9.]*/,"");
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
// title
title = /*LANG*/"Speed"+` (${units})`;
var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude");
// skip until we find our first data
while(l!==undefined && c[latIdx]=="") {
c = l.split(",");
l = f.readLine(f);
}
// now iterate
var p,lp = Bangle.project({lat:c[1],lon:c[2]});
var t,dx,dy,d,lt = c[timeIdx];
while(l!==undefined) {
++nl;c=l.split(",");
l = f.readLine(f);
if (c[latIdx] == "") {
continue;
}
t = c[timeIdx];
i = Math.round(80*(t - strt)/dur);
p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
dx = p.x-lp.x;
dy = p.y-lp.y;
d = Math.sqrt(dx*dx+dy*dy);
if (t!=lt) {
infn[i]+=d / (t-lt); // speed
infc[i]++;
}
lp = p;
lt = t;
}
} else throw new Error("Unknown type "+style);
var min=100000,max=-100000;
for (var i=0;i<infn.length;i++) {
if (infc[i]>0) infn[i]=factor*infn[i]/infc[i];
else { // no data - search back and see if we can find something
for (var j=i-1;j>=0;j--)
if (infc[j]) { infn[i]=infn[j]; break; }
}
var n = infn[i];
if (n>max) max=n;
if (n<min) min=n;
} }
// work out a nice grid value } else throw new Error("Unknown type "+style);
var heightDiff = max-min; var min=100000,max=-100000;
var grid = 1; for (var i=0;i<infn.length;i++) {
while (heightDiff/grid > 8) { if (infc[i]>0) infn[i]=factor*infn[i]/infc[i];
grid*=2; else { // no data - search back and see if we can find something
for (var j=i-1;j>=0;j--)
if (infc[j]) { infn[i]=infn[j]; break; }
} }
// draw var n = infn[i];
g.clear(1).setFont("6x8",1); if (n>max) max=n;
var r = require("graph").drawLine(g, infn, { if (n<min) min=n;
x:4,y:24,
width: g.getWidth()-24,
height: g.getHeight()-(24+8),
axes : true,
gridy : grid,
gridx : infn.length / 3,
title: title,
miny: min,
maxy: max,
xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
});
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.filename, info);
}, isBTN3?BTN3:BTN1);
g.flip();
} }
// work out a nice grid value
return E.showMenu(menu); var heightDiff = max-min;
var grid = 1;
while (heightDiff/grid > 8) {
grid*=2;
}
// draw
g.clear(1).setFont("6x8",1);
var r = require("graph").drawLine(g, infn, {
x:4,y:24,
width: g.getWidth()-24,
height: g.getHeight()-(24+8),
axes : true,
gridy : grid,
gridx : infn.length / 3,
title: title,
miny: min,
maxy: max,
xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
});
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.filename, info);
}, isBTN3?BTN3:BTN1);
g.flip();
} }

View File

@ -2,7 +2,7 @@
"id": "recorder", "id": "recorder",
"name": "Recorder", "name": "Recorder",
"shortName": "Recorder", "shortName": "Recorder",
"version": "0.37", "version": "0.38",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.", "description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,outdoors,gps,widget,clkinfo", "tags": "tool,outdoors,gps,widget,clkinfo",