Merge pull request #2255 from halemmerich/gpstrek

gpstrek - Improvements
pull/2256/head
Gordon Williams 2022-11-11 15:20:16 +00:00 committed by GitHub
commit aa7c38a08c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 355 additions and 262 deletions

View File

@ -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

View File

@ -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();
}

View File

@ -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"}],

View File

@ -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);

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Less time used during boot if disabled
0.03: Fixed some test data

View File

@ -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,

View File

@ -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",