Merge pull request #706 from hughbarney/master
Kitchen Combo - multiclock combo of Stepo, Walkersclock, Waypointer and Arrow, can quickly switch betweenpull/707/head^2
20
apps.json
|
@ -3041,5 +3041,23 @@
|
|||
"evaluate": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "id": "kitchen",
|
||||
"name": "Kitchen Combo",
|
||||
"icon": "kitchen.png",
|
||||
"version":"0.01",
|
||||
"description": "Combination of the stepo, walkersclock, arrow and waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"readme": "README.md",
|
||||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"kitchen.app.js","url":"kitchen.app.js"},
|
||||
{"name":"stepo.kit.js","url":"stepo.kit.js"},
|
||||
{"name":"gps.kit.js","url":"gps.kit.js"},
|
||||
{"name":"digi.kit.js","url":"digi.kit.js"},
|
||||
{"name":"compass.kit.js","url":"compass.kit.js"},
|
||||
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
|
||||
{"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
# Kitchen Combo - a multiclock format of the waypointer, walksersclock, stopo and arrow apps.
|
||||
|
||||

|
||||
|
||||
*...everything but the kitchen sink..*
|
||||
|
||||
NOTE: This app require Bangle firmware 2.08.187 or later.
|
||||
|
||||
The app is aimed at navigation whilst walking. Please note that it
|
||||
would be foolish in the extreme to rely on this as your only
|
||||
navigation aid!
|
||||
|
||||
Please refer to the section on calibration of the compass. This
|
||||
should be done each time the app is going to be used.
|
||||
|
||||
The app has 4 faces that can quickly be switched from one to another.
|
||||
* Stepo - a large font clock that dosplays the current steps in a doughnut guauge
|
||||
* GPS - when the GPS is on displays current grid ref, lat, lon, speed, altitude and course
|
||||
* Digi - a digital clock with day and date, displays battery and memory status (click BTN1)
|
||||
* Waypointer - a compass arrow that points to a selected waypoint when the GPS is on.
|
||||
- enables you to mark waypoints and cycle through a list of waypoints
|
||||
- shows distance and bearing to currently selected waypoint
|
||||
|
||||
|
||||
## Common buttons used to navigate through the app
|
||||
|
||||
* BTN3 - short press, next app/clock face
|
||||
* BTN3 - long press, reset the watch
|
||||
* BTN2 - long press, start the app launcher
|
||||
|
||||
The following buttons depend on which face is currently in use
|
||||
|
||||
* BTN1 - Short press
|
||||
- Digi : Cycle the battery, memory display on the mode line
|
||||
- GPS : Cycle through the GPS data displays (grid ref, lat lon, speed, alt, course)
|
||||
- Waypointer : Select previous waypoint
|
||||
* BTN1 - long press
|
||||
- GPS : switch GPS on or off
|
||||
- Waypointer : set or unset the current waypoint
|
||||
* BTN2 - short press
|
||||
- Waypointer : select next waypoint
|
||||
|
||||
|
||||
## Stepo
|
||||

|
||||
|
||||
- Displays the time in large font
|
||||
- Display current step count in a doughnut guage
|
||||
- Show step count in the middle of the doughnut guage
|
||||
- The guage show percentage of steps out of a goal of 10000 steps
|
||||
- When the battery is less than 25% the doughnut turns red
|
||||
- Use BTN3 to switch to the next app
|
||||
|
||||
## GPS
|
||||

|
||||
- Use BTN1 long press to switch the GPS on or off
|
||||
- Use BTN1 short press to switch between the display of the Os grid refernce, lat lon, speed, alt, course.
|
||||
- Use BTN3 to switch to the next app
|
||||
|
||||
## Digi
|
||||

|
||||
- Displays the time in large font
|
||||
- Display day and date
|
||||
- Use BTN1 to switch between display of battery and memory %.
|
||||
- Use BTN3 to switch to the next app.
|
||||
|
||||
## Waypointer
|
||||
- Use BTN1 to select previous waypoint (when GPS is on)
|
||||
- Use BTN2 to select the next waypoint (when GPS is on)
|
||||
- Use BTN3 to switch to the next app
|
||||
- Use BTN1 long press to clear a waypoint or to record the current position
|
||||
|
||||
When the GPS is off this screen acts as a compass and points
|
||||
North. The white digits below the arrow show your current heading
|
||||
with reference to North.
|
||||
|
||||
When the GPS in on the screen points to the selected waypoint which
|
||||
are loaded from the waypoints.json file. The compass arrow now points
|
||||
in the direction you need to walk in. Once you have selected a
|
||||
waypoint a bearing from your current position (received from a GPS
|
||||
fix) is calculated and the compass is set to point in that direction.
|
||||
If the arrow is pointing to the left, turning left should straighten
|
||||
the arrow up so that it is pointing straight ahead.
|
||||
|
||||
The large digits are the bearing from the current position. On the
|
||||
left is the distance to the waypoint in local units. When the
|
||||
selected waypoint has a lat/lon recorded the text of the distance and
|
||||
waypoint name will be shown in blue. If the waypoint name is shown
|
||||
in white it means it is available to record a waypoint.
|
||||
|
||||
Use BTN1 and BTN2 to select the previous or next waypoint
|
||||
respectively. In the screen shot below a waypoint giving the location
|
||||
of Stone Henge has been selected.
|
||||
|
||||

|
||||
|
||||
The screenshot above shows that Stone Henge is 259.9 miles from the
|
||||
current location. To travel towards Stone Henge I need to turn
|
||||
slightly right until the arrow is pointing straight ahead. As you
|
||||
continue to walk in the pointed direction you should see the distance
|
||||
to the waypoint reduce. The frequency of updates will depend on
|
||||
which settings you have used in the GPS.
|
||||
|
||||
At the top of the screen you can see two widgets. These are the [GPS
|
||||
Power
|
||||
Widget](https://github.com/espruino/BangleApps/tree/master/apps/widgps)
|
||||
and the [Compass Power Indicator Widget]. These can be installed
|
||||
seperately and provide you a indication of when the GPS and Compass
|
||||
are switched on and drawing power.
|
||||
|
||||
|
||||
### Marking Waypoints
|
||||
|
||||
The app lets you mark your current location as follows. There are
|
||||
vacant slots in the waypoint file which can be allocated a
|
||||
location. In the distributed waypoint file these are labelled WP0 to
|
||||
WP4. Select one of these - WP2 is shown below.
|
||||
|
||||

|
||||
|
||||
Bearing and distance are both zero as WP2 has currently no GPS
|
||||
location associated with it. To mark the location, long press BTN1.
|
||||
|
||||

|
||||
|
||||
The app indicates that WP2 is now marked by changing the color to
|
||||
blue. The distance should be small as shown in the screen shot as you
|
||||
have just marked your current location.
|
||||
|
||||
You can free the waypoint by long pressing BTN1 again.
|
||||
|
||||
|
||||
### Waypoint JSON file
|
||||
|
||||
When the app is loaded from the app loader, a file named
|
||||
`waypoints.json` is loaded along with the javascript etc. The file
|
||||
has the following contents:
|
||||
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"name":"NONE"
|
||||
},
|
||||
{
|
||||
"name":"No10",
|
||||
"lat":51.5032,
|
||||
"lon":-0.1269
|
||||
},
|
||||
{
|
||||
"name":"Stone",
|
||||
"lat":51.1788,
|
||||
"lon":-1.8260
|
||||
},
|
||||
{ "name":"WP0" },
|
||||
{ "name":"WP1" },
|
||||
{ "name":"WP2" },
|
||||
{ "name":"WP3" },
|
||||
{ "name":"WP4" }
|
||||
]
|
||||
```
|
||||
|
||||
The file contains the initial NONE waypoint which is useful if you
|
||||
just want to display course and speed. The next two entries are
|
||||
waypoints to No 10 Downing Street and to Stone Henge - obtained from
|
||||
Google Maps. The last five entries are entries which can be *marked*.
|
||||
|
||||
You add and delete entries using the Web IDE to load and then save
|
||||
the file from and to watch storage. The app itself does not limit the
|
||||
number of entries although it does load the entire file into RAM
|
||||
which will obviously limit this.
|
||||
|
||||
|
||||
### Waypoint Editor
|
||||
|
||||
Clicking on the download icon of gpsnav in the app loader invokes the
|
||||
waypoint editor. The editor downloads and displays the current
|
||||
`waypoints.json` file. Clicking the `Edit` button beside an entry
|
||||
causes the entry to be deleted from the list and displayed in the
|
||||
edit boxes. It can be restored - by clicking the `Add waypoint`
|
||||
button. A new markable entry is created by using the `Add name`
|
||||
button. The edited `waypoints.json` file is uploaded to the Bangle by
|
||||
clicking the `Upload` button.
|
||||
|
||||
|
||||
### Calibration of the Compass
|
||||
|
||||
The Compass should be calibrated before using the App to navigate to
|
||||
a waypoint (or a series of waypoints). To do this use either the
|
||||
Arrow Compass or the [Navigation
|
||||
Compass](https://github.com/espruino/BangleApps/tree/master/apps/magnav).
|
||||
Open the compass app and clicking on BTN3. The calibration process
|
||||
takes 30 seconds during which you should move the watch slowly
|
||||
through figures of 8. It is important that during calibration the
|
||||
watch is fully rotated around each of it axes. If the app does give
|
||||
the correct direction heading or is not stable with respect to tilt
|
||||
and roll - redo the calibration by pressing *BTN3*. Calibration data
|
||||
is recorded in a storage file named `magnav.json`.
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
(() => {
|
||||
function getFace(){
|
||||
var intervalRefSec;
|
||||
var pal_by;
|
||||
var pal_bw;
|
||||
var pal_bb;
|
||||
var buf1;
|
||||
var buf2;
|
||||
var bearing;
|
||||
var heading;
|
||||
var oldHeading;
|
||||
var CALIBDATA;
|
||||
var previous;
|
||||
var wp;
|
||||
var wp_distance;
|
||||
var wp_bearing;
|
||||
var loc;
|
||||
var gpsObject;
|
||||
|
||||
function log_debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
function init(gps) {
|
||||
showMem("compass init() START");
|
||||
gpsObject = gps;
|
||||
pal_by = new Uint16Array([0x0000,0xFFC0],0,1); // black, yellow
|
||||
pal_bw = new Uint16Array([0x0000,0xffff],0,1); // black, white
|
||||
pal_bb = new Uint16Array([0x0000,0x07ff],0,1); // black, blue
|
||||
buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||
buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||
|
||||
intervalRefSec = undefined;
|
||||
bearing = 0; // always point north if GPS is off
|
||||
heading = 0;
|
||||
oldHeading = 0;
|
||||
previous = {bs:"-", dst:"-", wp_name:"-", course:999};
|
||||
loc = require("locale");
|
||||
CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
getWaypoint();
|
||||
|
||||
/*
|
||||
* compass should be powered on before startDraw is called
|
||||
* otherwise compass power widget will not come on
|
||||
*/
|
||||
if (!Bangle.isCompassOn()) Bangle.setCompassPower(1);
|
||||
gps.determineGPSState();
|
||||
|
||||
showMem("compass init() END");
|
||||
}
|
||||
|
||||
function freeResources() {
|
||||
showMem("compass freeResources() START");
|
||||
gpsObject = undefined;
|
||||
pal_by = undefined;
|
||||
pal_bw = undefined;
|
||||
pal_bb = undefined;
|
||||
buf1 = undefined;
|
||||
buf2 = undefined;
|
||||
intervalRefSec = undefined;
|
||||
previous = undefined;
|
||||
|
||||
bearing = 0;
|
||||
heading = 0;
|
||||
oldHeading = 0;
|
||||
loc = undefined;
|
||||
CALIBDATA = undefined;
|
||||
wp = undefined;
|
||||
if (Bangle.isCompassOn()) Bangle.setCompassPower(0);
|
||||
showMem("compass freeResources() END");
|
||||
}
|
||||
|
||||
function flip1(x,y) {
|
||||
g.drawImage({width:128,height:128,bpp:1,buffer:buf1.buffer, palette:pal_by},x ,y);
|
||||
buf1.clear();
|
||||
}
|
||||
|
||||
function flip2_bw(x,y) {
|
||||
g.drawImage({width:80,height:40,bpp:1,buffer:buf2.buffer, palette:pal_bw},x ,y);
|
||||
buf2.clear();
|
||||
}
|
||||
|
||||
function flip2_bb(x,y) {
|
||||
g.drawImage({width:80,height:40,bpp:1,buffer:buf2.buffer, palette:pal_bb},x ,y);
|
||||
buf2.clear();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
log_debug("startTimer()");
|
||||
if (!Bangle.isCompassOn()) Bangle.setCompassPower(1);
|
||||
resetPrevious();
|
||||
draw();
|
||||
intervalRefSec = setInterval(draw, 500);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
log_debug("stopTimer()");
|
||||
if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);}
|
||||
if (Bangle.isCompassOn()) Bangle.setCompassPower(0);
|
||||
}
|
||||
|
||||
function showMem(msg) {
|
||||
var val = process.memory();
|
||||
var str = msg + " " + Math.round(val.usage*100/val.total) + "%";
|
||||
log_debug(str);
|
||||
}
|
||||
|
||||
function onButtonShort(btn) {
|
||||
switch(btn) {
|
||||
case 1:
|
||||
log_debug("prev waypoint");
|
||||
gpsObject.nextWaypoint(-1);
|
||||
break;
|
||||
case 2:
|
||||
log_debug("next waypoint");
|
||||
gpsObject.nextWaypoint(1);
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
resetPrevious();
|
||||
getWaypoint();
|
||||
drawGPSData();
|
||||
}
|
||||
|
||||
function onButtonLong(btn) {
|
||||
log_debug("markWaypoint()");
|
||||
if (btn !== 1) return;
|
||||
if (gpsObject.getState() !== gpsObject.GPS_RUNNING) return;
|
||||
|
||||
log_debug("markWaypoint()");
|
||||
|
||||
gpsObject.markWaypoint();
|
||||
resetPrevious();
|
||||
getWaypoint();
|
||||
drawGPSData();
|
||||
}
|
||||
|
||||
function getWaypoint() {
|
||||
log_debug("getWaypoint()");
|
||||
wp = gpsObject.getCurrentWaypoint();
|
||||
wp_distance = gpsObject.getWPdistance();
|
||||
wp_bearing = gpsObject.getWPbearing();
|
||||
log_debug(wp);
|
||||
log_debug(wp_distance);
|
||||
log_debug(wp_bearing);
|
||||
}
|
||||
|
||||
// takes 32ms
|
||||
function drawCompass(hd) {
|
||||
if (Math.abs(hd - oldHeading) < 2) return 0;
|
||||
hd=hd*Math.PI/180;
|
||||
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
||||
|
||||
var poly = [
|
||||
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
||||
64+44.7214*Math.sin(hd+p[1]), 64-44.7214*Math.cos(hd+p[1]),
|
||||
64+28.2843*Math.sin(hd+p[2]), 64-28.2843*Math.cos(hd+p[2]),
|
||||
64+63.2455*Math.sin(hd+p[3]), 64-63.2455*Math.cos(hd+p[3]),
|
||||
64+63.2455*Math.sin(hd+p[4]), 64-63.2455*Math.cos(hd+p[4]),
|
||||
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
||||
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
||||
];
|
||||
|
||||
buf1.fillPoly(poly);
|
||||
flip1(56, 56);
|
||||
}
|
||||
|
||||
// stops violent compass swings and wobbles, takes 3ms
|
||||
function newHeading(m,h){
|
||||
//log_debug("newHeading()");
|
||||
var s = Math.abs(m - h);
|
||||
var delta = (m>h)?1:-1;
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s<2) return h;
|
||||
if (s<3) return h;
|
||||
var hd = h + delta*(1 + Math.round(s/5));
|
||||
if (hd<0) hd+=360;
|
||||
if (hd>360)hd-= 360;
|
||||
return hd;
|
||||
}
|
||||
|
||||
// takes approx 7ms
|
||||
function tiltfixread(O,S){
|
||||
//log_debug("tiltfixread()");
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
//log_debug("draw()");
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_RUNNING) {
|
||||
wp_dist = gpsObject.getWPdistance();
|
||||
wp_bearing = gpsObject.getWPbearing();
|
||||
bearing = wp_bearing;
|
||||
} else {
|
||||
bearing = 0;
|
||||
wp_distance = 0;
|
||||
wp_bearing = 0;
|
||||
}
|
||||
|
||||
var dir = bearing - heading;
|
||||
if (dir < 0) dir += 360;
|
||||
if (dir > 360) dir -= 360;
|
||||
var t = drawCompass(dir); // we want compass to show us where to go
|
||||
oldHeading = dir;
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_RUNNING) {
|
||||
drawGPSData();
|
||||
} else {
|
||||
drawCompassHeading();
|
||||
}
|
||||
}
|
||||
|
||||
function drawCompassHeading() {
|
||||
//log_debug("drawCompassHeading()");
|
||||
// draw the heading
|
||||
buf2.setColor(1);
|
||||
buf2.setFontAlign(-1,-1);
|
||||
buf2.setFont("Vector",38);
|
||||
var hding = Math.round(heading);
|
||||
var hd = hding.toString();
|
||||
hd = hding < 10 ? "00"+hd : hding < 100 ? "0"+hd : hd;
|
||||
buf2.drawString(hd,0,0);
|
||||
flip2_bw(90, 200);
|
||||
}
|
||||
|
||||
function drawGPSData() {
|
||||
log_debug("drawGPSData()");
|
||||
buf2.setFont("Vector",24);
|
||||
var bs = wp_bearing.toString();
|
||||
bs = wp_bearing<10?"00"+bs : wp_bearing<100 ?"0"+bs : bs;
|
||||
var dst = loc.distance(wp_distance);
|
||||
|
||||
log_debug(bs);
|
||||
log_debug(dst);
|
||||
|
||||
// -1=left (default), 0=center, 1=right
|
||||
|
||||
// show distance on the left
|
||||
if (previous.dst !== dst) {
|
||||
previous.dst = dst
|
||||
buf2.setColor(1);
|
||||
buf2.setFontAlign(-1,-1);
|
||||
buf2.setFont("Vector", 20);
|
||||
if (gpsObject.waypointHasLocation()) {
|
||||
buf2.drawString(dst,0,0);
|
||||
flip2_bb(0, 200);
|
||||
} else {
|
||||
buf2.drawString(" ",0,0);
|
||||
flip2_bw(0, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// bearing, place in middle at bottom of compass
|
||||
if (previous.bs !== bs) {
|
||||
previous.bs = bs;
|
||||
buf2.setColor(1);
|
||||
buf2.setFontAlign(0, -1);
|
||||
buf2.setFont("Vector",38);
|
||||
buf2.drawString(bs,40,0);
|
||||
flip2_bw(80, 200);
|
||||
}
|
||||
|
||||
// waypoint name on right
|
||||
if (previous.wp_name !== wp.name) {
|
||||
buf2.setColor(1);
|
||||
buf2.setFontAlign(1,-1); // right, bottom
|
||||
buf2.setFont("Vector", 20);
|
||||
buf2.drawString(wp.name, 80, 0);
|
||||
if (gpsObject.waypointHasLocation())
|
||||
flip2_bb(160, 200);
|
||||
else
|
||||
flip2_bw(160, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the attributes that control the display refresh
|
||||
function resetPrevious() {
|
||||
log_debug("resetPrevious()");
|
||||
previous = {bs:"-", dst:"-", wp_name:"-", course:999};
|
||||
}
|
||||
|
||||
return {init:init, freeResources:freeResources, startTimer:startTimer, stopTimer:stopTimer,
|
||||
onButtonShort:onButtonShort, onButtonLong:onButtonLong};
|
||||
}
|
||||
|
||||
return getFace;
|
||||
|
||||
})();
|
|
@ -0,0 +1,146 @@
|
|||
(() => {
|
||||
function getFace(){
|
||||
var intervalRefSec;
|
||||
var buf;
|
||||
var days;
|
||||
var prevInfo;
|
||||
var prevDate;
|
||||
var prevTime;
|
||||
var infoMode;
|
||||
|
||||
const INFO_NONE = 0;
|
||||
const INFO_BATT = 1;
|
||||
const INFO_MEM = 2;
|
||||
const Y_TIME = 30;
|
||||
const Y_ACTIVITY = 116;
|
||||
const Y_MODELINE = 200;
|
||||
|
||||
function init(gps) {
|
||||
showMem("digi init 1");
|
||||
days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday","Friday", "Saturday"];
|
||||
prevInfo = "";
|
||||
prevTimeStr = "";
|
||||
prevDateStr = "";
|
||||
infoMode = INFO_NONE;
|
||||
g.clear();
|
||||
showMem("digi init 2");
|
||||
}
|
||||
|
||||
function freeResources() {
|
||||
showMem("digi free 1");
|
||||
days = undefined;
|
||||
prevInfo = undefined;
|
||||
prevTime = undefined;
|
||||
prevDate = undefined;
|
||||
showMem("digi free 2");
|
||||
}
|
||||
|
||||
function showMem(msg) {
|
||||
var val = process.memory();
|
||||
var str = msg + " " + Math.round(val.usage*100/val.total) + "%";
|
||||
//console.log(str);
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
draw();
|
||||
intervalRefSec = setInterval(draw, 5000);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);}
|
||||
}
|
||||
|
||||
function onButtonShort(btn) {
|
||||
if (btn === 1) cycleInfoMode();
|
||||
}
|
||||
|
||||
function onButtonLong(btn) {}
|
||||
function getGPSfix() { return undefined; }
|
||||
function setGPSfix(f) {}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
var time = da[4].substr(0,5);
|
||||
|
||||
if (time !== prevTime) {
|
||||
prevTime = time;
|
||||
g.setColor(0);
|
||||
g.fillRect(0, Y_TIME, 239, Y_ACTIVITY -1);
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("Vector",80);
|
||||
g.setFontAlign(0,-1);
|
||||
g.drawString(time, 120, Y_TIME);
|
||||
}
|
||||
|
||||
var day = days[d.getDay()];
|
||||
var dateStr = da[2] + " " + da[1] + " " + da[3];
|
||||
|
||||
if (dateStr !== prevDate) {
|
||||
prevDate = dateStr;
|
||||
g.setColor(0);
|
||||
g.fillRect(0, Y_ACTIVITY, 239, Y_MODELINE - 3);
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("Vector",26);
|
||||
g.drawString(day, 120, Y_ACTIVITY);
|
||||
g.drawString(dateStr, 120, Y_ACTIVITY + 40);
|
||||
}
|
||||
|
||||
drawInfo();
|
||||
}
|
||||
|
||||
function cycleInfoMode() {
|
||||
switch(infoMode) {
|
||||
case INFO_NONE:
|
||||
infoMode = INFO_BATT;
|
||||
break;
|
||||
case INFO_BATT:
|
||||
infoMode = INFO_MEM
|
||||
break;
|
||||
case INFO_MEM:
|
||||
default:
|
||||
infoMode = INFO_NONE;
|
||||
break;
|
||||
}
|
||||
drawInfo();
|
||||
}
|
||||
|
||||
function drawInfo() {
|
||||
let val;
|
||||
let str = "";
|
||||
let col = 0x07FF; // cyan
|
||||
|
||||
switch(infoMode) {
|
||||
case INFO_NONE:
|
||||
col = 0x0000;
|
||||
str = "";
|
||||
break;
|
||||
case INFO_MEM:
|
||||
val = process.memory();
|
||||
str = "Memory: " + Math.round(val.usage*100/val.total) + "%";
|
||||
break;
|
||||
case INFO_BATT:
|
||||
default:
|
||||
str = "Battery: " + E.getBattery() + "%";
|
||||
}
|
||||
|
||||
// check if we need to draw, avoid flicker
|
||||
if (str == prevInfo)
|
||||
return;
|
||||
|
||||
prevInfo = str;
|
||||
//g.setFont("6x8", 3);
|
||||
g.setFont("Vector",26);
|
||||
g.setColor(col);
|
||||
g.fillRect(0, Y_MODELINE - 3, 239, Y_MODELINE + 25);
|
||||
g.setColor(0,0,0);
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(str, 120, Y_MODELINE);
|
||||
}
|
||||
|
||||
return {init:init, freeResources:freeResources, startTimer:startTimer, stopTimer:stopTimer,
|
||||
onButtonShort:onButtonShort, onButtonLong:onButtonLong};
|
||||
}
|
||||
|
||||
return getFace;
|
||||
})();
|
|
@ -0,0 +1,182 @@
|
|||
(() => {
|
||||
function getFace(){
|
||||
var intervalRefSec;
|
||||
|
||||
const GDISP_OS = 4;
|
||||
const GDISP_LATLN = 5;
|
||||
const GDISP_SPEED = 6;
|
||||
const GDISP_ALT = 7;
|
||||
const GDISP_COURSE = 8;
|
||||
|
||||
const Y_TIME = 30;
|
||||
const Y_ACTIVITY = 120;
|
||||
const Y_MODELINE = 200;
|
||||
|
||||
let gpsDisplay = GDISP_OS;
|
||||
let clearActivityArea = true;
|
||||
let gpsObject = undefined;
|
||||
|
||||
function log_debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
function init(gps) {
|
||||
log_debug("gps init");
|
||||
//log_debug(gps);
|
||||
gpsObject = gps;
|
||||
gpsDisplay = GDISP_OS;
|
||||
clearActivityArea = true;
|
||||
gpsObject.determineGPSState();
|
||||
}
|
||||
|
||||
function freeResources() {}
|
||||
|
||||
function startTimer() {
|
||||
draw();
|
||||
intervalRefSec = setInterval(draw, 5000);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);}
|
||||
}
|
||||
|
||||
function onButtonShort(btn) {
|
||||
if (btn === 1) cycleGPSDisplay();
|
||||
}
|
||||
|
||||
function onButtonLong(btn) {
|
||||
if (btn === 1) toggleGPSPower();
|
||||
}
|
||||
|
||||
function draw(){
|
||||
drawGPSTime();
|
||||
drawGPSData();
|
||||
}
|
||||
|
||||
function drawGPSTime() {
|
||||
var time = gpsObject.getGPSTime();
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0,Y_TIME, 239, Y_ACTIVITY - 1);
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(0, -1);
|
||||
|
||||
if (time.length > 5)
|
||||
g.setFont("Vector", 56);
|
||||
else
|
||||
g.setFont("Vector", 80);
|
||||
|
||||
g.drawString(time, 120, Y_TIME);
|
||||
}
|
||||
|
||||
function drawGPSData() {
|
||||
if (clearActivityArea) {
|
||||
g.clearRect(0, Y_ACTIVITY, 239, Y_MODELINE - 1);
|
||||
clearActivityArea = false;
|
||||
}
|
||||
|
||||
g.setFontVector(26);
|
||||
g.setColor(0xFFC0);
|
||||
g.setFontAlign(0, -1);
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_OFF) {
|
||||
g.drawString("GPS off", 120, Y_ACTIVITY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_TIME) {
|
||||
g.drawString("Waiting for", 120, Y_ACTIVITY);
|
||||
g.drawString("GPS", 120, Y_ACTIVITY + 36);
|
||||
return;
|
||||
}
|
||||
|
||||
let fx = gpsObject.getLastFix();
|
||||
|
||||
log_debug("gpsObject.getState()= " + gpsObject.getState());
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_SATS) {
|
||||
g.drawString("Satellites", 120, Y_ACTIVITY);
|
||||
g.drawString(fx.satellites, 120, Y_ACTIVITY + 36);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_RUNNING) {
|
||||
let time = gpsObject.formatTime(fx.time);
|
||||
let age = gpsObject.timeSince(time);
|
||||
let os = gpsObject.getOsRef();
|
||||
//let ref = to_map_ref(6, os.easting, os.northing);
|
||||
let speed;
|
||||
let activityStr = "";
|
||||
|
||||
if (age < 0) age = 0;
|
||||
g.setFontVector(40);
|
||||
g.setColor(0xFFC0);
|
||||
|
||||
switch(gpsDisplay) {
|
||||
case GDISP_OS:
|
||||
activityStr = os;
|
||||
break;
|
||||
case GDISP_LATLN:
|
||||
g.setFontVector(26);
|
||||
activityStr = fx.lat.toFixed(4) + ", " + fx.lon.toFixed(4);
|
||||
break;
|
||||
case GDISP_SPEED:
|
||||
speed = fx.speed;
|
||||
speed = speed.toFixed(1);
|
||||
activityStr = speed + "kph";
|
||||
break;
|
||||
case GDISP_ALT:
|
||||
activityStr = fx.alt + "m";
|
||||
break;
|
||||
case GDISP_COURSE:
|
||||
activityStr = fx.course;
|
||||
break;
|
||||
}
|
||||
|
||||
g.clearRect(0, Y_ACTIVITY, 239, Y_MODELINE - 1);
|
||||
g.drawString(activityStr, 120, Y_ACTIVITY);
|
||||
g.setFont("6x8",2);
|
||||
g.setColor(1,1,1);
|
||||
g.drawString(age, 120, Y_ACTIVITY + 46);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleGPSPower() {
|
||||
gpsObject.toggleGPSPower();
|
||||
clearActivityArea = true;
|
||||
draw();
|
||||
}
|
||||
|
||||
function cycleGPSDisplay() {
|
||||
if (gpsObject.getState() !== gpsObject.GPS_RUNNING) return;
|
||||
|
||||
switch (gpsDisplay) {
|
||||
case GDISP_OS:
|
||||
gpsDisplay = GDISP_SPEED;
|
||||
break;
|
||||
case GDISP_SPEED:
|
||||
gpsDisplay = GDISP_ALT;
|
||||
break;
|
||||
case GDISP_ALT:
|
||||
gpsDisplay = GDISP_LATLN;
|
||||
break;
|
||||
case GDISP_LATLN:
|
||||
gpsDisplay = GDISP_COURSE;
|
||||
break;
|
||||
case GDISP_COURSE:
|
||||
default:
|
||||
gpsDisplay = GDISP_OS;
|
||||
break;
|
||||
}
|
||||
|
||||
clearActivityArea = true;
|
||||
drawGPSData();
|
||||
}
|
||||
|
||||
return {init:init, freeResources:freeResources, startTimer:startTimer, stopTimer:stopTimer,
|
||||
onButtonShort:onButtonShort, onButtonLong:onButtonLong};
|
||||
}
|
||||
|
||||
return getFace;
|
||||
|
||||
})();
|
|
@ -0,0 +1,482 @@
|
|||
// read in the faces
|
||||
var FACES = [];
|
||||
var STOR = require("Storage");
|
||||
STOR.list(/\.kit\.js$/).forEach(face=>FACES.push(eval(require("Storage").read(face))));
|
||||
var iface = STOR.list(/\.kit\.js$/).indexOf("stepo.kit.js");
|
||||
var face = FACES[iface]();
|
||||
var firstPress
|
||||
var pressTimer;
|
||||
|
||||
function stopdraw() {
|
||||
face.stopTimer();
|
||||
}
|
||||
|
||||
function startdraw() {
|
||||
Bangle.drawWidgets();
|
||||
face.startTimer();
|
||||
}
|
||||
|
||||
function nextFace(){
|
||||
stopdraw();
|
||||
face.freeResources();
|
||||
|
||||
iface += 1
|
||||
iface = iface % FACES.length;
|
||||
face = FACES[iface]();
|
||||
|
||||
g.clear();
|
||||
g.reset();
|
||||
face.init(gpsObj);
|
||||
startdraw();
|
||||
}
|
||||
|
||||
// when you feel the buzzer you know you have done a long press
|
||||
function longPressCheck() {
|
||||
Bangle.buzz();
|
||||
if (pressTimer) {
|
||||
clearInterval(pressTimer);
|
||||
pressTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// start a timer and buzz when held long enough
|
||||
function buttonPressed(btn) {
|
||||
if (btn === 3) {
|
||||
nextFace();
|
||||
} else {
|
||||
firstPress = getTime();
|
||||
pressTimer = setInterval(longPressCheck, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
// if you release too soon there is no buzz as timer is cleared
|
||||
function buttonReleased(btn) {
|
||||
var dur = getTime() - firstPress;
|
||||
if (pressTimer) {
|
||||
clearInterval(pressTimer);
|
||||
pressTimer = undefined;
|
||||
}
|
||||
|
||||
if ( dur >= 1.5 ) {
|
||||
switch(btn) {
|
||||
case 1:
|
||||
face.onButtonLong(btn);
|
||||
break;
|
||||
case 2:
|
||||
Bangle.showLauncher();
|
||||
break;
|
||||
case 3:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (btn !== 3) face.onButtonShort(btn);
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(buttonPressed.bind(null,1), BTN1, {repeat:true,edge:"rising"});
|
||||
setWatch(buttonPressed.bind(null,2), BTN2, {repeat:true,edge:"rising"});
|
||||
setWatch(nextFace, BTN3, {repeat:true,edge:"rising"});
|
||||
|
||||
setWatch(buttonReleased.bind(null,1), BTN1, {repeat:true,edge:"falling"});
|
||||
setWatch(buttonReleased.bind(null,2), BTN2, {repeat:true,edge:"falling"});
|
||||
// BTN 3 long press should always reset the bangle
|
||||
}
|
||||
|
||||
Bangle.on('kill',()=>{
|
||||
Bangle.setCompassPower(0);
|
||||
Bangle.setGPSPower(0);
|
||||
});
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on) {
|
||||
startdraw();
|
||||
} else {
|
||||
stopdraw();
|
||||
}
|
||||
});
|
||||
|
||||
/*****************************************************************************
|
||||
|
||||
Start of GPS object code so we can share it between faces
|
||||
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
function log_debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
|
||||
function degrees(a) {
|
||||
var d = a*180/Math.PI;
|
||||
return (d+360)%360;
|
||||
}
|
||||
|
||||
function GPS() {
|
||||
this.wp;
|
||||
this.wp_index = 0;
|
||||
this.wp_current = undefined;
|
||||
this.GPS_OFF = 0;
|
||||
this.GPS_TIME = 1;
|
||||
this.GPS_SATS = 2;
|
||||
this.GPS_RUNNING = 3;
|
||||
this.gpsState = this.GPS_OFF;
|
||||
this.gpsPowerState = false;
|
||||
this.last_fix = {
|
||||
fix: 0,
|
||||
alt: 0,
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
speed: 0,
|
||||
time: 0,
|
||||
satellites: 0
|
||||
};
|
||||
this.listenerCount = 0;
|
||||
this.loadFirstWaypoint();
|
||||
}
|
||||
|
||||
GPS.prototype.log_debug = function(o) {
|
||||
//console.log(o);
|
||||
};
|
||||
|
||||
GPS.prototype.getState = function() {
|
||||
return this.gpsState;
|
||||
}
|
||||
|
||||
GPS.prototype.getLastFix = function() {
|
||||
return this.last_fix;
|
||||
}
|
||||
|
||||
GPS.prototype.determineGPSState = function() {
|
||||
this.log_debug("determineGPSState");
|
||||
gpsPowerState = Bangle.isGPSOn();
|
||||
|
||||
//this.log_debug("last_fix.fix " + this.last_fix.fix);
|
||||
//this.log_debug("gpsPowerState " + this.gpsPowerState);
|
||||
//this.log_debug("last_fix.satellites " + this.last_fix.satellites);
|
||||
|
||||
if (!gpsPowerState) {
|
||||
this.gpsState = this.GPS_OFF;
|
||||
this.resetLastFix();
|
||||
} else if (this.last_fix.fix && this.gpsPowerState && this.last_fix.satellites > 0) {
|
||||
this.gpsState = this.GPS_RUNNING;
|
||||
} else {
|
||||
this.gpsState = this.GPS_SATS;
|
||||
}
|
||||
|
||||
this.log_debug("gpsState=" + this.gpsState);
|
||||
|
||||
if (this.gpsState !== this.GPS_OFF) {
|
||||
if (this.listenerCount === 0) {
|
||||
Bangle.on('GPS', processFix);
|
||||
this.listenerCount++;
|
||||
this.log_debug("listener added " + this.listenerCount);
|
||||
}
|
||||
} else {
|
||||
if (this.listenerCount > 0) {
|
||||
Bangle.removeListener("GPS", this.processFix);
|
||||
this.listenerCount--;
|
||||
this.log_debug("listener removed " + this.listenerCount);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
GPS.prototype.getGPSTime = function() {
|
||||
var time;
|
||||
|
||||
if (this.last_fix !== undefined && this.last_fix.time !== undefined && this.last_fix.time.toUTCString !== undefined &&
|
||||
(this.gpsState == this.GPS_SATS || this.gpsState == this.GPS_RUNNING)) {
|
||||
time = this.last_fix.time.toUTCString().split(" ");
|
||||
return time[4];
|
||||
} else {
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
time = da[4].substr(0,5);
|
||||
return time;
|
||||
}
|
||||
};
|
||||
|
||||
GPS.prototype.toggleGPSPower = function() {
|
||||
this.log_debug("toggleGPSPower()");
|
||||
this.gpsPowerState = Bangle.isGPSOn();
|
||||
this.gpsPowerState = !this.gpsPowerState;
|
||||
Bangle.setGPSPower(this.gpsPowerState ? 1 : 0);
|
||||
|
||||
this.resetLastFix();
|
||||
this.determineGPSState();
|
||||
|
||||
// poke the gps widget indicator to change
|
||||
if (WIDGETS.gps !== undefined) {
|
||||
WIDGETS.gps.draw();
|
||||
}
|
||||
};
|
||||
|
||||
GPS.prototype.resetLastFix = function() {
|
||||
this.last_fix = {
|
||||
fix: 0,
|
||||
alt: 0,
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
speed: 0,
|
||||
time: 0,
|
||||
satellites: 0
|
||||
};
|
||||
};
|
||||
|
||||
function processFix(fix) {
|
||||
//log_debug("processFix()");
|
||||
gpsObj.processFix(fix);
|
||||
}
|
||||
|
||||
GPS.prototype.processFix = function(fix) {
|
||||
//this.log_debug("GPS:processFix()");
|
||||
//this.log_debug(fix);
|
||||
this.last_fix.time = fix.time;
|
||||
|
||||
if (this.gpsState == this.GPS_TIME) {
|
||||
this.gpsState = this.GPS_SATS;
|
||||
}
|
||||
|
||||
if (fix.fix) {
|
||||
//this.log_debug("Got fix - setting state to GPS_RUNNING");
|
||||
this.gpsState = this.GPS_RUNNING;
|
||||
if (!this.last_fix.fix) Bangle.buzz(); // buzz on first position
|
||||
this.last_fix = fix;
|
||||
}
|
||||
};
|
||||
|
||||
GPS.prototype.formatTime = function(now) {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
return fd[4];
|
||||
};
|
||||
|
||||
GPS.prototype.timeSince = function(t) {
|
||||
var hms = t.split(":");
|
||||
var now = new Date();
|
||||
|
||||
var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds());
|
||||
var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]);
|
||||
|
||||
return (sn - st);
|
||||
};
|
||||
|
||||
GPS.prototype.getOsRef = function() {
|
||||
let os = OsGridRef.latLongToOsGrid(this.last_fix);
|
||||
let ref = to_map_ref(6, os.easting, os.northing);
|
||||
return ref;
|
||||
};
|
||||
|
||||
GPS.prototype.calcBearing = function(a,b) {
|
||||
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) -
|
||||
Math.sin(alat)*Math.cos(blat)*Math.cos(delta);
|
||||
return Math.round(degrees(Math.atan2(y, x)));
|
||||
}
|
||||
|
||||
GPS.prototype.calcDistance = function(a,b) {
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
|
||||
}
|
||||
|
||||
GPS.prototype.getWPdistance = function() {
|
||||
//log_debug(this.last_fix);
|
||||
//log_debug(this.wp_current);
|
||||
|
||||
if (this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0)
|
||||
return 0;
|
||||
else
|
||||
return this.calcDistance(this.last_fix, this.wp_current);
|
||||
}
|
||||
|
||||
GPS.prototype.getWPbearing = function() {
|
||||
//log_debug(this.last_fix);
|
||||
//log_debug(this.wp_current);
|
||||
|
||||
if (this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0)
|
||||
return 0;
|
||||
else
|
||||
return this.calcBearing(this.last_fix, this.wp_current);
|
||||
}
|
||||
|
||||
GPS.prototype.loadFirstWaypoint = function() {
|
||||
var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
|
||||
this.wp_index = 0;
|
||||
this.wp_current = waypoints[this.wp_index];
|
||||
log_debug(this.wp_current);
|
||||
return this.wp_current;
|
||||
}
|
||||
|
||||
GPS.prototype.getCurrentWaypoint = function() {
|
||||
return this.wp_current;
|
||||
}
|
||||
|
||||
GPS.prototype.waypointHasLocation = function() {
|
||||
if (this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
GPS.prototype.markWaypoint = function() {
|
||||
|
||||
if(this.wp_current.name === "NONE")
|
||||
return;
|
||||
|
||||
log_debug("GPS::markWaypoint()");
|
||||
|
||||
var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
|
||||
this.wp_current = waypoints[this.wp_index];
|
||||
|
||||
if (this.waypointHasLocation()) {
|
||||
waypoints[this.wp_index] = {name:this.wp_current.name, lat:0, lon:0};
|
||||
} else {
|
||||
waypoints[this.wp_index] = {name:this.wp_current.name, lat:this.last_fix.lat, lon:this.last_fix.lon};
|
||||
}
|
||||
|
||||
this.wp_current = waypoints[this.wp_index];
|
||||
require("Storage").writeJSON("waypoints.json", waypoints);
|
||||
log_debug("GPS::markWaypoint() written");
|
||||
}
|
||||
|
||||
GPS.prototype.nextWaypoint = function(inc) {
|
||||
var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
|
||||
this.wp_index+=inc;
|
||||
if (this.wp_index>=waypoints.length) this.wp_index=0;
|
||||
if (this.wp_index<0) this.wp_index = waypoints.length-1;
|
||||
this.wp_current = waypoints[this.wp_index];
|
||||
log_debug(this.wp_current);
|
||||
return this.wp_current;
|
||||
}
|
||||
|
||||
var gpsObj = new GPS();
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
|
||||
Start of OS lat lon to grid ref code
|
||||
|
||||
******************************************************************************/
|
||||
|
||||
Number.prototype.toRad = function() { return this*Math.PI/180; };
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */
|
||||
/* - www.movable-type.co.uk/scripts/gridref.js */
|
||||
/* - www.movable-type.co.uk/scripts/latlon-gridref.html */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
function OsGridRef(easting, northing) {
|
||||
this.easting = 0|easting;
|
||||
this.northing = 0|northing;
|
||||
}
|
||||
|
||||
OsGridRef.latLongToOsGrid = function(point) {
|
||||
var lat = point.lat.toRad();
|
||||
var lon = point.lon.toRad();
|
||||
|
||||
var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
|
||||
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
|
||||
var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W
|
||||
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
|
||||
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
|
||||
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
|
||||
|
||||
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
|
||||
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
|
||||
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
|
||||
var eta2 = nu/rho-1;
|
||||
|
||||
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
|
||||
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
|
||||
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
|
||||
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
|
||||
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
|
||||
|
||||
var cos3lat = cosLat*cosLat*cosLat;
|
||||
var cos5lat = cos3lat*cosLat*cosLat;
|
||||
var tan2lat = Math.tan(lat)*Math.tan(lat);
|
||||
var tan4lat = tan2lat*tan2lat;
|
||||
|
||||
var I = M + N0;
|
||||
var II = (nu/2)*sinLat*cosLat;
|
||||
var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
|
||||
var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
|
||||
var IV = nu*cosLat;
|
||||
var V = (nu/6)*cos3lat*(nu/rho-tan2lat);
|
||||
var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
|
||||
|
||||
var dLon = lon-lon0;
|
||||
var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon;
|
||||
|
||||
var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6;
|
||||
var E = E0 + IV*dLon + V*dLon3 + VI*dLon5;
|
||||
|
||||
return new OsGridRef(E, N);
|
||||
};
|
||||
|
||||
/*
|
||||
* converts northing, easting to standard OS grid reference.
|
||||
*
|
||||
* [digits=10] - precision (10 digits = metres)
|
||||
* to_map_ref(8, 651409, 313177); => 'TG 5140 1317'
|
||||
* to_map_ref(0, 651409, 313177); => '651409,313177'
|
||||
*
|
||||
*/
|
||||
function to_map_ref(digits, easting, northing) {
|
||||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
|
||||
|
||||
let e = easting;
|
||||
let n = northing;
|
||||
|
||||
// use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7
|
||||
if (digits == 0) {
|
||||
const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 };
|
||||
const ePad = e.toLocaleString('en', format);
|
||||
const nPad = n.toLocaleString('en', format);
|
||||
return `${ePad},${nPad}`;
|
||||
}
|
||||
|
||||
// get the 100km-grid indices
|
||||
const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000);
|
||||
|
||||
// translate those into numeric equivalents of the grid letters
|
||||
let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5);
|
||||
let l2 = (19 - n100km) * 5 % 25 + e100km % 5;
|
||||
|
||||
// compensate for skipped 'I' and build grid letter-pairs
|
||||
if (l1 > 7) l1++;
|
||||
if (l2 > 7) l2++;
|
||||
const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0));
|
||||
|
||||
// strip 100km-grid indices from easting & northing, and reduce precision
|
||||
e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2));
|
||||
n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2));
|
||||
|
||||
// pad eastings & northings with leading zeros
|
||||
e = e.toString().padStart(digits/2, '0');
|
||||
n = n.toString().padStart(digits/2, '0');
|
||||
|
||||
return `${letterPair} ${e} ${n}`;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
|
||||
End of GPS object
|
||||
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
face.init(gpsObj);
|
||||
startdraw();
|
||||
setButtons();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/AH4AVgAACF1crnNkF9QuBLoMHF9cyGIYuprovBX1cAsgABrovYJSBeBF4SPBFy8HmQbOLwdkFzAtBmYcNCIIuCYAIvXLgJ/BF5pecAAy9OdqgmCg8rmUznMyAAJkCEI5eEdqQgBU4YAJrsHEYheErwuSFpoxEEoa9WIwgwTgFeAwQvQLqQwFaYa7TLyYuBmQYBdKQuCIoUrlcyABcrKwJcWXgZJBEIQiBAAQlEEwI+BmU5CgINDF6ZgFGQI3BAAkzLw8OLygvBg8VhwaBLY5eEg8OisVjgABhwvVhwaCACgv/F78VF/4v/F8sHd9owBF68HFyhgYRyowDYQMVFqMHFy4xEAAMHg41BAAIrFiq6BAAIuZGhIAIFj4A/AH4A/AH4AWA=="))
|
|
@ -0,0 +1,8 @@
|
|||
require("Storage").write("kitchen.info",{
|
||||
"id":"kitchen",
|
||||
"name":"Kitchen",
|
||||
"src":"kitchen.app.js",
|
||||
"icon":"kitchen.img",
|
||||
"type":"clock",
|
||||
"files":"kitchen.info, kitchen.app.js, kitchen.img, stepo.kit.js, digi.kit.js, compass.kit.js, gps.kit.js"
|
||||
});
|
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 51 KiB |
|
@ -0,0 +1,136 @@
|
|||
(() => {
|
||||
function getFace(){
|
||||
var pal4color;
|
||||
var pal4red;
|
||||
var buf;
|
||||
var intervalRefSec;
|
||||
|
||||
function init(g) {
|
||||
showMem("stepo init 1");
|
||||
pal4color = new Uint16Array([0x0000,0xFFFF,0x7BEF,0xAFE5],0,2); // b,w,grey,greenyellow
|
||||
pal4red = new Uint16Array([0x0000,0xFFFF,0xF800,0xAFE5],0,2); // b,w,red,greenyellow
|
||||
buf = Graphics.createArrayBuffer(120,120,2,{msb:true});
|
||||
showMem("stepo init 2");
|
||||
}
|
||||
|
||||
function freeResources() {
|
||||
showMem("stepo free 1");
|
||||
pal4color = undefined;
|
||||
pal4red = undefined;
|
||||
buf = undefined;
|
||||
showMem("stepo free 2");
|
||||
}
|
||||
|
||||
function showMem(msg) {
|
||||
var val = process.memory();
|
||||
var str = msg + " " + Math.round(val.usage*100/val.total) + "%";
|
||||
//console.log(str);
|
||||
}
|
||||
|
||||
function flip(x,y) {
|
||||
g.drawImage({width:120,height:120,bpp:2,buffer:buf.buffer, palette:pal4color}, x, y);
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
function flip_red(x,y) {
|
||||
g.drawImage({width:120,height:120,bpp:2,buffer:buf.buffer, palette:pal4red}, x, y);
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
function onButtonShort(btn) {}
|
||||
function onButtonLong(btn) {}
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
draw();
|
||||
intervalRefSec = setInterval(draw, 5000);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);}
|
||||
}
|
||||
|
||||
function drawSteps() {
|
||||
var i = 0;
|
||||
var cx = 60;
|
||||
var cy = 60;
|
||||
var r = 56;
|
||||
var steps = getSteps();
|
||||
var percent = steps / 10000;
|
||||
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
var startrot = 0 - 180;
|
||||
var midrot = -180 - (360 * percent);
|
||||
var endrot = -360 - 180;
|
||||
|
||||
buf.setColor(3); // green-yellow
|
||||
|
||||
// draw guauge
|
||||
for (i = startrot; i > midrot; i -= 4) {
|
||||
x = cx + r * Math.sin(radians(i));
|
||||
y = cy + r * Math.cos(radians(i));
|
||||
buf.fillCircle(x,y,4);
|
||||
}
|
||||
|
||||
buf.setColor(2); // grey
|
||||
|
||||
// draw remainder of guage in grey
|
||||
for (i = midrot; i > endrot; i -= 4) {
|
||||
x = cx + r * Math.sin(radians(i));
|
||||
y = cy + r * Math.cos(radians(i));
|
||||
buf.fillCircle(x,y,4);
|
||||
}
|
||||
|
||||
// draw steps
|
||||
buf.setColor(1); // white
|
||||
buf.setFont("Vector", 24);
|
||||
buf.setFontAlign(0,0);
|
||||
buf.drawString(steps, cx, cy);
|
||||
|
||||
// change the remaining color to RED if battery is below 25%
|
||||
if (E.getBattery() > 25)
|
||||
flip(60,115);
|
||||
else
|
||||
flip_red(60,115);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
var time = da[4].substr(0,5);
|
||||
|
||||
g.clearRect(0, 30, 239, 99);
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(0, -1);
|
||||
g.setFont("Vector", 80);
|
||||
g.drawString(time, 120, 30, true);
|
||||
|
||||
drawSteps();
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
if (stepsWidget() !== undefined)
|
||||
return stepsWidget().getSteps();
|
||||
return "-";
|
||||
}
|
||||
|
||||
function stepsWidget() {
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom;
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {init:init, freeResources:freeResources, startTimer:startTimer, stopTimer:stopTimer,
|
||||
onButtonShort:onButtonShort, onButtonLong:onButtonLong};
|
||||
}
|
||||
|
||||
return getFace;
|
||||
|
||||
})();
|
|
@ -0,0 +1,170 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h4>List of waypoints</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Lat.</th>
|
||||
<th>Long.</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="waypoints">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<h4>Add a new waypoint</h4>
|
||||
<form id="add_waypoint_form">
|
||||
<div class="columns">
|
||||
<div class="column col-3 col-xs-8">
|
||||
<input class="form-input input-sm" type="text" id="add_waypoint_name" placeholder="Name">
|
||||
</div>
|
||||
<div class="column col-3 col-xs-8">
|
||||
<input class="form-input input-sm" value="0.0000" type="number" step="any" id="add_latitude" placeholder="Lat">
|
||||
</div>
|
||||
<div class="column col-3 col-xs-8">
|
||||
<input class="form-input input-sm" value="0.0000" type="number" step="any" id="add_longtitude" placeholder="Long">
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-3 col-xs-8">
|
||||
<button id="add_name_button" class="btn btn-primary btn-sm">Add Name Only</button>
|
||||
</div>
|
||||
<div class="column col-3 col-xs-8">
|
||||
<button id="add_waypoint_button" class="btn btn-primary btn-sm">Add Waypoint</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
<button id="Download" class="btn btn-error">Reload</button> <button id="Upload" class="btn btn-primary">Upload</button>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var waypoints = []
|
||||
|
||||
var $name = document.getElementById('add_waypoint_name')
|
||||
var $form = document.getElementById('add_waypoint_form')
|
||||
var $button = document.getElementById('add_waypoint_button')
|
||||
var $name_button = document.getElementById('add_name_button')
|
||||
var $latitude = document.getElementById('add_latitude')
|
||||
var $longtitude = document.getElementById('add_longtitude')
|
||||
var $list = document.getElementById('waypoints')
|
||||
|
||||
function compare(a, b){
|
||||
var x = a.name.toLowerCase();
|
||||
var y = b.name.toLowerCase();
|
||||
if (x=="none") {return -1};
|
||||
if (y=="none") {return 1};
|
||||
if (x < y) {return -1;}
|
||||
if (x > y) {return 1;}
|
||||
return 0;
|
||||
}
|
||||
|
||||
$button.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
var name = $name.value.trim()
|
||||
if(!name) return;
|
||||
var lat = parseFloat($latitude.value).toPrecision(5);
|
||||
var lon = parseFloat($longtitude.value).toPrecision(5);
|
||||
|
||||
waypoints.push({
|
||||
name, lat,lon,
|
||||
});
|
||||
|
||||
waypoints.sort(compare);
|
||||
|
||||
renderWaypoints()
|
||||
$name.value = ''
|
||||
$latitude.value = (0).toPrecision(5);
|
||||
$longtitude.value = (0).toPrecision(5);
|
||||
});
|
||||
|
||||
$name_button.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
var name = $name.value.trim()
|
||||
if(!name) return;
|
||||
|
||||
waypoints.push({
|
||||
name
|
||||
});
|
||||
waypoints.sort(compare);
|
||||
|
||||
renderWaypoints()
|
||||
$name.value = ''
|
||||
$latitude.value = 0.0000
|
||||
$longtitude.value = 0.0000
|
||||
});
|
||||
|
||||
|
||||
function removeWaypoint(index){
|
||||
$name.value = waypoints[index].name
|
||||
$latitude.value = waypoints[index].lat
|
||||
$longtitude.value = waypoints[index].lon
|
||||
waypoints = waypoints.filter((p,i) => i!==index)
|
||||
renderWaypoints()
|
||||
}
|
||||
|
||||
function renderWaypoints(){
|
||||
$list.innerHTML = ''
|
||||
waypoints.forEach((waypoint,index) => {
|
||||
var $waypoint = document.createElement('tr')
|
||||
if (index==0){
|
||||
$waypoint.innerHTML = `<td>${waypoint.name}</td>`
|
||||
} else if(waypoint.lat==undefined){
|
||||
$waypoint.innerHTML = `<td>${waypoint.name}</td><td>------</td><td>-----</td><td><button class="btn btn-action btn-primary" onclick="removeWaypoint(${index})"><i class="icon icon-edit"></i></button></td>`
|
||||
} else {
|
||||
$waypoint.innerHTML = `<td>${waypoint.name}</td><td>${waypoint.lat}</td><td>${waypoint.lon}</td><td><button class="btn btn-action btn-primary" onclick="removeWaypoint(${index})"><i class="icon icon-edit"></i></button></td>`
|
||||
}
|
||||
$list.appendChild($waypoint)
|
||||
})
|
||||
$name.focus()
|
||||
}
|
||||
|
||||
function downloadJSONfile(fileid, callback) {
|
||||
Puck.write(`\x10(function() {
|
||||
var pts = require("Storage").readJSON("${fileid}")||[{name:"NONE"}];
|
||||
Bluetooth.print(JSON.stringify(pts));
|
||||
})()\n`,contents=>{
|
||||
var storedpts = JSON.parse(contents);
|
||||
callback(storedpts);
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFile(fileid, contents) {
|
||||
Puck.write(`\x10(function() {
|
||||
require("Storage").write("${fileid}",'${contents}');
|
||||
Bluetooth.print("OK");
|
||||
})()\n`,ret=>{
|
||||
console.log("uploadFile",ret);
|
||||
});
|
||||
}
|
||||
|
||||
function gotStored(pts){
|
||||
waypoints = pts;
|
||||
renderWaypoints();
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
downloadJSONfile("waypoints.json", gotStored);
|
||||
}
|
||||
|
||||
document.getElementById("Download").addEventListener("click", function() {
|
||||
downloadJSONfile("waypoints.json", gotStored);
|
||||
});
|
||||
|
||||
document.getElementById("Upload").addEventListener("click", function() {
|
||||
var data = JSON.stringify(waypoints);
|
||||
uploadFile("waypoints.json",data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"name":"NONE"
|
||||
},
|
||||
{
|
||||
"name":"No10",
|
||||
"lat":51.5032,
|
||||
"lon":-0.1269
|
||||
},
|
||||
{
|
||||
"name":"Stone",
|
||||
"lat":51.1788,
|
||||
"lon":-1.8260
|
||||
},
|
||||
{ "name":"WP0" },
|
||||
{ "name":"WP1" },
|
||||
{ "name":"WP2" },
|
||||
{ "name":"WP3" },
|
||||
{ "name":"WP4" }
|
||||
]
|