Merge pull request #2581 from nxdefiant/gpsmagdir

Adding gpsmagcourse service
pull/2735/head^2
Gordon Williams 2023-05-04 10:35:48 +01:00 committed by GitHub
commit 1c79c76193
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 291 additions and 0 deletions

View File

@ -0,0 +1,38 @@
# GPS Compass course switcher
The GPS course and speed is calculated by the difference of positions.
However GPS position is noisy and prone to jump around.
This results in randomly jumping GPS course values when speed is slow or standing still.
So in these cases a user might want to get the moving direction from a compass instead.
This is why this service replaces the GPS course with the compass heading when the speed is slower then 6 km/h (threshold is configurable, see settings).
You can switch between the built-in compass and "Navigation Compass" (magnav) as the source of the compass heading. When using magnav on Bangle.js 2 at least firmware 2v16.191 is recommended to get a three-dimensional reading.
## Important Notes
* **Watch orientation**
When the GPS is calculating the course the orientation of the watch does not matter.
When the Compass is used as the source of the current heading its top must obviously point at the moving direction (Usually away from you).
* **Compass reset and calibration**
When using "Navigation Compass" as compass source (see settings) please remember to calibrate it regularly. The author recommends to calibrate before every use and at least each time after attaching the charge cable.
With this service installed the built-in compass calibration is automatically reset when the compass is turned on (deactivatable in settings). It can also be reset with a tap on the Widget (Bangle.js 2 only). Please note that directly after a reset the built-in compass must be turned 360 degrees to provide a useable value.
* **True north vs magnetic north**
Please note that the compass does not point to the "real north" but depending on your location there is an offset, see [Magnetic declination](https://en.wikipedia.org/wiki/Magnetic_declination)
However the error from local magnetic interference and from calibration will probably be higher..
## Widget
The widget indicates if the current GPS course is provided from GPS or compass.
It can be turned off in the settings.
On Bangle.js 2 a click on the widget does reset the built-in compass, it has only an affect if the built-in compass is used.
## Settings
* **Speed threshold**
- (default = 6 km/h) When GPS speed is lower then this threshold use the compass direction. The speed must be for at least 10 seconds this fast to switch back to GPS course. The optimum threshold varies with the quality of the GPS reception.
* **Compass source**
- off: Disables this service.
- built-in (default if "Navigation Compass" is not installed): Uses the built-in compass. Its calibration can be restarted by pressing the Widget. The watch must be orientated horizontally for the compass heading to be used.
- magnav (default if "Navigation Compass" is installed and calibrated): Compass heading is provided by "Navigation Compass" (magnav).
* **Reset compass when powered on**
- Off: Do nothing when compass is turned on.
- On (default): The calibration of the built-in compass is reset when it is turned on.
* **Show Widget**
- Never: Widget is hidden.
- Active (default): Widget is only visible when replacing GPS course with compass heading.
- GPS on: Widget is shown as soon as GPS is enabled, crossed out when GPS provides the course and displayed normally when the compass heading is used.

BIN
apps/gpsmagcourse/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

79
apps/gpsmagcourse/boot.js Normal file
View File

@ -0,0 +1,79 @@
{
const settings = Object.assign({
speed: 6, // when lower then this use direction from compass
compassSrc: 2, // [off, built-in, magnav]
resetCompassOnPwr: true, // reset compass on power on
}, require("Storage").readJSON("gpsmagcourse.json", true) || {});
const CALIBDATA = (settings.compassSrc === 2) ? require("Storage").readJSON("magnav.json",1) : undefined;
let cntAboveSpeed = 0;
let lastGPS;
// Check if magnav is installed
try {
require("magnav");
} catch(err) {
// not installed, adjust settings to work without magnav
if (settings.compassSrc === 2) {
settings.compassSrc = 1;
}
}
if (settings.compassSrc === 2 && !CALIBDATA) {
// No calibration for magnav, fallback to built-in compass
settings.compassSrc = 1;
}
// execute Bangle.resetCompass() after Bangle.setCompassPower();
if (settings.resetCompassOnPwr) {
const origSetCompassPower = Bangle.setCompassPower;
Bangle.setCompassPower = function(on, id) {
const isOn = origSetCompassPower(on, id);
if (on) {
Bangle.resetCompass();
}
return isOn;
};
} // if (settings.resetCompassOnPwr)
if (settings.compassSrc > 0) {
const isFaceUp = (acc) => {
return (acc.z<-6700/8192) && (acc.z>-9000/8192) && Math.abs(acc.x)<2048/8192 && Math.abs(acc.y)<2048/8192;
};
const changeGpsCourse = (gps) => {
cntAboveSpeed = gps.speed < settings.speed ? 0 : cntAboveSpeed+1;
if (cntAboveSpeed < 10) { // need to stay x events above or equal threshold
if (settings.compassSrc === 1 && isFaceUp(Bangle.getAccel())) { // Use built-in compass heading only if face is up
const heading = Bangle.getCompass().heading;
if (!isNaN(heading)) {
gps.courseOrig = gps.course;
gps.course = Bangle.getCompass().heading;
}
} else if (settings.compassSrc === 2) { // magnav
gps.courseOrig = gps.course;
gps.course = require("magnav").tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
}
}
return gps;
};
// Modify GPS event
Bangle.on('GPS', gps => {
lastGPS = gps;
if (!isNaN(gps.course)) {
changeGpsCourse(gps);
}
});
const origGetGPSFix = Bangle.getGPSFix;
Bangle.getGPSFix = function() {
return lastGPS === undefined ? origGetGPSFix() : lastGPS;
};
// Enable Compass with GPS
const origSetGPSPower = Bangle.setGPSPower;
Bangle.setGPSPower = function(on, id) {
const isGPSon = origSetGPSPower(on, id);
Bangle.setCompassPower(isGPSon, "gpsmagcourse" + (id || ''));
return isGPSon;
};
} // if (settings.compassSrc > 0)
}

View File

@ -0,0 +1,19 @@
{
"id": "gpsmagcourse",
"name": "GPS Compass course switcher",
"shortName":"GPS/Compass course",
"icon": "app.png",
"version":"0.01",
"description": "Replaces the GPS course with the compass heading when speed is slow or standing still to avoid the value from jumping randomly. For best experience also install \"Navigation Compass\", although not a requirement (see README).",
"type": "bootloader",
"tags": "outdoors,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"gpsmagcourse.boot.js","url":"boot.js"},
{"name":"gpsmagcourse.wid.js","url":"widget.js"},
{"name":"gpsmagcourse.settings.js","url":"settings.js"}
],
"data": [{"name":"gpsmagcourse.json"}]
}

