diff --git a/apps/_example_clkinfo/ChangeLog b/apps/_example_clkinfo/ChangeLog index 4c21f3ace..78ba28f3b 100644 --- a/apps/_example_clkinfo/ChangeLog +++ b/apps/_example_clkinfo/ChangeLog @@ -1 +1 @@ -0.01: New Widget! +0.01: New Clock Info! diff --git a/apps/_example_clock/ChangeLog b/apps/_example_clock/ChangeLog new file mode 100644 index 000000000..09953593e --- /dev/null +++ b/apps/_example_clock/ChangeLog @@ -0,0 +1 @@ +0.01: New Clock! diff --git a/apps/_example_clock/README.md b/apps/_example_clock/README.md new file mode 100644 index 000000000..5d750a965 --- /dev/null +++ b/apps/_example_clock/README.md @@ -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 diff --git a/apps/_example_clock/app-icon.js b/apps/_example_clock/app-icon.js new file mode 100644 index 000000000..49232b838 --- /dev/null +++ b/apps/_example_clock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")) diff --git a/apps/_example_clock/clock.js b/apps/_example_clock/clock.js new file mode 100644 index 000000000..7e97cf758 --- /dev/null +++ b/apps/_example_clock/clock.js @@ -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(); \ No newline at end of file diff --git a/apps/_example_clock/icon.png b/apps/_example_clock/icon.png new file mode 100644 index 000000000..582cb2e08 Binary files /dev/null and b/apps/_example_clock/icon.png differ diff --git a/apps/_example_clock/metadata.json b/apps/_example_clock/metadata.json new file mode 100644 index 000000000..c6e1256d3 --- /dev/null +++ b/apps/_example_clock/metadata.json @@ -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} + ] +} diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index d531e43a9..108242825 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -32,3 +32,4 @@ Allow alarm enable/disable 0.31: Implement API for activity fetching 0.32: Added support for loyalty cards from gadgetbridge +0.33: Fix alarms created in Gadgetbridge not repeating diff --git a/apps/android/boot.js b/apps/android/boot.js index 846fc40a8..63f9b2883 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -81,7 +81,12 @@ for (var j = 0; j < event.d.length; j++) { // prevents all alarms from going off at once?? 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 a = require("sched").newDefaultAlarm(); a.id = "gb"+j; @@ -89,6 +94,7 @@ 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.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format + a.rp = rp; a.last = last; alarms.push(a); } diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 68bd946c5..5babc520b 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "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.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/bthometemp/metadata.json b/apps/bthometemp/metadata.json index 8ffb22c83..fc6804f17 100644 --- a/apps/bthometemp/metadata.json +++ b/apps/bthometemp/metadata.json @@ -2,7 +2,7 @@ "name": "BTHome Temperature and Pressure", "shortName":"BTHome T", "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", "tags": "bthome,bluetooth,temperature", "supports" : ["BANGLEJS2"], diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json index a34621b04..c58e5e486 100644 --- a/apps/ha/metadata.json +++ b/apps/ha/metadata.json @@ -2,7 +2,7 @@ "id": "ha", "name": "Home Assistant", "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", "type": "app", "tags": "tool,clkinfo,bluetooth", diff --git a/apps/hasensors/metadata.json b/apps/hasensors/metadata.json index 106f11407..5764c6100 100644 --- a/apps/hasensors/metadata.json +++ b/apps/hasensors/metadata.json @@ -3,7 +3,7 @@ "name": "Home Assistant Sensors", "shortName": "HA sensors", "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", "type": "bootloader", "tags": "tool,sensors", diff --git a/apps/legoremote/ChangeLog b/apps/legoremote/ChangeLog index 5560f00bc..b86638553 100644 --- a/apps/legoremote/ChangeLog +++ b/apps/legoremote/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Add more control styles diff --git a/apps/legoremote/README.md b/apps/legoremote/README.md index a95b7b154..b6cd82979 100644 --- a/apps/legoremote/README.md +++ b/apps/legoremote/README.md @@ -10,8 +10,21 @@ in the future this app will be able to support other types of remote (see below) ## Usage -Run the app, and ensure you're not connected to your watch via Bluetooth -(a warning will pop up if so). +Run the app, then choose the type of controls you want and ensure you're not connected +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. diff --git a/apps/legoremote/app.js b/apps/legoremote/app.js index 1c76a54a8..40935cabf 100644 --- a/apps/legoremote/app.js +++ b/apps/legoremote/app.js @@ -1,5 +1,4 @@ var lego = require("mouldking"); -lego.start(); E.on('kill', () => { // return to normal Bluetooth advertising NRF.setAdvertising({},{showName:true}); @@ -12,59 +11,133 @@ var controlState = ""; Bangle.loadWidgets(); Bangle.drawWidgets(); 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() { - g.reset().clearRect(R); - var c, ninety = Math.PI/2; - var colOn = "#f00", colOff = g.theme.fg; - c = getBoxCoords(1.5, 0.5); - g.setColor(controlState=="up"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:0}); - 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); +function startLegoButtons(controls) { + // 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 + }; } -} -draw(); -NRF.on('connect', draw); -NRF.on('disconnect', draw); -function setControlState(s) { - controlState = s; - var c = {}; - var speed = 3; - if (s=="up") c={a:-speed,b:-speed}; - if (s=="down") c={a:speed,b:speed}; - if (s=="left") c={a:speed,b:-speed}; - if (s=="right") c={a:-speed,b:speed}; + function draw() { + g.reset().clearRect(R); + var c, ninety = Math.PI/2; + var colOn = "#f00", colOff = g.theme.fg; + c = getBoxCoords(1.5, 0.5); + g.setColor(controlState=="up"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:0}); + 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); + } + 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(); - lego.set(c); + NRF.on('connect', draw); + NRF.on('disconnect', draw); } -Bangle.on('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==1) setControlState("up"); - } else if (y==1) { - if (x==0) setControlState("left"); - if (x==1) setControlState("down"); - if (x==2) setControlState("right"); +function startLegoLinear() { + var mx = R.x+R.w/2; + var my = R.y+R.h/2; + var x=0,y=0; + var scale = 10; + + function draw() { + g.reset().clearRect(R); + for (var i=3;i<60;i+=10) + g.drawCircle(mx,my,i); + g.setColor("#F00"); + var px = E.clip(mx + x*scale, R.x+20, R.x2-20); + 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), }); diff --git a/apps/legoremote/metadata.json b/apps/legoremote/metadata.json index c86251860..8fe4c4b44 100644 --- a/apps/legoremote/metadata.json +++ b/apps/legoremote/metadata.json @@ -1,7 +1,7 @@ { "id": "legoremote", "name": "LEGO Remote control", "shortName":"LEGO Remote", - "version":"0.01", + "version":"0.02", "description": "Use your Bangle.js to control LEGO models. See the README for compatibility", "icon": "app.png", "tags": "toy,lego,bluetooth", diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 239b89abc..f0a1e5c5a 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -33,3 +33,5 @@ 0.26: Ensure that when redrawing, we always cancel any in-progress track draw 0.27: Display message if no map is installed 0.28: Fix rounding errors +0.29: Keep exit at bottom of menu + Speed up latLonToXY for track rendering \ No newline at end of file diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 7c015cfc8..2d14fbd2a 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -172,7 +172,6 @@ function showMenu() { var menu = { "":{title:/*LANG*/"Map"}, "< Back": ()=> showMap(), - /*LANG*/"Exit": () => load(), }; // If we have a GPS fix, add a menu item to center it if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{ @@ -180,7 +179,6 @@ function showMenu() { m.lon = fix.lon; showMap(); }; - menu = Object.assign(menu, { /*LANG*/"Zoom In": () =>{ m.scale /= 2; @@ -234,6 +232,7 @@ function showMenu() { } }; } + menu[/*LANG*/"Exit"] = () => load(); E.showMenu(menu); } diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 2b010c576..904c1bd94 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,7 +2,7 @@ "id": "openstmap", "name": "OpenStreetMap", "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", "readme": "README.md", "icon": "app.png", diff --git a/apps/openstmap/openstmap.js b/apps/openstmap/openstmap.js index 7e84f66e5..eb1aeaf3c 100644 --- a/apps/openstmap/openstmap.js +++ b/apps/openstmap/openstmap.js @@ -38,17 +38,17 @@ if (m.map) { m.lat = m.map.lat; // 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 exports.draw = function() { - var cx = g.getWidth()/2; - var cy = g.getHeight()/2; var p = Bangle.project({lat:m.lat,lon:m.lon}); let count = 0; m.maps.forEach((map,idx) => { var d = map.scale/m.scale; - 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 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 o = {}; var s = map.tilesize; 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 -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; +exports.latLonToXY = function(lat, lon) { "ram" + var p = Bangle.project({lat:m.lat,lon:m.lon}), + q = Bangle.project({lat:lat, lon:lon}); return { - x : Math.round((q.x-p.x)/m.scale + cx), - y : Math.round(cy - (q.y-p.y)/m.scale) + x : Math.round((q.x-p.x)/m.scale + CX), + y : Math.round(CY - (q.y-p.y)/m.scale) }; }; @@ -102,4 +100,4 @@ exports.scroll = function(x,y) { var b = Bangle.project({lat:m.lat+1,lon:m.lon+1}); this.lon += x * m.scale / (a.x-b.x); this.lat -= y * m.scale / (a.y-b.y); -}; +}; \ No newline at end of file diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index dadd3fbcb..0e20a13fc 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -45,3 +45,4 @@ 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 setInterval. +0.38: Tweaks to speed up track rendering \ No newline at end of file diff --git a/apps/recorder/app.js b/apps/recorder/app.js index a2218420a..c02a800d0 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -213,230 +213,230 @@ function viewTrack(filename, info) { }); }; menu['< Back'] = () => { viewTracks(); }; + return E.showMenu(menu); +} - function plotTrack(info) { "ram" - 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 plotTrack(info) { "ram" + 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 to convert lat/lon to XY - var getMapXY; - if (info.qOSTM) { - // scale map to view full track - const max = Bangle.project({lat: info.maxLat, lon: info.maxLong}); - const min = Bangle.project({lat: info.minLat, lon: info.minLong}); - const scaleX = (max.x-min.x)/Bangle.appRect.w; - const scaleY = (max.y-min.y)/Bangle.appRect.h; - osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin - getMapXY = osm.latLonToXY.bind(osm); - } else { - getMapXY = function(lat, lon) { "ram" - return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), - y:cy + Math.round((info.lat - lat)*info.scale)}; - }; - } + // Function to convert lat/lon to XY + var XY; + if (info.qOSTM) { + // scale map to view full track + const max = Bangle.project({lat: info.maxLat, lon: info.maxLong}); + const min = Bangle.project({lat: info.minLat, lon: info.minLong}); + const scaleX = (max.x-min.x)/Bangle.appRect.w; + const scaleY = (max.y-min.y)/Bangle.appRect.h; + osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin + XY = osm.latLonToXY.bind(osm); + } else { + XY = function(lat, lon) { "ram" + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; + }; + } - E.showMenu(); // remove menu - E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn); - g.flip(); // on buffered screens, draw a not saying we're busy - g.clear(1); - var s = require("Storage"); - var W = g.getWidth(); - var H = g.getHeight(); - var cx = W/2; - var cy = 24 + (H-24)/2; - if (!info.qOSTM) { - g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); - g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); - } else { - osm.lat = info.lat; - osm.lon = info.lon; - osm.draw(); - g.setColor("#000"); + E.showMenu(); // remove menu + E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); + var s = require("Storage"); + var G = g; + var W = g.getWidth(); + var H = g.getHeight(); + var cx = W/2; + var cy = 24 + (H-24)/2; + if (!info.qOSTM) { + g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); + g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); + } else { + osm.lat = info.lat; + osm.lon = info.lon; + 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 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 = 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 + // now iterate + var p,lp = Bangle.project({lat:c[1],lon:c[2]}); + var t,dx,dy,d,lt = c[timeIdx]; while(l!==undefined) { - c = l.split(",");l = f.readLine(f); - if (c[latIdx]=="")continue; - lat = +c[latIdx]; - long = +c[lonIdx]; - mp = getMapXY(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]; + ++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]++; } - } 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 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;i0) 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 8) { - grid*=2; + } else throw new Error("Unknown type "+style); + var min=100000,max=-100000; + for (var i=0;i0) 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; } } - // 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(); + var n = infn[i]; + if (n>max) max=n; + if (n 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(); } -showMainMenu(); +showMainMenu(); \ No newline at end of file diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index b0f42e1b4..a231a98e9 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "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.", "icon": "app.png", "tags": "tool,outdoors,gps,widget,clkinfo",