diff --git a/apps/gpstrek/ChangeLog b/apps/gpstrek/ChangeLog index d46ada767..c36c23c72 100644 --- a/apps/gpstrek/ChangeLog +++ b/apps/gpstrek/ChangeLog @@ -8,3 +8,8 @@ Fix widget adding listeners more than once 0.07: Show checkered flag for target markers Single waypoints are now shown in the compass view +0.08: Better handle state in widget + Slightly faster drawing by doing some caching + Reconstruct battery voltage by using calibrated batFullVoltage + Averaging for smoothing compass headings + Save state if route or waypoint has been chosen diff --git a/apps/gpstrek/app.js b/apps/gpstrek/app.js index f26811ed3..b3ec79fd2 100644 --- a/apps/gpstrek/app.js +++ b/apps/gpstrek/app.js @@ -1,48 +1,69 @@ + +{ //run in own scope for fast switch const STORAGE = require("Storage"); -const showWidgets = true; -let numberOfSlices=4; +const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144; + +let init = function(){ + global.screen = 1; + global.drawTimeout = undefined; + global.lastDrawnScreen = 0; + global.firstDraw = true; + global.slices = []; + global.maxScreens = 1; + global.scheduleDraw = false; -if (showWidgets){ Bangle.loadWidgets(); -} + WIDGETS.gpstrek.start(false); + if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 3; +}; -let state = WIDGETS.gpstrek.getState(); -WIDGETS.gpstrek.start(false); +let cleanup = function(){ + if (global.drawTimeout) clearTimeout(global.drawTimeout); + delete global.screen; + delete global.drawTimeout; + delete global.lastDrawnScreen; + delete global.firstDraw; + delete global.slices; + delete global.maxScreens; +}; -function parseNumber(toParse){ +init(); +scheduleDraw = true; + +let parseNumber = function(toParse){ if (toParse.includes(".")) return parseFloat(toParse); return parseFloat("" + toParse + ".0"); -} +}; -function parseWaypoint(filename, offset, result){ +let parseWaypoint = function(filename, offset, result){ result.lat = parseNumber(STORAGE.read(filename, offset, 11)); result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12)); return offset + 12; -} +}; -function parseWaypointWithElevation(filename, offset, result){ +let parseWaypointWithElevation = function (filename, offset, result){ offset = parseWaypoint(filename, offset, result); result.alt = parseNumber(STORAGE.read(filename, offset, 6)); return offset + 6; -} +}; -function parseWaypointWithName(filename, offset, result){ +let parseWaypointWithName = function(filename, offset, result){ offset = parseWaypoint(filename, offset, result); return parseName(filename, offset, result); -} +}; -function parseName(filename, offset, result){ +let parseName = function(filename, offset, result){ let nameLength = STORAGE.read(filename, offset, 2) - 0; result.name = STORAGE.read(filename, offset += 2, nameLength); return offset + nameLength; -} +}; -function parseWaypointWithElevationAndName(filename, offset, result){ +let parseWaypointWithElevationAndName = function(filename, offset, result){ offset = parseWaypointWithElevation(filename, offset, result); return parseName(filename, offset, result); -} +}; -function getEntry(filename, offset, result){ +let getEntry = function(filename, offset, result){ result.fileOffset = offset; let type = STORAGE.read(filename, offset++, 1); if (type == "") return -1; @@ -68,12 +89,12 @@ function getEntry(filename, offset, result){ result.fileLength = offset - result.fileOffset; //print(result); return offset; -} +}; const labels = ["N","NE","E","SE","S","SW","W","NW"]; const loc = require("locale"); -function matchFontSize(graphics, text, height, width){ +let matchFontSize = function(graphics, text, height, width){ graphics.setFontVector(height); let metrics; let size = 1; @@ -81,13 +102,19 @@ function matchFontSize(graphics, text, height, width){ size -= 0.05; graphics.setFont("Vector",Math.floor(height*size)); } -} +}; -function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){ +let getDoubleLineSlice = function(title1,title2,provider1,provider2,refreshTime){ let lastDrawn = Date.now() - Math.random()*refreshTime; + let lastValue1 = 0; + let lastValue2 = 0; return { refresh: function (){ - return Date.now() - lastDrawn > (Bangle.isLocked()?(refreshTime?refreshTime:5000):(refreshTime?refreshTime*2:10000)); + let bigChange1 = (Math.abs(lastValue1 - provider1()) > 1); + let bigChange2 = (Math.abs(lastValue2 - provider2()) > 1); + let refresh = (Bangle.isLocked()?(refreshTime?refreshTime*5:10000):(refreshTime?refreshTime*2:1000)); + let old = (Date.now() - lastDrawn) > refresh; + return (bigChange1 || bigChange2) && old; }, draw: function (graphics, x, y, height, width){ lastDrawn = Date.now(); @@ -95,29 +122,29 @@ function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){ if (typeof title2 == "function") title2 = title2(); graphics.clearRect(x,y,x+width,y+height); - let value = provider1(); - matchFontSize(graphics, title1 + value, Math.floor(height*0.5), width); + lastValue1 = provider1(); + matchFontSize(graphics, title1 + lastValue1, Math.floor(height*0.5), width); graphics.setFontAlign(-1,-1); graphics.drawString(title1, x+2, y); graphics.setFontAlign(1,-1); - graphics.drawString(value, x+width, y); + graphics.drawString(lastValue1, x+width, y); - value = provider2(); - matchFontSize(graphics, title2 + value, Math.floor(height*0.5), width); + lastValue2 = provider2(); + matchFontSize(graphics, title2 + lastValue2, Math.floor(height*0.5), width); graphics.setFontAlign(-1,-1); graphics.drawString(title2, x+2, y+(height*0.5)); graphics.setFontAlign(1,-1); - graphics.drawString(value, x+width, y+(height*0.5)); + graphics.drawString(lastValue2, x+width, y+(height*0.5)); } }; -} +}; -function getTargetSlice(targetDataSource){ +let getTargetSlice = function(targetDataSource){ let nameIndex = 0; let lastDrawn = Date.now() - Math.random()*3000; return { refresh: function (){ - return Date.now() - lastDrawn > (Bangle.isLocked()?10000:3000); + return Date.now() - lastDrawn > (Bangle.isLocked()?3000:10000); }, draw: function (graphics, x, y, height, width){ lastDrawn = Date.now(); @@ -174,9 +201,9 @@ function getTargetSlice(targetDataSource){ } } }; -} +}; -function drawCompass(graphics, x, y, height, width, increment, start){ +let drawCompass = function(graphics, x, y, height, width, increment, start){ graphics.setFont12x20(); graphics.setFontAlign(0,-1); graphics.setColor(graphics.theme.fg); @@ -197,14 +224,19 @@ function drawCompass(graphics, x, y, height, width, increment, start){ xpos+=increment*15; if (xpos > width + 20) break; } -} +}; -function getCompassSlice(compassDataSource){ +let getCompassSlice = function(compassDataSource){ let lastDrawn = Date.now() - Math.random()*2000; + let lastDrawnValue = 0; const buffers = 4; let buf = []; return { - refresh : function (){return Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true;}, + refresh : function (){ + let bigChange = (Math.abs(lastDrawnValue - compassDataSource.getCourse()) > 2); + let old = (Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true); + return bigChange && old; + }, draw: function (graphics, x,y,height,width){ lastDrawn = Date.now(); const max = 180; @@ -212,12 +244,14 @@ function getCompassSlice(compassDataSource){ graphics.clearRect(x,y,x+width,y+height); - var start = compassDataSource.getCourse() - 90; - if (isNaN(compassDataSource.getCourse())) start = -90; + lastDrawnValue = compassDataSource.getCourse(); + + var start = lastDrawnValue - 90; + if (isNaN(lastDrawnValue)) start = -90; if (start<0) start+=360; start = start % 360; - if (state.acc && compassDataSource.getCourseType() == "MAG"){ + if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG"){ drawCompass(graphics,0,y+width*0.05,height-width*0.05,width,increment,start); } else { drawCompass(graphics,0,y,height,width,increment,start); @@ -226,7 +260,8 @@ function getCompassSlice(compassDataSource){ if (compassDataSource.getPoints){ for (let p of compassDataSource.getPoints()){ - var bpos = p.bearing - compassDataSource.getCourse(); + g.reset(); + var bpos = p.bearing - lastDrawnValue; if (bpos>180) bpos -=360; if (bpos<-180) bpos +=360; bpos+=120; @@ -251,6 +286,7 @@ function getCompassSlice(compassDataSource){ } if (compassDataSource.getMarkers){ for (let m of compassDataSource.getMarkers()){ + g.reset(); g.setColor(m.fillcolor); let mpos = m.xpos * width; if (m.xpos < 0.05) mpos = Math.floor(width*0.05); @@ -263,9 +299,9 @@ function getCompassSlice(compassDataSource){ graphics.setColor(g.theme.fg); graphics.fillRect(x,y,Math.floor(width*0.05),y+height); graphics.fillRect(Math.ceil(width*0.95),y,width,y+height); - if (state.acc && compassDataSource.getCourseType() == "MAG") { - let xh = E.clip(width*0.5-height/2+(((state.acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2); - let yh = E.clip(y+(((state.acc.y+1)/2)*height),y,y+height); + if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG") { + let xh = E.clip(width*0.5-height/2+(((WIDGETS.gpstrek.getState().acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2); + let yh = E.clip(y+(((WIDGETS.gpstrek.getState().acc.y+1)/2)*height),y,y+height); graphics.fillRect(width*0.5 - height/2, y, width*0.5 + height/2, y + Math.floor(width*0.05)); @@ -287,44 +323,48 @@ function getCompassSlice(compassDataSource){ graphics.drawRect(Math.floor(width*0.05),y,Math.ceil(width*0.95),y+height); } }; -} +}; -function radians(a) { +let radians = function(a) { return a*Math.PI/180; -} +}; -function degrees(a) { - var d = a*180/Math.PI; +let degrees = function(a) { + let d = a*180/Math.PI; return (d+360)%360; -} +}; -function bearing(a,b){ +let bearing = function(a,b){ if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity; - var delta = radians(b.lon-a.lon); - var alat = radians(a.lat); - var blat = radians(b.lat); - var y = Math.sin(delta) * Math.cos(blat); - var x = Math.cos(alat)*Math.sin(blat) - + let delta = radians(b.lon-a.lon); + let alat = radians(a.lat); + let blat = radians(b.lat); + let y = Math.sin(delta) * Math.cos(blat); + let x = Math.cos(alat)*Math.sin(blat) - Math.sin(alat)*Math.cos(blat)*Math.cos(delta); return Math.round(degrees(Math.atan2(y, x))); -} +}; -function distance(a,b){ +let distance = function(a,b){ if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity; - var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); - var y = radians(b.lat-a.lat); + let x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + let y = radians(b.lat-a.lat); return Math.round(Math.sqrt(x*x + y*y) * 6371000); -} +}; -function triangle (x, y, width, height){ +let getAveragedCompass = function(){ + return Math.round(WIDGETS.gpstrek.getState().avgComp); +}; + +let triangle = function(x, y, width, height){ return [ Math.round(x),Math.round(y), Math.round(x+width * 0.5), Math.round(y+height), Math.round(x-width * 0.5), Math.round(y+height) ]; -} +}; -function onSwipe(dir){ +let onSwipe = function(dir){ if (dir < 0) { nextScreen(); } else if (dir > 0) { @@ -332,9 +372,9 @@ function onSwipe(dir){ } else { nextScreen(); } -} +}; -function setButtons(){ +let setButtons = function(){ let options = { mode: "custom", swipe: onSwipe, @@ -342,9 +382,9 @@ function setButtons(){ touch: nextScreen }; Bangle.setUI(options); -} +}; -function getApproxFileSize(name){ +let getApproxFileSize = function(name){ let currentStart = STORAGE.getStats().totalBytes; let currentSize = 0; for (let i = currentStart; i > 500; i/=2){ @@ -358,9 +398,9 @@ function getApproxFileSize(name){ currentSize += currentDiff; } return currentSize; -} +}; -function parseRouteData(filename, progressMonitor){ +let parseRouteData = function(filename, progressMonitor){ let routeInfo = {}; routeInfo.filename = filename; @@ -406,40 +446,40 @@ function parseRouteData(filename, progressMonitor){ set(routeInfo, 0); return routeInfo; -} +}; -function hasPrev(route){ +let hasPrev = function(route){ if (route.mirror) return route.index < (route.count - 1); return route.index > 0; -} +}; -function hasNext(route){ +let hasNext = function(route){ if (route.mirror) return route.index > 0; return route.index < (route.count - 1); -} +}; -function next(route){ +let next = function(route){ if (!hasNext(route)) return; if (route.mirror) set(route, --route.index); if (!route.mirror) set(route, ++route.index); -} +}; -function set(route, index){ +let set = function(route, index){ route.currentWaypoint = {}; route.index = index; getEntry(route.filename, route.refs[index], route.currentWaypoint); -} +}; -function prev(route){ +let prev = function(route){ if (!hasPrev(route)) return; if (route.mirror) set(route, ++route.index); if (!route.mirror) set(route, --route.index); -} +}; let lastMirror; let cachedLast; -function getLast(route){ +let getLast = function(route){ let wp = {}; if (lastMirror != route.mirror){ if (route.mirror) getEntry(route.filename, route.refs[0], wp); @@ -448,14 +488,14 @@ function getLast(route){ cachedLast = wp; } return cachedLast; -} +}; -function removeMenu(){ +let removeMenu = function(){ E.showMenu(); switchNav(); -} +}; -function showProgress(progress, title, max){ +let showProgress = function(progress, title, max){ //print("Progress",progress,max) let message = title? title: "Loading"; if (max){ @@ -466,17 +506,17 @@ function showProgress(progress, title, max){ for (let i = dots; i < 4; i++) message += " "; } E.showMessage(message); -} +}; -function handleLoading(c){ +let handleLoading = function(c){ E.showMenu(); - state.route = parseRouteData(c, showProgress); - state.waypoint = null; + WIDGETS.gpstrek.getState().route = parseRouteData(c, showProgress); + WIDGETS.gpstrek.getState().waypoint = null; + WIDGETS.gpstrek.getState().route.mirror = false; removeMenu(); - state.route.mirror = false; -} +}; -function showRouteSelector (){ +let showRouteSelector = function(){ var menu = { "" : { back : showRouteMenu, @@ -488,9 +528,9 @@ function showRouteSelector (){ }); E.showMenu(menu); -} +}; -function showRouteMenu(){ +let showRouteMenu = function(){ var menu = { "" : { "title" : "Route", @@ -499,48 +539,48 @@ function showRouteMenu(){ "Select file" : showRouteSelector }; - if (state.route){ + if (WIDGETS.gpstrek.getState().route){ menu.Mirror = { - value: state && state.route && !!state.route.mirror || false, + value: WIDGETS.gpstrek.getState() && WIDGETS.gpstrek.getState().route && !!WIDGETS.gpstrek.getState().route.mirror || false, onchange: v=>{ - state.route.mirror = v; + WIDGETS.gpstrek.getState().route.mirror = v; } }; menu['Select closest waypoint'] = function () { - if (state.currentPos && state.currentPos.lat){ - setClosestWaypoint(state.route, null, showProgress); removeMenu(); + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){ + setClosestWaypoint(WIDGETS.gpstrek.getState().route, null, showProgress); removeMenu(); } else { E.showAlert("No position").then(()=>{E.showMenu(menu);}); } }; menu['Select closest waypoint (not visited)'] = function () { - if (state.currentPos && state.currentPos.lat){ - setClosestWaypoint(state.route, state.route.index, showProgress); removeMenu(); + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){ + setClosestWaypoint(WIDGETS.gpstrek.getState().route, WIDGETS.gpstrek.getState().route.index, showProgress); removeMenu(); } else { E.showAlert("No position").then(()=>{E.showMenu(menu);}); } }; menu['Select waypoint'] = { - value : state.route.index, - min:1,max:state.route.count,step:1, - onchange : v => { set(state.route, v-1); } + value : WIDGETS.gpstrek.getState().route.index, + min:1,max:WIDGETS.gpstrek.getState().route.count,step:1, + onchange : v => { set(WIDGETS.gpstrek.getState().route, v-1); } }; menu['Select waypoint as current position'] = function (){ - state.currentPos.lat = state.route.currentWaypoint.lat; - state.currentPos.lon = state.route.currentWaypoint.lon; - state.currentPos.alt = state.route.currentWaypoint.alt; + WIDGETS.gpstrek.getState().currentPos.lat = WIDGETS.gpstrek.getState().route.currentWaypoint.lat; + WIDGETS.gpstrek.getState().currentPos.lon = WIDGETS.gpstrek.getState().route.currentWaypoint.lon; + WIDGETS.gpstrek.getState().currentPos.alt = WIDGETS.gpstrek.getState().route.currentWaypoint.alt; removeMenu(); }; } - if (state.route && hasPrev(state.route)) - menu['Previous waypoint'] = function() { prev(state.route); removeMenu(); }; - if (state.route && hasNext(state.route)) - menu['Next waypoint'] = function() { next(state.route); removeMenu(); }; + if (WIDGETS.gpstrek.getState().route && hasPrev(WIDGETS.gpstrek.getState().route)) + menu['Previous waypoint'] = function() { prev(WIDGETS.gpstrek.getState().route); removeMenu(); }; + if (WIDGETS.gpstrek.getState().route && hasNext(WIDGETS.gpstrek.getState().route)) + menu['Next waypoint'] = function() { next(WIDGETS.gpstrek.getState().route); removeMenu(); }; E.showMenu(menu); -} +}; -function showWaypointSelector(){ +let showWaypointSelector = function(){ let waypoints = require("waypoints").load(); var menu = { "" : { @@ -550,41 +590,41 @@ function showWaypointSelector(){ waypoints.forEach((wp,c)=>{ menu[waypoints[c].name] = function (){ - state.waypoint = waypoints[c]; - state.waypointIndex = c; - state.route = null; + WIDGETS.gpstrek.getState().waypoint = waypoints[c]; + WIDGETS.gpstrek.getState().waypointIndex = c; + WIDGETS.gpstrek.getState().route = null; removeMenu(); }; }); E.showMenu(menu); -} +}; -function showCalibrationMenu(){ +let showCalibrationMenu = function(){ let menu = { "" : { "title" : "Calibration", back : showMenu, }, "Barometer (GPS)" : ()=>{ - if (!state.currentPos || isNaN(state.currentPos.alt)){ + if (!WIDGETS.gpstrek.getState().currentPos || isNaN(WIDGETS.gpstrek.getState().currentPos.alt)){ E.showAlert("No GPS altitude").then(()=>{E.showMenu(menu);}); } else { - state.calibAltDiff = state.altitude - state.currentPos.alt; - E.showAlert("Calibrated Altitude Difference: " + state.calibAltDiff.toFixed(0)).then(()=>{removeMenu();}); + WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().currentPos.alt; + E.showAlert("Calibrated Altitude Difference: " + WIDGETS.gpstrek.getState().calibAltDiff.toFixed(0)).then(()=>{removeMenu();}); } }, "Barometer (Manual)" : { - value : Math.round(state.currentPos && (state.currentPos.alt != undefined && !isNaN(state.currentPos.alt)) ? state.currentPos.alt: state.altitude), + value : Math.round(WIDGETS.gpstrek.getState().currentPos && (WIDGETS.gpstrek.getState().currentPos.alt != undefined && !isNaN(WIDGETS.gpstrek.getState().currentPos.alt)) ? WIDGETS.gpstrek.getState().currentPos.alt: WIDGETS.gpstrek.getState().altitude), min:-2000,max: 10000,step:1, - onchange : v => { state.calibAltDiff = state.altitude - v; } + onchange : v => { WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - v; } }, "Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();}, }; E.showMenu(menu); -} +}; -function showWaypointMenu(){ +let showWaypointMenu = function(){ let menu = { "" : { "title" : "Waypoint", @@ -593,21 +633,21 @@ function showWaypointMenu(){ "Select waypoint" : showWaypointSelector, }; E.showMenu(menu); -} +}; -function showBackgroundMenu(){ +let showBackgroundMenu = function(){ let menu = { "" : { "title" : "Background", back : showMenu, }, - "Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});}, - "Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});}, + "Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});}, + "Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});}, }; E.showMenu(menu); -} +}; -function showMenu(){ +let showMenu = function(){ var mainmenu = { "" : { "title" : "Main", @@ -617,50 +657,55 @@ function showMenu(){ "Waypoint" : showWaypointMenu, "Background" : showBackgroundMenu, "Calibration": showCalibrationMenu, - "Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}});}, + "Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});}, "Info rows" : { - value : numberOfSlices, + value : WIDGETS.gpstrek.getState().numberOfSlices, min:1,max:6,step:1, - onchange : v => { setNumberOfSlices(v); } + onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; } }, }; E.showMenu(mainmenu); -} +}; -let scheduleDraw = true; -function switchMenu(){ - screen = 0; - scheduleDraw = false; - showMenu(); -} +let switchMenu = function(){ + stopDrawing(); + showMenu(); +}; -function drawInTimeout(){ - setTimeout(()=>{ +let stopDrawing = function(){ + if (drawTimeout) clearTimeout(drawTimeout); + scheduleDraw = false; +}; + +let drawInTimeout = function(){ + if (global.drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(()=>{ + drawTimeout = undefined; draw(); - if (scheduleDraw) - setTimeout(drawInTimeout, 0); - },0); -} + },50); +}; -function switchNav(){ +let switchNav = function(){ if (!screen) screen = 1; setButtons(); scheduleDraw = true; + firstDraw = true; drawInTimeout(); -} +}; -function nextScreen(){ +let nextScreen = function(){ screen++; if (screen > maxScreens){ screen = 1; } -} + drawInTimeout(); +}; -function setClosestWaypoint(route, startindex, progress){ - if (startindex >= state.route.count) startindex = state.route.count - 1; - if (!state.currentPos.lat){ +let setClosestWaypoint = function(route, startindex, progress){ + if (startindex >= WIDGETS.gpstrek.getState().route.count) startindex = WIDGETS.gpstrek.getState().route.count - 1; + if (!WIDGETS.gpstrek.getState().currentPos.lat){ set(route, startindex); return; } @@ -670,7 +715,7 @@ function setClosestWaypoint(route, startindex, progress){ if (progress && (i % 5 == 0)) progress(i-(startindex?startindex:0), "Searching", route.count); let wp = {}; getEntry(route.filename, route.refs[i], wp); - let curDist = distance(state.currentPos, wp); + let curDist = distance(WIDGETS.gpstrek.getState().currentPos, wp); if (curDist < minDist){ minDist = curDist; minIndex = i; @@ -679,30 +724,28 @@ function setClosestWaypoint(route, startindex, progress){ } } set(route, minIndex); -} - -let screen = 1; +}; const finishIcon = atob("CggB//meZmeZ+Z5n/w=="); const compassSliceData = { getCourseType: function(){ - return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG"; + return (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) ? "GPS" : "MAG"; }, getCourse: function (){ - if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course; - return state.compassHeading?state.compassHeading:undefined; + if(compassSliceData.getCourseType() == "GPS") return WIDGETS.gpstrek.getState().currentPos.course; + return getAveragedCompass(); }, getPoints: function (){ let points = []; - if (state.currentPos && state.currentPos.lon && state.route && state.route.currentWaypoint){ - points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"}); + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint){ + points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().route.currentWaypoint), color:"#0f0"}); } - if (state.currentPos && state.currentPos.lon && state.route){ - points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon}); + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route){ + points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, getLast(WIDGETS.gpstrek.getState().route)), icon: finishIcon}); } - if (state.currentPos && state.currentPos.lon && state.waypoint){ - points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon}); + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().waypoint){ + points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().waypoint), icon: finishIcon}); } return points; }, @@ -714,79 +757,74 @@ const compassSliceData = { const waypointData = { icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"), getProgress: function() { - return (state.route.index + 1) + "/" + state.route.count; + return (WIDGETS.gpstrek.getState().route.index + 1) + "/" + WIDGETS.gpstrek.getState().route.count; }, getTarget: function (){ - if (distance(state.currentPos,state.route.currentWaypoint) < 30 && hasNext(state.route)){ - next(state.route); + if (distance(WIDGETS.gpstrek.getState().currentPos,WIDGETS.gpstrek.getState().route.currentWaypoint) < 30 && hasNext(WIDGETS.gpstrek.getState().route)){ + next(WIDGETS.gpstrek.getState().route); Bangle.buzz(1000); } - return state.route.currentWaypoint; + return WIDGETS.gpstrek.getState().route.currentWaypoint; }, getStart: function (){ - return state.currentPos; + return WIDGETS.gpstrek.getState().currentPos; } }; const finishData = { icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="), getTarget: function (){ - if (state.route) return getLast(state.route); - if (state.waypoint) return state.waypoint; + if (WIDGETS.gpstrek.getState().route) return getLast(WIDGETS.gpstrek.getState().route); + if (WIDGETS.gpstrek.getState().waypoint) return WIDGETS.gpstrek.getState().waypoint; }, getStart: function (){ - return state.currentPos; + return WIDGETS.gpstrek.getState().currentPos; } }; -let sliceHeight; -function setNumberOfSlices(number){ - numberOfSlices = number; - sliceHeight = Math.floor((g.getHeight()-(showWidgets?24:0))/numberOfSlices); -} - -let slices = []; -let maxScreens = 1; -setNumberOfSlices(3); +let getSliceHeight = function(number){ + return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices); +}; let compassSlice = getCompassSlice(compassSliceData); let waypointSlice = getTargetSlice(waypointData); let finishSlice = getTargetSlice(finishData); let eleSlice = getDoubleLineSlice("Up","Down",()=>{ - return loc.distance(state.up,3) + "/" + (state.route ? loc.distance(state.route.up,3):"---"); + return loc.distance(WIDGETS.gpstrek.getState().up,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.up,3):"---"); },()=>{ - return loc.distance(state.down,3) + "/" + (state.route ? loc.distance(state.route.down,3): "---"); + return loc.distance(WIDGETS.gpstrek.getState().down,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.down,3): "---"); }); let statusSlice = getDoubleLineSlice("Speed","Alt",()=>{ let speed = 0; - if (state.currentPos && state.currentPos.speed) speed = state.currentPos.speed; + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.speed) speed = WIDGETS.gpstrek.getState().currentPos.speed; return loc.speed(speed,2); },()=>{ let alt = Infinity; - if (!isNaN(state.altitude)){ - alt = isNaN(state.calibAltDiff) ? state.altitude : (state.altitude - state.calibAltDiff); + if (!isNaN(WIDGETS.gpstrek.getState().altitude)){ + alt = isNaN(WIDGETS.gpstrek.getState().calibAltDiff) ? WIDGETS.gpstrek.getState().altitude : (WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().calibAltDiff); } - if (state.currentPos && state.currentPos.alt) alt = state.currentPos.alt; + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.alt) alt = WIDGETS.gpstrek.getState().currentPos.alt; + if (isNaN(alt)) return "---"; return loc.distance(alt,3); }); let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{ - return (state.compassHeading?Math.round(state.compassHeading):"---") + "°"; + return getAveragedCompass() + "°"; },()=>{ let course = "---°"; - if (state.currentPos && state.currentPos.course) course = state.currentPos.course + "°"; + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) course = WIDGETS.gpstrek.getState().currentPos.course + "°"; return course; },200); let healthSlice = getDoubleLineSlice("Heart","Steps",()=>{ - return state.bpm; + return WIDGETS.gpstrek.getState().bpm || "---"; },()=>{ - return state.steps; + return !isNaN(WIDGETS.gpstrek.getState().steps)? WIDGETS.gpstrek.getState().steps: "---"; }); let system2Slice = getDoubleLineSlice("Bat","",()=>{ - return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + NRF.getBattery().toFixed(2) + "V"; + return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + (analogRead(D3)*4.2/BAT_FULL).toFixed(2) + "V"; },()=>{ return ""; }); @@ -798,17 +836,17 @@ let systemSlice = getDoubleLineSlice("RAM","Storage",()=>{ return (STORAGE.getFree()/1024).toFixed(0)+"kB"; }); -function updateSlices(){ +let updateSlices = function(){ slices = []; slices.push(compassSlice); - if (state.currentPos && state.currentPos.lat && state.route && state.route.currentWaypoint && state.route.index < state.route.count - 1) { + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint && WIDGETS.gpstrek.getState().route.index < WIDGETS.gpstrek.getState().route.count - 1) { slices.push(waypointSlice); } - if (state.currentPos && state.currentPos.lat && (state.route || state.waypoint)) { + if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && (WIDGETS.gpstrek.getState().route || WIDGETS.gpstrek.getState().waypoint)) { slices.push(finishSlice); } - if ((state.route && state.route.down !== undefined) || state.down != undefined) { + if ((WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.down !== undefined) || WIDGETS.gpstrek.getState().down != undefined) { slices.push(eleSlice); } slices.push(statusSlice); @@ -816,42 +854,44 @@ function updateSlices(){ slices.push(healthSlice); slices.push(systemSlice); slices.push(system2Slice); - maxScreens = Math.ceil(slices.length/numberOfSlices); -} + maxScreens = Math.ceil(slices.length/WIDGETS.gpstrek.getState().numberOfSlices); +}; -function clear() { - g.clearRect(0,(showWidgets ? 24 : 0), g.getWidth(),g.getHeight()); -} -let lastDrawnScreen; -let firstDraw = true; +let clear = function() { + g.clearRect(Bangle.appRect); +}; -function draw(){ - if (!screen) return; - let ypos = showWidgets ? 24 : 0; +let draw = function(){ + if (!global.screen) return; + let ypos = Bangle.appRect.y; - let firstSlice = (screen-1)*numberOfSlices; + let firstSlice = (screen-1)*WIDGETS.gpstrek.getState().numberOfSlices; updateSlices(); let force = lastDrawnScreen != screen || firstDraw; if (force){ clear(); - if (showWidgets){ - Bangle.drawWidgets(); - } } + if (firstDraw) Bangle.drawWidgets(); lastDrawnScreen = screen; - for (let slice of slices.slice(firstSlice,firstSlice + numberOfSlices)) { + let sliceHeight = getSliceHeight(); + for (let slice of slices.slice(firstSlice,firstSlice + WIDGETS.gpstrek.getState().numberOfSlices)) { g.reset(); if (!slice.refresh || slice.refresh() || force) slice.draw(g,0,ypos,sliceHeight,g.getWidth()); ypos += sliceHeight+1; g.drawLine(0,ypos-1,g.getWidth(),ypos-1); } + + if (scheduleDraw){ + drawInTimeout(); + } firstDraw = false; -} +}; switchNav(); -g.clear(); +clear(); +} diff --git a/apps/gpstrek/metadata.json b/apps/gpstrek/metadata.json index cf5d06baa..3e27a3247 100644 --- a/apps/gpstrek/metadata.json +++ b/apps/gpstrek/metadata.json @@ -1,7 +1,7 @@ { "id": "gpstrek", "name": "GPS Trekking", - "version": "0.07", + "version": "0.08", "description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!", "icon": "icon.png", "screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}], diff --git a/apps/gpstrek/widget.js b/apps/gpstrek/widget.js index 347df2df5..363ade8ee 100644 --- a/apps/gpstrek/widget.js +++ b/apps/gpstrek/widget.js @@ -1,6 +1,28 @@ (() => { +const SAMPLES=5; +function initState(){ + //cleanup volatile state here + state = {}; + state.compassSamples = new Array(SAMPLES).fill(0); + state.lastSample = 0; + state.sampleIndex = 0; + state.currentPos={}; + state.steps = 0; + state.calibAltDiff = 0; + state.numberOfSlices = 3; + state.steps = 0; + state.up = 0; + state.down = 0; + state.saved = 0; + state.avgComp = 0; +} + const STORAGE=require('Storage'); -let state = STORAGE.readJSON("gpstrek.state.json")||{}; +let state = STORAGE.readJSON("gpstrek.state.json"); +if (!state) { + state = {}; + initState(); +} let bgChanged = false; function saveState(){ @@ -8,12 +30,13 @@ function saveState(){ STORAGE.writeJSON("gpstrek.state.json", state); } -E.on("kill",()=>{ - if (bgChanged){ +function onKill(){ + if (bgChanged || state.route || state.waypoint){ saveState(); } -}); +} +E.on("kill", onKill); function onPulse(e){ state.bpm = e.bpm; @@ -23,27 +46,47 @@ function onGPS(fix) { if(fix.fix) state.currentPos = fix; } -function onMag(e) { - if (!state.compassHeading) state.compassHeading = e.heading; +let radians = function(a) { + return a*Math.PI/180; +}; - //if (a+180)mod 360 == b then - //return (a+b)/2 mod 360 and ((a+b)/2 mod 360) + 180 (they are both the solution, so you may choose one depending if you prefer counterclockwise or clockwise direction) -//else - //return arctan( (sin(a)+sin(b)) / (cos(a)+cos(b) ) +let degrees = function(a) { + let d = a*180/Math.PI; + return (d+360)%360; +}; - /* - let average; - let a = radians(compassHeading); - let b = radians(e.heading); - if ((a+180) % 360 == b){ - average = ((a+b)/2 % 360); //can add 180 depending on rotation - } else { - average = Math.atan( (Math.sin(a)+Math.sin(b))/(Math.cos(a)+Math.cos(b)) ); +function average(samples){ + let s = 0; + let c = 0; + for (let h of samples){ + s += Math.sin(radians(h)); + c += Math.cos(radians(h)); + } + s /= samples.length; + c /= samples.length; + let result = degrees(Math.atan(s/c)); + + if (c < 0) result += 180; + if (s < 0 && c > 0) result += 360; + + result%=360; + return result; +} + +function onMag(e) { + if (!isNaN(e.heading)){ + if (Bangle.isLocked() || (Bangle.getGPSFix() && Bangle.getGPSFix().lon)) + state.avgComp = e.heading; + else { + state.compassSamples[state.sampleIndex++] = e.heading; + state.lastSample = Date.now(); + if (state.sampleIndex > SAMPLES - 1){ + state.sampleIndex = 0; + let avg = average(state.compassSamples); + state.avgComp = average([state.avgComp,avg]); + } + } } - print("Angle",compassHeading,e.heading, average); - compassHeading = (compassHeading + degrees(average)) % 360; - */ - state.compassHeading = Math.round(e.heading); } function onStep(e) { @@ -73,6 +116,16 @@ function onAcc (e){ state.acc = e; } +function update(){ + if (state.active){ + start(false); + } + if (state.active == !(WIDGETS.gpstrek.width)) { + if(WIDGETS.gpstrek) WIDGETS.gpstrek.width = state.active?24:0; + Bangle.drawWidgets(); + } +} + function start(bg){ Bangle.removeListener('GPS', onGPS); Bangle.removeListener("HRM", onPulse); @@ -94,9 +147,9 @@ function start(bg){ if (bg){ if (!state.active) bgChanged = true; state.active = true; + update(); saveState(); } - Bangle.drawWidgets(); } function stop(bg){ @@ -114,22 +167,10 @@ function stop(bg){ Bangle.removeListener("step", onStep); Bangle.removeListener("pressure", onPressure); Bangle.removeListener('accel', onAcc); + E.removeListener("kill", onKill); } + update(); saveState(); - Bangle.drawWidgets(); -} - -function initState(){ - //cleanup volatile state here - state.currentPos={}; - state.steps = Bangle.getStepCount(); - state.calibAltDiff = 0; - state.up = 0; - state.down = 0; -} - -if (state.saved && state.saved < Date.now() - 60000){ - initState(); } if (state.active){ @@ -141,11 +182,15 @@ WIDGETS["gpstrek"]={ width:state.active?24:0, resetState: initState, getState: function() { + if (state.saved && Date.now() - state.saved > 60000 || !state){ + initState(); + } return state; }, start:start, stop:stop, draw:function() { + update(); if (state.active){ g.reset(); g.drawImage(atob("GBiBAAAAAAAAAAAYAAAYAAAYAAA8AAA8AAB+AAB+AADbAADbAAGZgAGZgAMYwAMYwAcY4AYYYA5+cA3/sB/D+B4AeBAACAAAAAAAAA=="), this.x, this.y); diff --git a/apps/sensortools/ChangeLog b/apps/sensortools/ChangeLog index eb1ab5cbc..518e5dab4 100644 --- a/apps/sensortools/ChangeLog +++ b/apps/sensortools/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Less time used during boot if disabled +0.03: Fixed some test data diff --git a/apps/sensortools/lib.js b/apps/sensortools/lib.js index 460245c2f..595492251 100644 --- a/apps/sensortools/lib.js +++ b/apps/sensortools/lib.js @@ -278,12 +278,13 @@ exports.enable = () => { let currentLat=20; let currentLon=10; let currentCourse=10; - let currentAlt=-100; + let currentDir=1000; + let currentAlt=500; let currentSats=5; modGps(() => { - currentLat += 0.1; + currentLat += 0.01; if (currentLat > 50) currentLat = 20; - currentLon += 0.1; + currentLon += 0.01; if (currentLon > 20) currentLon = 10; currentSpeed *= 10; if (currentSpeed > 1000) currentSpeed = 1; @@ -291,8 +292,9 @@ exports.enable = () => { if (currentCourse > 360) currentCourse -= 360; currentSats += 1; if (currentSats > 10) currentSats = 5; - currentAlt *= 10; - if (currentAlt > 1000) currentAlt = -100; + currentAlt += currentDir; + if (currentAlt > 20000) currentDir = -2000; + if (currentAlt < -2000) currentDir = 2000; return { "lat": currentLat, "lon": currentLon, diff --git a/apps/sensortools/metadata.json b/apps/sensortools/metadata.json index 59a129ec1..9f7a36c5c 100644 --- a/apps/sensortools/metadata.json +++ b/apps/sensortools/metadata.json @@ -2,7 +2,7 @@ "id": "sensortools", "name": "Sensor tools", "shortName": "Sensor tools", - "version": "0.02", + "version": "0.03", "description": "Tools for testing and debugging apps that use sensor input", "icon": "icon.png", "type": "bootloader",