View File

@ -0,0 +1,75 @@
(function(back) {
var FILE = "gpsmagcourse.json";
// Load settings
const settings = Object.assign({
speed: 6, // when lower then this use direction from compass
compassSrc: 2, // [off, built-in, magnav]
resetCompassOnPwr: true, // reset compass on power on
showWidget: 2, // 0 = never, 1 = when replacing GPS course with compass course, 2 = when GPS is on
}, require("Storage").readJSON(FILE, true) || {});
let magnavInstalled = true;
// Check if magnav is installed
try {
require("magnav");
} catch(err) {
// not installed
magnavInstalled = false;
}
if (!magnavInstalled) {
// adjust settings to work without magnav
if (settings.compassSrc === 2) {
settings.compassSrc = 1;
}
}
const compassSrcOpts = [/*LANG*/"off", /*LANG*/"built-in"];
if (magnavInstalled) {
compassSrcOpts.push(/*LANG*/"magnav");
}
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
const menu = {
"" : { "title" : /*LANG*/"GPS/Com.course" },
"< Back" : () => back(),
/*LANG*/'Speed threshold': {
value: settings.speed,
min: 1, max: 20, step: 0.5,
onchange: v => {
settings.speed = v;
writeSettings();
}
},
/*LANG*/'Compass source': {
value: settings.compassSrc,
min: 0, max: compassSrcOpts.length-1,
format: v => compassSrcOpts[v],
onchange: v => {
settings.compassSrc = v;
writeSettings();
}
},
/*LANG*/'Reset compass when powered on': {
value: !!settings.resetCompassOnPwr,
onchange: v => {
settings.resetCompassOnPwr = v;
writeSettings();
}
},
/*LANG*/'Show Widget': {
value: settings.showWidget,
min: 0, max: 2,
format: v => [/*LANG*/"Never", /*LANG*/"Active", /*LANG*/"GPS on"][v],
onchange: v => {
settings.showWidget = v;
writeSettings();
}
},
};
// Show the menu
E.showMenu(menu);
})

