Merge branch 'master' into master
40
apps.json
|
@ -120,7 +120,7 @@
|
|||
{ "id": "setting",
|
||||
"name": "Settings",
|
||||
"icon": "settings.png",
|
||||
"version":"0.16",
|
||||
"version":"0.17",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"tags": "tool,system",
|
||||
"storage": [
|
||||
|
@ -291,6 +291,18 @@
|
|||
{"name":"gpsrec.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "gpsnav",
|
||||
"name": "GPS Navigation",
|
||||
"icon": "icon.png",
|
||||
"version":"0.01",
|
||||
"description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"storage": [
|
||||
{"name":"gpsnav.app.js","url":"app.js"},
|
||||
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
|
||||
{"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "heart",
|
||||
"name": "Heart Rate Recorder",
|
||||
"icon": "app.png",
|
||||
|
@ -319,7 +331,7 @@
|
|||
{ "id": "files",
|
||||
"name": "App Manager",
|
||||
"icon": "files.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Show currently installed apps, free space, and allow their deletion from the watch",
|
||||
"tags": "tool,system,files",
|
||||
"storage": [
|
||||
|
@ -890,7 +902,7 @@
|
|||
{ "id": "wohrm",
|
||||
"name": "Workout HRM",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"readme": "README.md",
|
||||
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
|
||||
"tags": "hrm,workout",
|
||||
|
@ -1018,7 +1030,7 @@
|
|||
{ "id": "astrocalc",
|
||||
"name": "Astrocalc",
|
||||
"icon": "astrocalc.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
|
||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
||||
"allow_emulator":true,
|
||||
|
@ -1264,8 +1276,8 @@
|
|||
"name": "Calculator",
|
||||
"shortName":"Calculator",
|
||||
"icon": "calculator.png",
|
||||
"version":"0.01",
|
||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus. Push button1 and 3 to navigate up/down, tap right or left to navigate the sides, push button 2 to select.",
|
||||
"version":"0.02",
|
||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||
"tags": "app,tool",
|
||||
"storage": [
|
||||
{"name":"calculator.app.js","url":"app.js"},
|
||||
|
@ -1305,5 +1317,19 @@
|
|||
{"name":"hidcam.app.js","url":"app.js"},
|
||||
{"name":"hidcam.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rclock",
|
||||
"name": "Round clock with seconds, minutes and date",
|
||||
"shortName":"Round Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Designed round clock with ticks for minutes and seconds",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"rclock.app.js","url":"rclock.app.js"},
|
||||
{"name":"rclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Create astrocalc app
|
||||
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
/**
|
||||
* BangleJS ASTROCALC
|
||||
*
|
||||
* Inspired by: https://www.timeanddate.com
|
||||
*
|
||||
* Original Author: Paul Cockrell https://github.com/paulcockrell
|
||||
* Created: April 2020
|
||||
*
|
||||
* Calculate the Sun and Moon positions based on watch GPS and display graphically
|
||||
*/
|
||||
|
||||
const SunCalc = require("suncalc.js");
|
||||
const storage = require("Storage");
|
||||
const LAST_GPS_FILE = "astrocalc.gps.json";
|
||||
let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null);
|
||||
|
||||
function drawMoon(phase, x, y) {
|
||||
const moonImgFiles = [
|
||||
|
@ -296,22 +306,49 @@ function indexPageMenu(gps) {
|
|||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function getCenterStringX(str) {
|
||||
return (g.getWidth() - g.stringWidth(str)) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
|
||||
*/
|
||||
function drawGPSWaitPage() {
|
||||
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))
|
||||
|
||||
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="));
|
||||
const str1 = "Astrocalc v0.02";
|
||||
const str2 = "Locating GPS";
|
||||
const str3 = "Please wait...";
|
||||
|
||||
g.clear();
|
||||
g.drawImage(img, 100, 50);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString("Astrocalc v0.01", 80, 105);
|
||||
g.drawString("Locating GPS", 85, 140);
|
||||
g.drawString("Please wait...", 80, 155);
|
||||
g.drawString(str1, getCenterStringX(str1), 105);
|
||||
g.drawString(str2, getCenterStringX(str2), 140);
|
||||
g.drawString(str3, getCenterStringX(str3), 155);
|
||||
|
||||
if (lastGPS) {
|
||||
lastGPS = JSON.parse(lastGPS);
|
||||
lastGPS.time = new Date();
|
||||
|
||||
const str4 = "Press Button 3 to use last GPS";
|
||||
g.setColor("#d32e29");
|
||||
g.fillRect(0, 190, g.getWidth(), 215);
|
||||
g.setColor("#ffffff");
|
||||
g.drawString(str4, getCenterStringX(str4), 200);
|
||||
|
||||
setWatch(() => {
|
||||
clearWatch();
|
||||
Bangle.setGPSPower(0);
|
||||
m = indexPageMenu(lastGPS);
|
||||
}, BTN3, {repeat: false});
|
||||
}
|
||||
|
||||
g.flip();
|
||||
|
||||
const DEBUG = false;
|
||||
if (DEBUG) {
|
||||
clearWatch();
|
||||
|
||||
const gps = {
|
||||
"lat": 56.45783133333,
|
||||
"lon": -3.02188583333,
|
||||
|
@ -330,7 +367,10 @@ function drawGPSWaitPage() {
|
|||
|
||||
Bangle.on('GPS', (gps) => {
|
||||
if (gps.fix === 0) return;
|
||||
clearWatch();
|
||||
|
||||
if (isNaN(gps.course)) gps.course = 0;
|
||||
require("Storage").writeJSON(LAST_GPS_FILE, JSON.stringify(gps));
|
||||
Bangle.setGPSPower(0);
|
||||
Bangle.buzz();
|
||||
Bangle.setLCDPower(true);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: fix precision rounding issue + no reset when equals pressed
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Calculator
|
||||
|
||||
Basic calculator reminiscent of MacOs's one. Handy for small calculus.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/702227/79086938-bd3f4380-7d35-11ea-9988-a1a42916643f.png" height="384" width="384" />
|
||||
|
||||
## Features
|
||||
|
||||
- add / substract / divide / multiply
|
||||
- handles floats
|
||||
- basic memory button
|
||||
|
||||
## Controls
|
||||
|
||||
- UP: BTN1
|
||||
- DOWN: BTN3
|
||||
- LEFT: BTN4
|
||||
- RIGHT: BTN5
|
||||
- SELECT: BTN2
|
||||
|
||||
## Creator
|
||||
|
||||
<https://twitter.com/fredericrous>
|
|
@ -144,19 +144,57 @@ function drawKey(name, k, selected) {
|
|||
g.drawString(k.val || name, k.xy[0] + RIGHT_MARGIN + rMargin, k.xy[1] + BOTTOM_MARGIN + bMargin);
|
||||
}
|
||||
|
||||
function getIntWithPrecision(x) {
|
||||
var xStr = x.toString();
|
||||
var xRadix = xStr.indexOf('.');
|
||||
var xPrecision = xRadix === -1 ? 0 : xStr.length - xRadix - 1;
|
||||
return {
|
||||
num: Number(xStr.replace('.', '')),
|
||||
p: xPrecision
|
||||
};
|
||||
}
|
||||
|
||||
function multiply(x, y) {
|
||||
var xNum = getIntWithPrecision(x);
|
||||
var yNum = getIntWithPrecision(y);
|
||||
return xNum.num * yNum.num / Math.pow(10, xNum.p + yNum.p);
|
||||
}
|
||||
|
||||
function divide(x, y) {
|
||||
var xNum = getIntWithPrecision(x);
|
||||
var yNum = getIntWithPrecision(y);
|
||||
return xNum.num / yNum.num / Math.pow(10, xNum.p - yNum.p);
|
||||
}
|
||||
|
||||
function sum(x, y) {
|
||||
let xNum = getIntWithPrecision(x);
|
||||
let yNum = getIntWithPrecision(y);
|
||||
|
||||
let diffPrecision = Math.abs(xNum.p - yNum.p);
|
||||
if (diffPrecision > 0) {
|
||||
if (xNum.p > yNum.p) {
|
||||
yNum.num = yNum.num * Math.pow(10, diffPrecision);
|
||||
} else {
|
||||
xNum.num = xNum.num * Math.pow(10, diffPrecision);
|
||||
}
|
||||
}
|
||||
return (xNum.num + yNum.num) / Math.pow(10, Math.max(xNum.p, yNum.p));
|
||||
}
|
||||
|
||||
function subtract(x, y) {
|
||||
return sum(x, -y);
|
||||
}
|
||||
|
||||
function doMath(x, y, operator) {
|
||||
// might not be a number due to display of dot "." algo
|
||||
x = Number(x);
|
||||
y = Number(y);
|
||||
switch (operator) {
|
||||
case '/':
|
||||
return x / y;
|
||||
return divide(x, y);
|
||||
case '*':
|
||||
return x * y;
|
||||
return multiply(x, y);
|
||||
case '+':
|
||||
return x + y;
|
||||
return sum(x, y);
|
||||
case '-':
|
||||
return x - y;
|
||||
return subtract(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +242,7 @@ function displayOutput(num) {
|
|||
}
|
||||
|
||||
len = (num + '').length;
|
||||
if (numNumeric < 0) {
|
||||
if (numNumeric < 0 || (numNumeric === 0 && 1/numNumeric === -Infinity)) {
|
||||
// minus is not available in font 7x11Numeric7Seg, we use Vector
|
||||
g.setFont('Vector', 20);
|
||||
g.drawString('-', 220 - (len * 15), 10);
|
||||
|
@ -214,18 +252,33 @@ function displayOutput(num) {
|
|||
}
|
||||
g.drawString(num, 220 - (len * 15) + minusMarge, 10);
|
||||
}
|
||||
|
||||
var wasPressedEquals = false;
|
||||
var hasPressedNumber = false;
|
||||
function calculatorLogic(x) {
|
||||
if (hasPressedEquals) {
|
||||
currNumber = results;
|
||||
if (wasPressedEquals && hasPressedNumber !== false) {
|
||||
prevNumber = null;
|
||||
operator = null;
|
||||
results = null;
|
||||
isDecimal = null;
|
||||
displayOutput(currNumber);
|
||||
hasPressedEquals = false;
|
||||
currNumber = hasPressedNumber;
|
||||
wasPressedEquals = false;
|
||||
hasPressedNumber = false;
|
||||
return;
|
||||
}
|
||||
if (prevNumber != null && currNumber != null && operator != null) {
|
||||
if (hasPressedEquals) {
|
||||
if (hasPressedNumber) {
|
||||
prevNumber = null;
|
||||
hasPressedNumber = false;
|
||||
operator = null;
|
||||
} else {
|
||||
currNumber = null;
|
||||
prevNumber = results;
|
||||
}
|
||||
hasPressedEquals = false;
|
||||
wasPressedEquals = true;
|
||||
}
|
||||
|
||||
if (currNumber == null && operator != null && '/*-+'.indexOf(x) !== -1) {
|
||||
operator = x;
|
||||
displayOutput(prevNumber);
|
||||
} else if (prevNumber != null && currNumber != null && operator != null) {
|
||||
// we execute the calculus only when there was a previous number entered before and an operator
|
||||
results = doMath(prevNumber, currNumber, operator);
|
||||
operator = x;
|
||||
|
@ -255,8 +308,10 @@ function buttonPress(val) {
|
|||
operator = null;
|
||||
} else {
|
||||
keys.R.val = 'AC';
|
||||
drawKey('R', keys.R);
|
||||
drawKey('R', keys.R, true);
|
||||
}
|
||||
wasPressedEquals = false;
|
||||
hasPressedNumber = false;
|
||||
displayOutput(0);
|
||||
break;
|
||||
case '%':
|
||||
|
@ -265,11 +320,12 @@ function buttonPress(val) {
|
|||
} else if (currNumber != null) {
|
||||
displayOutput(currNumber /= 100);
|
||||
}
|
||||
hasPressedNumber = false;
|
||||
break;
|
||||
case 'N':
|
||||
if (results != null) {
|
||||
displayOutput(results *= -1);
|
||||
} else if (currNumber != null) {
|
||||
} else {
|
||||
displayOutput(currNumber *= -1);
|
||||
}
|
||||
break;
|
||||
|
@ -278,6 +334,7 @@ function buttonPress(val) {
|
|||
case '-':
|
||||
case '+':
|
||||
calculatorLogic(val);
|
||||
hasPressedNumber = false;
|
||||
break;
|
||||
case '.':
|
||||
keys.R.val = 'C';
|
||||
|
@ -290,18 +347,24 @@ function buttonPress(val) {
|
|||
results = doMath(prevNumber, currNumber, operator);
|
||||
prevNumber = results;
|
||||
displayOutput(results);
|
||||
hasPressedEquals = true;
|
||||
hasPressedEquals = 1;
|
||||
}
|
||||
hasPressedNumber = false;
|
||||
break;
|
||||
default:
|
||||
keys.R.val = 'C';
|
||||
drawKey('R', keys.R);
|
||||
const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity);
|
||||
if (isDecimal) {
|
||||
currNumber = currNumber == null ? 0 + '.' + val : currNumber + '.' + val;
|
||||
currNumber = currNumber == null || hasPressedEquals === 1 ? 0 + '.' + val : currNumber + '.' + val;
|
||||
isDecimal = false;
|
||||
} else {
|
||||
currNumber = currNumber == null ? val : currNumber + val;
|
||||
currNumber = currNumber == null || hasPressedEquals === 1 ? val : (is0Negative ? '-' + val : currNumber + val);
|
||||
}
|
||||
if (hasPressedEquals === 1) {
|
||||
hasPressedEquals = 2;
|
||||
}
|
||||
hasPressedNumber = currNumber;
|
||||
displayOutput(currNumber);
|
||||
break;
|
||||
}
|
||||
|
@ -315,38 +378,15 @@ for (var k in keys) {
|
|||
g.setFont('7x11Numeric7Seg', 2.8);
|
||||
g.drawString('0', 205, 10);
|
||||
|
||||
|
||||
setWatch(function() {
|
||||
drawKey(selected, keys[selected]);
|
||||
// key 0 is 2 keys wide, go up to 1 if it was previously selected
|
||||
if (selected == '0' && prevSelected === '1') {
|
||||
prevSelected = selected;
|
||||
selected = '1';
|
||||
} else {
|
||||
prevSelected = selected;
|
||||
selected = keys[selected].trbl[0];
|
||||
}
|
||||
drawKey(selected, keys[selected], true);
|
||||
}, BTN1, {repeat: true, debounce: 100});
|
||||
|
||||
setWatch(function() {
|
||||
function moveDirection(d) {
|
||||
drawKey(selected, keys[selected]);
|
||||
prevSelected = selected;
|
||||
selected = keys[selected].trbl[2];
|
||||
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : keys[selected].trbl[d];
|
||||
drawKey(selected, keys[selected], true);
|
||||
}, BTN3, {repeat: true, debounce: 100});
|
||||
}
|
||||
|
||||
Bangle.on('touch', function(direction) {
|
||||
drawKey(selected, keys[selected]);
|
||||
prevSelected = selected;
|
||||
if (direction == 1) {
|
||||
selected = keys[selected].trbl[3];
|
||||
} else if (direction == 2) {
|
||||
selected = keys[selected].trbl[1];
|
||||
}
|
||||
drawKey(selected, keys[selected], true);
|
||||
});
|
||||
|
||||
setWatch(function() {
|
||||
buttonPress(selected);
|
||||
}, BTN2, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100});
|
||||
setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100});
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Calculator tests</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/7.1.0/mocha.min.css">
|
||||
<style>
|
||||
#header {
|
||||
margin: 60px 50px;
|
||||
font: 1em "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-weight: 200;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="header"></div>
|
||||
<div id="mocha"></div>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/7.1.0/mocha.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.2.0/chai.min.js"></script>
|
||||
<script>
|
||||
// mocks
|
||||
const _ = () => {}
|
||||
setWatch = _
|
||||
drawKey = _
|
||||
currentOutput = 0;
|
||||
g = {
|
||||
setFont: _,
|
||||
drawString: n => currentOutput = n,
|
||||
setColor: _,
|
||||
fillRect: _,
|
||||
clear: _
|
||||
}
|
||||
_graphics = function () {
|
||||
this.setFontCustom = _
|
||||
}
|
||||
Graphics = _graphics
|
||||
BTN1 = 1
|
||||
BTN2 = 2
|
||||
BTN3 = 3
|
||||
BTN4 = 4
|
||||
BTN5 = 5
|
||||
Bangle = {
|
||||
on: _
|
||||
}
|
||||
Terminal = { println: console.log }
|
||||
</script>
|
||||
<script src="app.js"></script>
|
||||
<script>
|
||||
header`
|
||||
// Unit tests for the BangleJS's Calculator app
|
||||
`
|
||||
|
||||
mocha.setup({ui:'bdd'})
|
||||
chai.should()
|
||||
var expect = chai.expect
|
||||
|
||||
const sequencePress = x => x.split('').forEach(y => buttonPress(y))
|
||||
const sequenceReset = _ => [...Array(2)].forEach(x => buttonPress('R'))
|
||||
|
||||
describe("Simple arithmetic", function(){
|
||||
it("multiplication", function(){
|
||||
multiply(1.4,2.4).should.equal(3.36)
|
||||
})
|
||||
|
||||
it("division", function(){
|
||||
divide(4.4,2).should.equal(2.2)
|
||||
})
|
||||
|
||||
it("sum", function(){
|
||||
sum(4.1,2).should.equal(6.1)
|
||||
})
|
||||
|
||||
it("subtract", function(){
|
||||
subtract(4.1,2).should.equal(2.1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Simple Operation with Reset", function(){
|
||||
|
||||
it("Simple addition", function(){
|
||||
sequencePress("50+3=")
|
||||
currentOutput.should.equal(53)
|
||||
})
|
||||
|
||||
it("Reset output 'C' then 'AC'", function(){
|
||||
sequenceReset()
|
||||
currentOutput.should.equal(0)
|
||||
})
|
||||
|
||||
it("Complex calculus", function(){
|
||||
sequenceReset()
|
||||
sequencePress("3*3+3-2/2=")
|
||||
currentOutput.should.equal(5)
|
||||
})
|
||||
|
||||
it("Change operator", function(){
|
||||
sequenceReset()
|
||||
sequencePress("3*+/3=")
|
||||
currentOutput.should.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Operations on Double-s", function(){
|
||||
|
||||
it("Simple addition", function(){
|
||||
sequenceReset()
|
||||
sequencePress("1.3+1.7=")
|
||||
currentOutput.should.equal(3)
|
||||
})
|
||||
|
||||
it("some calculation", function(){
|
||||
sequenceReset()
|
||||
sequencePress("1.3+1.7*2.22/2=")
|
||||
currentOutput.should.equal(3.33)
|
||||
})
|
||||
|
||||
it("No corrupt opposed to what javascript Number would", function(){
|
||||
sequenceReset()
|
||||
sequencePress("1.3+1.7*2.2/2=")
|
||||
currentOutput.should.equal(3.3)
|
||||
})
|
||||
|
||||
it("Complex calcul", function(){
|
||||
sequenceReset()
|
||||
sequencePress("48/.2/")
|
||||
currentOutput.should.equal(240)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Negative Operations", function(){
|
||||
|
||||
it("Negative on first number", function(){
|
||||
sequenceReset()
|
||||
sequencePress("50N+3=")
|
||||
currentOutput.should.equal(-47)
|
||||
})
|
||||
|
||||
it("Substract negative", function(){
|
||||
sequenceReset()
|
||||
sequencePress("50N-3=")
|
||||
currentOutput.should.equal(-53)
|
||||
})
|
||||
|
||||
it("Negative before number is typed", function(){
|
||||
sequenceReset()
|
||||
sequencePress("N50-3=")
|
||||
currentOutput.should.equal(-53)
|
||||
})
|
||||
|
||||
it("Negative addition on second number", function(){
|
||||
sequenceReset()
|
||||
sequencePress("50-N33=")
|
||||
currentOutput.should.equal(83)
|
||||
})
|
||||
|
||||
it("Negative zero", function(){
|
||||
sequenceReset()
|
||||
sequencePress("N")
|
||||
currentOutput.should.equal(-0)
|
||||
sequenceReset()
|
||||
sequencePress("0N")
|
||||
currentOutput.should.equal(-0)
|
||||
sequenceReset()
|
||||
sequencePress("N0")
|
||||
currentOutput.should.equal('-0')
|
||||
sequenceReset()
|
||||
sequencePress("0N")
|
||||
currentOutput.should.equal(-0)
|
||||
sequencePress("N0")
|
||||
currentOutput.should.equal('0')
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
describe("Zero division", function(){
|
||||
|
||||
it("Divide 0 by 0", function(){
|
||||
sequenceReset()
|
||||
sequencePress("0/0=")
|
||||
currentOutput.should.equal('NOT A NUMBER')
|
||||
})
|
||||
|
||||
it("Divde N by 0", function(){
|
||||
sequenceReset()
|
||||
sequencePress("1/0=")
|
||||
currentOutput.should.equal('INFINITY')
|
||||
})
|
||||
|
||||
it("Divde -N by 0", function(){
|
||||
sequenceReset()
|
||||
sequencePress("N1/0=")
|
||||
currentOutput.should.equal('-INFINITY')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Press equals '='", function(){
|
||||
|
||||
it("should display result when new operation button is pressed", function(){
|
||||
sequenceReset()
|
||||
sequencePress("5+6+")
|
||||
currentOutput.should.equal(11)
|
||||
sequenceReset()
|
||||
sequencePress("5-6*4/2/")
|
||||
currentOutput.should.equal(-2)
|
||||
})
|
||||
|
||||
it("New operation after '='", function(){
|
||||
sequenceReset()
|
||||
sequencePress("5+4=5")
|
||||
currentOutput.should.equal('5')
|
||||
sequenceReset()
|
||||
sequencePress("N5+4*3-3/-1=5")
|
||||
currentOutput.should.equal('5')
|
||||
})
|
||||
|
||||
it("Double '=' repeats last operation", function(){
|
||||
sequenceReset()
|
||||
sequencePress("2+2==")
|
||||
currentOutput.should.equal(6)
|
||||
})
|
||||
|
||||
it("New operation applied to calculated result", function(){
|
||||
sequenceReset()
|
||||
sequencePress("9*9=*9=")
|
||||
currentOutput.should.equal(729)
|
||||
})
|
||||
|
||||
it("Turn result negative, do addition", function(){
|
||||
sequenceReset()
|
||||
sequencePress("9*9=N+1=")
|
||||
currentOutput.should.equal(-80)
|
||||
})
|
||||
|
||||
it("New operation after '=' dissociated from previous one", function(){
|
||||
sequenceReset()
|
||||
sequencePress("9*9=9*")
|
||||
currentOutput.should.equal('9')
|
||||
sequenceReset()
|
||||
sequencePress("9*9=99+1=*2=")
|
||||
currentOutput.should.equal(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Memory", function(){
|
||||
|
||||
it("Reset 1st number with 'C'", function(){
|
||||
sequenceReset()
|
||||
sequencePress("50R3+6=")
|
||||
currentOutput.should.equal(9)
|
||||
})
|
||||
|
||||
it("Reset 2nd number with 'C'", function(){
|
||||
sequenceReset()
|
||||
sequencePress("50+3R+6=")
|
||||
currentOutput.should.equal(56)
|
||||
})
|
||||
|
||||
it("Complex calcul", function(){
|
||||
sequenceReset()
|
||||
sequencePress("/3*3+3R-+2/2=")
|
||||
currentOutput.should.equal(5.5)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
mocha.run()
|
||||
function header(str) { document.getElementById('header').innerHTML = str[0].replace(/\n/, '').replace(/\n/g, '<br>') }
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1 +1,2 @@
|
|||
0.02: Fix deletion of apps - now use files list in app.info (fix #262)
|
||||
0.03: Add support for data files
|
||||
|
|
|
@ -30,29 +30,80 @@ function showMainMenu() {
|
|||
return E.showMenu(mainmenu);
|
||||
}
|
||||
|
||||
function eraseApp(app) {
|
||||
E.showMessage('Erasing\n' + app.name + '...');
|
||||
function isGlob(f) {return /[?*]/.test(f)}
|
||||
function globToRegex(pattern) {
|
||||
const ESCAPE = '.*+-?^${}()|[]\\';
|
||||
const regex = pattern.replace(/./g, c => {
|
||||
switch (c) {
|
||||
case '?': return '.';
|
||||
case '*': return '.*';
|
||||
default: return ESCAPE.includes(c) ? ('\\' + c) : c;
|
||||
}
|
||||
});
|
||||
return new RegExp('^'+regex+'$');
|
||||
}
|
||||
|
||||
function eraseFiles(app) {
|
||||
app.files.split(",").forEach(f=>storage.erase(f));
|
||||
}
|
||||
function eraseData(app) {
|
||||
if(!app.data) return;
|
||||
const d=app.data.split(';'),
|
||||
files=d[0].split(','),
|
||||
sFiles=(d[1]||'').split(',');
|
||||
let erase = f=>storage.erase(f);
|
||||
files.forEach(f=>{
|
||||
if (!isGlob(f)) erase(f);
|
||||
else storage.list(globToRegex(f)).forEach(erase);
|
||||
})
|
||||
erase = sf=>storage.open(sf,'r').erase();
|
||||
sFiles.forEach(sf=>{
|
||||
if (!isGlob(sf)) erase(sf);
|
||||
else storage.list(globToRegex(sf+'\u0001'))
|
||||
.forEach(fs=>erase(fs.substring(0,fs.length-1)));
|
||||
})
|
||||
}
|
||||
function eraseApp(app, files,data) {
|
||||
E.showMessage('Erasing\n' + app.name + '...');
|
||||
if (files) eraseFiles(app)
|
||||
if (data) eraseData(app)
|
||||
}
|
||||
function eraseOne(app, files,data){
|
||||
E.showPrompt('Erase\n'+app.name+'?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
eraseApp(app, files,data)
|
||||
showApps();
|
||||
} else {
|
||||
showAppMenu(app)
|
||||
}
|
||||
})
|
||||
}
|
||||
function eraseAll(apps, files,data) {
|
||||
E.showPrompt('Erase all?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
for(var n = 0; n<apps.length; n++)
|
||||
eraseApp(apps[n], files,data);
|
||||
}
|
||||
showApps();
|
||||
})
|
||||
}
|
||||
|
||||
function showAppMenu(app) {
|
||||
const appmenu = {
|
||||
let appmenu = {
|
||||
'': {
|
||||
'title': app.name,
|
||||
},
|
||||
'< Back': () => m = showApps(),
|
||||
'Erase': () => {
|
||||
E.showPrompt('Erase\n' + app.name + '?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
eraseApp(app);
|
||||
m = showApps();
|
||||
} else {
|
||||
m = showAppMenu(app)
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
if (app.data) {
|
||||
appmenu['Erase Completely'] = () => eraseOne(app, true, true)
|
||||
appmenu['Erase App,Keep Data'] = () => eraseOne(app,true, false)
|
||||
appmenu['Only Erase Data'] = () => eraseOne(app,false, true)
|
||||
} else {
|
||||
appmenu['Erase'] = () => eraseOne(app,true, false)
|
||||
}
|
||||
return E.showMenu(appmenu);
|
||||
}
|
||||
|
||||
|
@ -78,13 +129,12 @@ function showApps() {
|
|||
return menu;
|
||||
}, appsmenu);
|
||||
appsmenu['Erase All'] = () => {
|
||||
E.showPrompt('Erase all?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
for (var n = 0; n < list.length; n++)
|
||||
eraseApp(list[n]);
|
||||
}
|
||||
m = showApps();
|
||||
E.showMenu({
|
||||
'': {'title': 'Erase All'},
|
||||
'Erase Everything': () => eraseAll(list, true, true),
|
||||
'Erase Apps,Keep Data': () => eraseAll(list, true, false),
|
||||
'Only Erase Data': () => eraseAll(list, false, true),
|
||||
'< Back': () => showApps(),
|
||||
});
|
||||
};
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,66 @@
|
|||
## gpsnav - navigate to waypoints
|
||||
|
||||
The app is aimed at small boat navigation although it can also be used to mark the location of your car, bicycle etc and then get directions back to it. Please note that it would be foolish in the extreme to rely on this as your only boat navigation aid!
|
||||
|
||||
The app displays direction of travel (course), speed, direction to waypoint (bearing) and distance to waypoint. The screen shot below is before the app has got a GPS fix.
|
||||
|
||||

|
||||
|
||||
The large digits are the course and speed. The top of the display is a linear compass which displays the direction of travel when a fix is received and you are moving. The blue text is the name of the current waypoint. NONE means that there is no waypoint set and so bearing and distance will remain at 0. To select a waypoint, press BTN2 (middle) and wait for the blue text to turn white. Then use BTN1 and BTN3 to select a waypoint. The waypoint choice is fixed by pressing BTN2 again. In the screen shot below a waypoint giving the location of Stone Henge has been selected.
|
||||
|
||||

|
||||
|
||||
The display shows that Stone Henge is 108.75Km from the location where I made the screenshot and the direction is 255 degrees - approximately west. The display shows that I am currently moving approximately north - albeit slowly!. The position of the blue circle indicates that I need to turn left to get on course to Stone Henge. When the circle and red triangle line up you are on course and course will equal bearing.
|
||||
|
||||
### 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 WP1 has currently no GPS location associated with it. To mark the location, press BTN2.
|
||||
|
||||

|
||||
|
||||
The app indicates that WP2 is now marked by adding the prefix @ to it's name. The distance should be small as shown in the screen shot as you have just marked your current location.
|
||||
|
||||
### 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:
|
||||
|
||||
~~~
|
||||
[
|
||||
{
|
||||
"mark":0,
|
||||
"name":"NONE"
|
||||
},
|
||||
{
|
||||
"mark":1,
|
||||
"name":"No10",
|
||||
"lat":51.5032,
|
||||
"lon":-0.1269
|
||||
},
|
||||
{
|
||||
"mark":1,
|
||||
"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.
|
||||
|
||||
I plan to release an accompanying watch app to edit waypoint files in the near future and a way to download your own waypoint file using the app loader.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AFmACysDC9+IC6szC/8AgUgLwYXBPAgLDAA8kC5MyC5cyogXHmYiDURMkDAMzC4JgBmcyoAXMGANCC4YDBkgXMHwVEC4hQDC5kyF4kjJ4QAMOgMjC4eCohGNMARbCC4ODkilLAAQSBCYJ3EmYVLhAWCCgQaCAAUwCpowCFwYADIRAYHC4wZFRQIAGnAhJXgwAFxAYHwC9JFwiQCFhIZISAQwDX5sCoQTCDYUjUpAAFglElAXDmS9JAAtEoUyC4ckkbvMC4QQBC4YeBC5sEB4IXEkgfBJBkEH4QXCCYMkoQXMHwcIC4ZQCUpYMDC4oiBC5YEDC40AkCRNAAIXBCJ4X2URgAJhAXvCyoA/ACoA="))
|
|
@ -0,0 +1,224 @@
|
|||
const Yoff = 40;
|
||||
var pal2color = new Uint16Array([0x0000,0xffff,0x07ff,0xC618],0,2);
|
||||
var buf = Graphics.createArrayBuffer(240,50,2,{msb:true});
|
||||
|
||||
function flip(b,y) {
|
||||
g.drawImage({width:240,height:50,bpp:2,buffer:b.buffer, palette:pal2color},0,y);
|
||||
b.clear();
|
||||
}
|
||||
|
||||
var brg=0;
|
||||
var wpindex=0;
|
||||
const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||
|
||||
function drawCompass(course) {
|
||||
buf.setColor(1);
|
||||
buf.setFont("Vector",16);
|
||||
var start = course-90;
|
||||
if (start<0) start+=360;
|
||||
buf.fillRect(28,45,212,49);
|
||||
var xpos = 30;
|
||||
var frag = 15 - start%15;
|
||||
if (frag<15) xpos+=frag; else frag = 0;
|
||||
for (var i=frag;i<=180-frag;i+=15){
|
||||
var res = start + i;
|
||||
if (res%90==0) {
|
||||
buf.drawString(labels[Math.floor(res/45)%8],xpos-8,0);
|
||||
buf.fillRect(xpos-2,25,xpos+2,45);
|
||||
} else if (res%45==0) {
|
||||
buf.drawString(labels[Math.floor(res/45)%8],xpos-12,0);
|
||||
buf.fillRect(xpos-2,30,xpos+2,45);
|
||||
} else if (res%15==0) {
|
||||
buf.fillRect(xpos,35,xpos+1,45);
|
||||
}
|
||||
xpos+=15;
|
||||
}
|
||||
if (wpindex!=0) {
|
||||
var bpos = brg - course;
|
||||
if (bpos>180) bpos -=360;
|
||||
if (bpos<-180) bpos +=360;
|
||||
bpos+=120;
|
||||
if (bpos<30) bpos = 14;
|
||||
if (bpos>210) bpos = 226;
|
||||
buf.setColor(2);
|
||||
buf.fillCircle(bpos,40,8);
|
||||
}
|
||||
flip(buf,Yoff);
|
||||
}
|
||||
|
||||
//displayed heading
|
||||
var heading = 0;
|
||||
function newHeading(m,h){
|
||||
var s = Math.abs(m - h);
|
||||
var delta = 1;
|
||||
if (s<2) return h;
|
||||
if (m > h){
|
||||
if (s >= 180) { delta = -1; s = 360 - s;}
|
||||
} else if (m <= h){
|
||||
if (s < 180) delta = -1;
|
||||
else s = 360 -s;
|
||||
}
|
||||
delta = delta * (1 + Math.round(s/15));
|
||||
heading+=delta;
|
||||
if (heading<0) heading += 360;
|
||||
if (heading>360) heading -= 360;
|
||||
return heading;
|
||||
}
|
||||
|
||||
var course =0;
|
||||
var speed = 0;
|
||||
var satellites = 0;
|
||||
var wp;
|
||||
var dist=0;
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
|
||||
function degrees(a) {
|
||||
var d = a*180/Math.PI;
|
||||
return (d+360)%360;
|
||||
}
|
||||
|
||||
function bearing(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)));
|
||||
}
|
||||
|
||||
function distance(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);
|
||||
}
|
||||
|
||||
var selected = false;
|
||||
|
||||
function drawN(){
|
||||
buf.setColor(1);
|
||||
buf.setFont("6x8",2);
|
||||
buf.drawString("o",100,0);
|
||||
buf.setFont("6x8",1);
|
||||
buf.drawString("kph",220,40);
|
||||
buf.setFont("Vector",40);
|
||||
var cs = course.toString();
|
||||
cs = course<10?"00"+cs : course<100 ?"0"+cs : cs;
|
||||
buf.drawString(cs,10,0);
|
||||
var txt = (speed<10) ? speed.toFixed(1) : Math.round(speed);
|
||||
buf.drawString(txt,140,4);
|
||||
flip(buf,Yoff+70);
|
||||
buf.setColor(1);
|
||||
buf.setFont("Vector",20);
|
||||
var bs = brg.toString();
|
||||
bs = brg<10?"00"+bs : brg<100 ?"0"+bs : bs;
|
||||
buf.setColor(3);
|
||||
buf.drawString("Brg: ",0,0);
|
||||
buf.drawString("Dist: ",0,30);
|
||||
buf.setColor(selected?1:2);
|
||||
buf.drawString(wp.name,140,0);
|
||||
buf.setColor(1);
|
||||
buf.drawString(bs,60,0);
|
||||
if (dist<1000)
|
||||
buf.drawString(dist.toString()+"m",60,30);
|
||||
else
|
||||
buf.drawString((dist/1000).toFixed(2)+"Km",60,30);
|
||||
flip(buf,Yoff+130);
|
||||
g.setFont("6x8",1);
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(10,230,60,239);
|
||||
g.setColor(1,1,1);
|
||||
g.drawString("Sats " + satellites.toString(),10,230);
|
||||
}
|
||||
|
||||
var savedfix;
|
||||
|
||||
function onGPS(fix) {
|
||||
savedfix = fix;
|
||||
if (fix!==undefined){
|
||||
course = isNaN(fix.course) ? course : Math.round(fix.course);
|
||||
speed = isNaN(fix.speed) ? speed : fix.speed;
|
||||
satellites = fix.satellites;
|
||||
}
|
||||
if (Bangle.isLCDOn()) {
|
||||
if (fix!==undefined && fix.fix==1){
|
||||
dist = distance(fix,wp);
|
||||
if (isNaN(dist)) dist = 0;
|
||||
brg = bearing(fix,wp);
|
||||
if (isNaN(brg)) brg = 0;
|
||||
}
|
||||
drawN();
|
||||
}
|
||||
}
|
||||
|
||||
var intervalRef;
|
||||
|
||||
function clearTimers() {
|
||||
if(intervalRef) {clearInterval(intervalRef);}
|
||||
}
|
||||
|
||||
function startTimers() {
|
||||
intervalRefSec = setInterval(function() {
|
||||
newHeading(course,heading);
|
||||
if (course!=heading) drawCompass(heading);
|
||||
},200);
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on) {
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
startTimers();
|
||||
drawAll();
|
||||
}else {
|
||||
clearTimers();
|
||||
}
|
||||
});
|
||||
|
||||
function drawAll(){
|
||||
g.setColor(1,0.5,0.5);
|
||||
g.fillPoly([120,Yoff+50,110,Yoff+70,130,Yoff+70]);
|
||||
g.setColor(1,1,1);
|
||||
drawN();
|
||||
drawCompass(heading);
|
||||
}
|
||||
|
||||
var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
|
||||
wp=waypoints[0];
|
||||
|
||||
function nextwp(inc){
|
||||
if (!selected) return;
|
||||
wpindex+=inc;
|
||||
if (wpindex>=waypoints.length) wpindex=0;
|
||||
if (wpindex<0) wpindex = waypoints.length-1;
|
||||
wp = waypoints[wpindex];
|
||||
drawN();
|
||||
}
|
||||
|
||||
function doselect(){
|
||||
if (selected && waypoints[wpindex].mark===undefined && savedfix.fix) {
|
||||
waypoints[wpindex] ={mark:1, name:"@"+wp.name, lat:savedfix.lat, lon:savedfix.lon};
|
||||
wp = waypoints[wpindex];
|
||||
require("Storage").writeJSON("waypoints.json", waypoints);
|
||||
}
|
||||
selected=!selected;
|
||||
drawN();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
Bangle.setLCDBrightness(1);
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// load widgets can turn off GPS
|
||||
Bangle.setGPSPower(1);
|
||||
drawAll();
|
||||
startTimers();
|
||||
Bangle.on('GPS', onGPS);
|
||||
// Toggle selected
|
||||
setWatch(nextwp.bind(null,-1), BTN1, {repeat:true,edge:"falling"});
|
||||
setWatch(doselect, BTN2, {repeat:true,edge:"falling"});
|
||||
setWatch(nextwp.bind(null,1), BTN3, {repeat:true,edge:"falling"});
|
||||
|
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 66 KiB |
|
@ -0,0 +1,23 @@
|
|||
[
|
||||
{
|
||||
"mark":0,
|
||||
"name":"NONE"
|
||||
},
|
||||
{
|
||||
"mark":1,
|
||||
"name":"No10",
|
||||
"lat":51.5032,
|
||||
"lon":-0.1269
|
||||
},
|
||||
{
|
||||
"mark":1,
|
||||
"name":"Stone",
|
||||
"lat":51.1788,
|
||||
"lon":-1.8260
|
||||
},
|
||||
{ "name":"WP0" },
|
||||
{ "name":"WP1" },
|
||||
{ "name":"WP2" },
|
||||
{ "name":"WP3" },
|
||||
{ "name":"WP4" }
|
||||
]
|
|
@ -0,0 +1 @@
|
|||
0.01: First published version of app
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+If4A/AH4AXqwBEF9VWlYxEAoIAllYuGGwIxnSxAwkR4InCFIbGmF4TCCGAYEBSgK/kXYQxFetDzCLYhgjeBQ3EGE69ESwgwoZYiSpMAgCEGFRfqYQrDblRfRMDdU0QFDp2iAAN4HIowBLYYwXvHG4w0D4wtB0QDBGApcCGYqLSEgIvEAwIqCHQNUYArdaKwIlBRwYpDGgIvEL4QxBYDIvEAAhpBpxZaF6BeBvAIFL4qVXF44uIF4pffFxI0GF7ouKlbrClaNXF4wEB0VUAAUqF4qTEF7heBAAhjDLQS+CL7MqqgECLgZfNGDIAORIaNZACCOBLIbvaFxy/ERtDpCAgYCDF1DsnFgS2ERk4sBF4hhBMYgAiE4bsDF0zAKMFABBXkxZEX4QunWwS4CFtCMEFsN4AAOiAYcAqgGB0UqgGip2iqgvcD4IuCAYgwBAoINBAIN4F7gkBAAplCGgVUNQhfcqlOAAIDCgEqAQIBBAoKXBAQIAL"))
|
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
var minutes;
|
||||
var seconds;
|
||||
var hours;
|
||||
var date;
|
||||
var first = true;
|
||||
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
};
|
||||
|
||||
// Ssettings
|
||||
const settings = {
|
||||
time: {
|
||||
color: '#f0af00',
|
||||
shadow: '#CF7500',
|
||||
font: 'Vector',
|
||||
size: 60,
|
||||
middle: screen.middle - 30,
|
||||
center: screen.center,
|
||||
},
|
||||
date: {
|
||||
color: '#f0af00',
|
||||
shadow: '#CF7500',
|
||||
font: 'Vector',
|
||||
size: 15,
|
||||
middle: screen.height - 20, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
circle: {
|
||||
colormin: '#eeeeee',
|
||||
colorsec: '#bbbbbb',
|
||||
width: 10,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
height: screen.height
|
||||
}
|
||||
};
|
||||
|
||||
const dateStr = function (date) {
|
||||
day = date.getDate();
|
||||
month = date.getMonth();
|
||||
year = date.getFullYear();
|
||||
if (day < 10) {
|
||||
day = "0" + day;
|
||||
}
|
||||
if (month < 10) {
|
||||
month = "0" + month;
|
||||
}
|
||||
|
||||
return year + "-" + month + "-" + day;
|
||||
};
|
||||
|
||||
const getArcXY = function (centerX, centerY, radius, angle) {
|
||||
var s, r = [];
|
||||
s = 2 * Math.PI * angle / 360;
|
||||
r.push(centerX + Math.round(Math.cos(s) * radius));
|
||||
r.push(centerY + Math.round(Math.sin(s) * radius));
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
const drawMinArc = function (sections, color) {
|
||||
g.setColor(color);
|
||||
rad = (settings.circle.height / 2) - 20;
|
||||
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
|
||||
g.setColor('#333333');
|
||||
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width-4)
|
||||
};
|
||||
|
||||
const drawSecArc = function (sections, color) {
|
||||
g.setColor(color);
|
||||
rad = (settings.circle.height / 2) - 40;
|
||||
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
|
||||
g.setColor('#333333');
|
||||
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width-4)
|
||||
};
|
||||
|
||||
const drawClock = function () {
|
||||
|
||||
currentTime = new Date();
|
||||
|
||||
//Set to initial time when started
|
||||
if (first == true) {
|
||||
minutes = currentTime.getMinutes();
|
||||
seconds = currentTime.getSeconds();
|
||||
for (count = 0; count <= minutes; count++) {
|
||||
drawMinArc(count, settings.circle.colormin);
|
||||
}
|
||||
|
||||
for (count = 0; count <= seconds; count++) {
|
||||
drawSecArc(count, settings.circle.colorsec);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
|
||||
// Reset seconds
|
||||
if (seconds == 59) {
|
||||
g.setColor('#000000');
|
||||
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 40);
|
||||
}
|
||||
// Reset minutes
|
||||
if (minutes == 59 && seconds == 59) {
|
||||
g.setColor('#000000');
|
||||
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 20);
|
||||
}
|
||||
|
||||
//Get date as a string
|
||||
date = dateStr(currentTime);
|
||||
|
||||
// Update minutes when needed
|
||||
if (minutes != currentTime.getMinutes()) {
|
||||
minutes = currentTime.getMinutes();
|
||||
drawMinArc(minutes, settings.circle.colormin);
|
||||
}
|
||||
|
||||
//Update seconds when needed
|
||||
if (seconds != currentTime.getSeconds()) {
|
||||
seconds = currentTime.getSeconds();
|
||||
drawSecArc(seconds, settings.circle.colorsec);
|
||||
}
|
||||
|
||||
//Write the time as configured in the settings
|
||||
hours = currentTime.getHours();
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size);
|
||||
g.drawString(hours, settings.time.center, settings.time.middle);
|
||||
|
||||
//Write the date as configured in the settings
|
||||
g.setColor(settings.date.color);
|
||||
g.setFont(settings.date.font, settings.date.size);
|
||||
g.drawString(date, settings.date.center, settings.date.middle);
|
||||
};
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (on) drawClock();
|
||||
});
|
||||
|
||||
// clean app screen
|
||||
g.clear();
|
||||
g.setFontAlign( 0, 0, 0);
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 30 sec
|
||||
setInterval(drawClock, 1E3);
|
||||
|
||||
// draw now
|
||||
drawClock();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
||||
}
|
|
@ -18,3 +18,4 @@
|
|||
0.14: Reduce memory usage when running app settings page
|
||||
0.15: Reduce memory usage when running default clock chooser (#294)
|
||||
0.16: Reduce memory usage further when running app settings page
|
||||
0.17: Remove need for "settings" in appid.info
|
||||
|
|
|
@ -416,10 +416,19 @@ function showAppSettingsMenu() {
|
|||
'': { 'title': 'App Settings' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
}
|
||||
const apps = storage.list(/\.info$/)
|
||||
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.settings)?{sortorder:a.sortorder,name:a.name,settings:a.settings}:undefined})
|
||||
.filter(app => app) // filter out any undefined apps
|
||||
.sort((a, b) => a.sortorder - b.sortorder)
|
||||
const apps = storage.list(/\.settings\.js$/)
|
||||
.map(s => s.substr(0, s.length-12))
|
||||
.map(id => {
|
||||
const a=storage.readJSON(id+'.info',1);
|
||||
return {id:id,name:a.name,sortorder:a.sortorder};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const n = (0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
})
|
||||
if (apps.length === 0) {
|
||||
appmenu['No app has settings'] = () => { };
|
||||
}
|
||||
|
@ -433,10 +442,7 @@ function showAppSettings(app) {
|
|||
E.showMessage(`${app.name}:\n${msg}!\n\nBTN1 to go back`);
|
||||
setWatch(showAppSettingsMenu, BTN1, { repeat: false });
|
||||
}
|
||||
let appSettings = storage.read(app.settings);
|
||||
if (!appSettings) {
|
||||
return showError('Missing settings');
|
||||
}
|
||||
let appSettings = storage.read(app.id+'.settings.js');
|
||||
try {
|
||||
appSettings = eval(appSettings);
|
||||
} catch (e) {
|
||||
|
|
|
@ -60,8 +60,6 @@ var AppInfo = {
|
|||
if (app.type && app.type!="app") json.type = app.type;
|
||||
if (fileContents.find(f=>f.name==app.id+".app.js"))
|
||||
json.src = app.id+".app.js";
|
||||
if (fileContents.find(f=>f.name==app.id+".settings.js"))
|
||||
json.settings = app.id+".settings.js";
|
||||
if (fileContents.find(f=>f.name==app.id+".img"))
|
||||
json.icon = app.id+".img";
|
||||
if (app.sortorder) json.sortorder = app.sortorder;
|
||||
|
|
|
@ -9,5 +9,8 @@
|
|||
"scripts": {
|
||||
"test": "node bin/sanitycheck.js",
|
||||
"start": "npx http-server"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": "^7.1.1"
|
||||
}
|
||||
}
|
||||
|
|