View File

@ -0,0 +1,80 @@
(() => {
const settings = Object.assign({
compassSrc: 1, // 0 = off
showWidget: 2, // 0 = never, 1 = when replacing GPS course with compass course, 2 = when GPS is on
}, require("Storage").readJSON("gpsmagcourse.json", true) || {});
function isInside(rect, e) {
return e.x>=rect.x && e.x<rect.x+rect.w
&& e.y>=rect.y && e.y<=rect.y+rect.h;
}
function draw() {
if (this.width) {
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
if (this.show) {
this.width = 24;
g.reset();
g.drawImage(require("heatshrink").decompress(atob("jEYwgrohEN6EwBQ+DBYM4wALFxGA7vdB4IWFxEABYMAnAlECwMNBYPQCIQLDgALDDAI5EBYIFBBYIeBBYRBGA4QnBCAZBDA4ILLEZYLMKYR9FAgaKFNYpgCD4RBFAwQLBCwpOELAwACgeIwbLHK5ILPAAwA=")), this.x, this.y);
if (this.show === 2) {
// draw stroke
g.setColor(1,0,0).fillPoly([this.x+2, 0,
this.x+this.width-1,this.y+21,
this.x+this.width-3, this.y+23,
this.x, 2
]);
}
}
}
const newWidth = this.show ? 24 : 0;
if (newWidth !== this.width) {
this.width = newWidth;
Bangle.drawWidgets();
}
}
if (settings.compassSrc > 0 && settings.showWidget > 0) {
// add your widget
WIDGETS.gpsmagcourse={
area:"tr", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
width: 0, // hide by default
draw:draw,
show:0 // 0 = hide, 1 = show, 2 = with stroke
};
// show only when GPS course is replaced
Bangle.on('GPS', function(gps) {
if (gps.courseOrig && WIDGETS.gpsmagcourse.show !== 1 && Bangle.isGPSOn()) {
WIDGETS.gpsmagcourse.show = 1;
WIDGETS.gpsmagcourse.draw();
} else if (!gps.courseOrig && WIDGETS.gpsmagcourse.show === 1) {
WIDGETS.gpsmagcourse.show = settings.showWidget === 1 ? 0 : 2;
WIDGETS.gpsmagcourse.draw();
}
});
// hide widget if GPS is turned off
const origSetGPSPower = Bangle.setGPSPower;
Bangle.setGPSPower = function(on, id) {
const isGPSon = origSetGPSPower(on, id);
if (!isGPSon && WIDGETS.gpsmagcourse.show) {
WIDGETS.gpsmagcourse.show = 0;
WIDGETS.gpsmagcourse.draw();
} else if (isGPSon && !WIDGETS.gpsmagcourse.show) {
WIDGETS.gpsmagcourse.show = 2;
WIDGETS.gpsmagcourse.draw();
}
return isGPSon;
};
// reset compass on click on widget
Bangle.on('touch', function(button, touch) {
if (touch && WIDGETS.gpsmagcourse && WIDGETS.gpsmagcourse.x && WIDGETS.gpsmagcourse.width && isInside({x: WIDGETS.gpsmagcourse.x, y: WIDGETS.gpsmagcourse.y, w: WIDGETS.gpsmagcourse.width, h: 24}, touch)) {
Bangle.buzz(50);
Bangle.resetCompass();
}
});
} // if (settings.compassSrc > 0 && settings.showWidget)
})();