Bryan 2023-11-05 08:27:41 -06:00
commit d23352d32b
12 changed files with 599 additions and 201 deletions

View File

@ -1 +1,2 @@
0.01: attempt to import 0.01: attempt to import
0.02: better GPS support, adding altitude and temperature support

View File

@ -25,8 +25,9 @@ minutes, real distance will be usually higher than approximation.
Useful gestures: Useful gestures:
F -- disable GPS. F -- disable GPS.
G -- enable GPS for 4 hours. G -- enable GPS for 4 hours in low power mode.
N -- take a note and write it to the log. N -- take a note and write it to the log.
S -- enable GPS for 30 minutes in high power mode.
When application detects watch is being worn, it will use vibrations When application detects watch is being worn, it will use vibrations
to communicate back to the user. to communicate back to the user.
@ -48,4 +49,10 @@ night.
I'd like to make display nicer, and likely more dynamic, displaying I'd like to make display nicer, and likely more dynamic, displaying
whatever application believes is most important at the time (and whatever application believes is most important at the time (and
possibly allowing scrolling). possibly allowing scrolling).
Todo:
*) only turn on compass when needed
*) adjust draw timeouts to save power

View File

@ -1,13 +1,15 @@
{ "id": "sixths", { "id": "sixths",
"name": "Sixth sense", "name": "Sixth sense",
"version":"0.01", "version":"0.02",
"description": "Clock for outdoor use with GPS support", "description": "Clock for outdoor use with GPS support",
"icon": "app.png", "icon": "app.png",
"readme": "README.md", "readme": "README.md",
"supports" : ["BANGLEJS2"], "supports" : ["BANGLEJS2"],
"tags": "", "allow_emulator": true,
"type": "clock",
"tags": "clock",
"storage": [ "storage": [
{"name":"sixths.app.js","url":"app.js"}, {"name":"sixths.app.js","url":"sixths.app.js"},
{"name":"sixths.img","url":"app-icon.js","evaluate":true} {"name":"sixths.img","url":"app-icon.js","evaluate":true}
] ]
} }

View File

@ -1,20 +1,53 @@
// Sixth sense
// Options you'll want to edit
const rest_altitude = 354;
const geoid_to_sea_level = 0; // Maybe BangleJS2 already compensates?
const W = g.getWidth(); const W = g.getWidth();
const H = g.getHeight(); const H = g.getHeight();
var cx = 100; cy = 105; sc = 70; var cx = 100; cy = 105; sc = 70;
var buzz = "", msg = "";
temp = 0; alt = 0; bpm = 0; temp = 0; alt = 0; bpm = 0;
var buzz = "", msg = "", inm = "", l = "", note = "(NOTEHERE)"; var buzz = "", /* Set this to transmit morse via vibrations */
var mode = 0, mode_time = 0; // 0 .. normal, 1 .. note inm = "", l = "", /* For incoming morse handling */
in_str = "",
note = "(NOTEHERE)",
debug = "v930", debug2 = "(otherdb)", debug3 = "(short)";
var mode = 0, mode_time = 0; // 0 .. normal, 1 .. note, 2.. mark name
var disp_mode = 0; // 0 .. normal, 1 .. small time
var gps_on = 0, last_fix = 0, last_restart = 0, last_pause = 0, last_fstart = 0; // utime // GPS handling
var gps_needed = 0, gps_limit = 0; // seconds var gps_on = 0, // time GPS was turned on
last_fix = 0, // time of last fix
last_restart = 0, last_pause = 0, last_fstart = 0; // utime
var gps_needed = 0, // how long to wait for a fix
gps_limit = 0, // timeout -- when to stop recording
gps_speed_limit = 0;
var prev_fix = null; var prev_fix = null;
var gps_dist = 0; var gps_dist = 0;
var is_active = false; var mark_heading = -1;
var cur_altitude = 0, cur_temperature = 0, alt_adjust = 0;
const rest_altitude = 354; // Is the human present?
var is_active = false, last_active = getTime();
var is_level = false;
// For altitude handling.
var cur_altitude = 0;
var cur_temperature = 0, alt_adjust = 0;
var alt_adjust_mode = "";
// Marks
var cur_mark = null;
// Icons
icon_alt = "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00";
icon_m = "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00";
icon_km = "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00";
icon_kph = "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3";
icon_c = "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
function toMorse(x) { function toMorse(x) {
r = ""; r = "";
@ -28,12 +61,10 @@ function toMorse(x) {
} }
return r; return r;
} }
function aload(s) { function aload(s) {
buzz += toMorse(' E'); buzz += toMorse(' E');
load(s); load(s);
} }
function gpsRestart() { function gpsRestart() {
print("gpsRestart"); print("gpsRestart");
Bangle.setGPSPower(1, "sixths"); Bangle.setGPSPower(1, "sixths");
@ -41,33 +72,155 @@ function gpsRestart() {
last_pause = 0; last_pause = 0;
last_fstart = 0; last_fstart = 0;
} }
function gpsPause() { function gpsPause() {
print("gpsPause"); print("gpsPause");
Bangle.setGPSPower(0, "sixths"); Bangle.setGPSPower(0, "sixths");
last_restart = 0; last_restart = 0;
last_pause = getTime(); last_pause = getTime();
} }
function gpsOn() { function gpsOn() {
gps_on = getTime(); gps_on = getTime();
gps_needed = 1000; gps_needed = 1000;
gps_limit = 60*60*4;
last_fix = 0; last_fix = 0;
prev_fix = null; prev_fix = null;
gps_dist = 0; gps_dist = 0;
gpsRestart(); gpsRestart();
} }
function gpsOff() { function gpsOff() {
Bangle.setGPSPower(0, "sixths"); Bangle.setGPSPower(0, "sixths");
gps_on = 0; gps_on = 0;
} }
function fmtDist(km) { return km.toFixed(1) + icon_km; }
function fmtSteps(n) { return fmtDist(0.001 * 0.719 * n); }
function fmtAlt(m) { return m.toFixed(0) + icon_alt; }
function fmtTimeDiff(d) {
if (d < 180)
return ""+d.toFixed(0);
d = d/60;
return ""+d.toFixed(0)+"m";
}
function gpsHandleFix(fix) {
if (!prev_fix) {
show("GPS acquired", 10);
buzz += " .";
prev_fix = fix;
}
if (0) {
/* GPS altitude fluctuates a lot, not really usable */
alt_adjust = cur_altitude - (fix.alt + geoid_to_sea_level);
alt_adjust_mode = "g";
}
if (1) {
debug = ""+fix.alt+"m "+alt_adjust;
}
if (1) {
let now1 = Date();
let now2 = fix.time;
n1 = now1.getMinutes() * 60 + now1.getSeconds();
n2 = now2.getMinutes() * 60 + now2.getSeconds();
debug2 = "te "+(n2-n1)+"s";
}
loggps(fix);
d = calcDistance(fix, prev_fix);
if (d > 30) {
prev_fix = fix;
gps_dist += d/1000;
}
}
function gpsHandle() {
let msg = "";
if (!last_restart) {
d = (getTime()-last_pause);
if (last_fix)
msg = "PL"+ fmtTimeDiff(getTime()-last_fix);
else
msg = "PN"+ fmtTimeDiff(getTime()-gps_on);
print("gps on, paused ", d, gps_needed);
if (d > gps_needed * 2) {
gpsRestart();
}
} else {
fix = Bangle.getGPSFix();
if (fix && fix.fix && fix.lat) {
gpsHandleFix(fix);
msg = fix.speed.toFixed(1) + icon_kph;
print("GPS FIX", msg);
if (!last_fstart)
last_fstart = getTime();
last_fix = getTime();
gps_needed = 60;
} else {
if (last_fix)
msg = "L"+ fmtTimeDiff(getTime()-last_fix);
else {
msg = "N"+ fmtTimeDiff(getTime()-gps_on);
if (fix) {
msg += " " + fix.satellites + "sats";
}
}
}
d = (getTime()-last_restart);
d2 = (getTime()-last_fstart);
print("gps on, restarted ", d, gps_needed, d2, fix.lat);
if (getTime() > gps_speed_limit &&
(d > gps_needed || (last_fstart && d2 > 10))) {
gpsPause();
gps_needed = gps_needed * 1.5;
print("Pausing, next try", gps_needed);
}
}
msg += " "+gps_dist.toFixed(1)+icon_km;
return msg;
}
function markNew() {
let r = {};
r.time = getTime();
r.fix = prev_fix;
r.steps = Bangle.getHealthStatus("day").steps;
r.gps_dist = gps_dist;
r.altitude = cur_altitude;
r.name = "auto";
return r;
}
function markHandle() {
let m = cur_mark;
msg = m.name + ">" + fmtTimeDiff(getTime()- m.time);
if (m.fix && m.fix.fix) {
let s = fmtDist(calcDistance(m.fix, prev_fix)/1000) + icon_km;
msg += " " + s;
debug = "wp>" + s;
mark_heading = 180 + calcBearing(m.fix, prev_fix);
debug2 = "wp>" + mark_heading;
} else {
msg += " w" + fmtDist(gps_dist - m.gps_dist);
}
return msg;
}
function entryDone() {
show(":" + in_str);
buzz += " .";
switch (mode) {
case 1: logstamp(">" + in_str); break;
case 2: cur_mark.name = in_str; break;
}
in_str = 0;
mode = 0;
}
function inputHandler(s) { function inputHandler(s) {
print("Ascii: ", s); print("Ascii: ", s, s[0], s[1]);
if (mode == 1) { if (s[0] == '^') {
note = note + s; switch (s[1]) {
case 'E': mode = 0; break;
case 'T': entryDone(); break;
}
return;
}
if ((mode == 1) || (mode == 2)){
in_str = in_str + s;
show(">"+in_str, 10);
mode_time = getTime(); mode_time = getTime();
return; return;
} }
@ -80,12 +233,21 @@ function inputHandler(s) {
else else
s = s+(bat/5); s = s+(bat/5);
buzz += toMorse(s); buzz += toMorse(s);
show("Bat "+bat+"%", 60);
break;
case 'F': gpsOff(); show("GPS off", 3); break;
case 'G': gpsOn(); gps_limit = getTime() + 60*60*4; show("GPS on", 3); break;
case 'I':
disp_mode += 1;
if (disp_mode == 2) {
disp_mode = 0;
}
break; break;
case 'F': gpsOff(); break;
case 'G': gpsOn(); break;
case 'L': aload("altimeter.app.js"); break; case 'L': aload("altimeter.app.js"); break;
case 'N': mode = 1; note = ">"; mode_time = getTime(); break; case 'M': mode = 2; show("M>", 10); cur_mark = markNew(); mode_time = getTime(); break;
case 'N': mode = 1; show(">", 10); mode_time = getTime(); break;
case 'O': aload("orloj.app.js"); break; case 'O': aload("orloj.app.js"); break;
case 'S': gpsOn(); gps_limit = getTime() + 60*30; gps_speed_limit = gps_limit; show("GPS on", 3); break;
case 'T': case 'T':
s = ' T'; s = ' T';
d = new Date(); d = new Date();
@ -94,9 +256,9 @@ function inputHandler(s) {
buzz += toMorse(s); buzz += toMorse(s);
break; break;
case 'R': aload("run.app.js"); break; case 'R': aload("run.app.js"); break;
case 'Y': buzz += " ."; Bangle.resetCompass(); break;
} }
} }
const morseDict = { const morseDict = {
'.-': 'A', '.-': 'A',
'-...': 'B', '-...': 'B',
@ -135,37 +297,46 @@ const morseDict = {
'-....': '6', '-....': '6',
'-----': '0', '-----': '0',
}; };
let asciiDict = {}; let asciiDict = {};
for (let k in morseDict) { for (let k in morseDict) {
print(k, morseDict[k]); print(k, morseDict[k]);
asciiDict[morseDict[k]] = k; asciiDict[morseDict[k]] = k;
} }
function morseToAscii(morse) { function morseToAscii(morse) {
return morseDict[morse]; return morseDict[morse];
} }
function asciiToMorse(char) { function asciiToMorse(char) {
return asciiDict[char]; return asciiDict[char];
} }
function morseHandler() { function morseHandler() {
inputHandler(morseToAscii(inm)); if (inm[0] == "^") {
inputHandler("^"+morseToAscii(inm.substr(1)));
} else {
inputHandler(morseToAscii(inm));
}
inm = ""; inm = "";
l = ""; l = "";
} }
function touchHandler(d) { function touchHandler(d) {
let x = Math.floor(d.x); let x = Math.floor(d.x);
let y = Math.floor(d.y); let y = Math.floor(d.y);
g.setColor(0.25, 0, 0); if (1) { /* Just a debugging feature */
g.fillCircle(W-x, W-y, 5); g.setColor(0.25, 0, 0);
if (0)
if (d.b) { g.fillCircle(W-x, W-y, 5);
else
g.fillCircle(x, y, 5);
}
if (!d.b) {
morseHandler();
l = "";
return;
}
if (y > H/2 && l == "") {
inm = "^";
}
if (x < W/2 && y < H/2 && l != ".u") { if (x < W/2 && y < H/2 && l != ".u") {
inm = inm + "."; inm = inm + ".";
l = ".u"; l = ".u";
@ -181,14 +352,10 @@ function touchHandler(d) {
if (x > W/2 && y > H/2 && l != "-d") { if (x > W/2 && y > H/2 && l != "-d") {
inm = inm + "-"; inm = inm + "-";
l = "-d"; l = "-d";
} }
} else //print(inm, "drag:", d);
morseHandler();
print(inm, "drag:", d);
} }
function add0(i) { function add0(i) {
if (i > 9) { if (i > 9) {
return ""+i; return ""+i;
@ -196,18 +363,14 @@ function add0(i) {
return "0"+i; return "0"+i;
} }
} }
var lastHour = -1, lastMin = -1; var lastHour = -1, lastMin = -1;
function logstamp(s) { function logstamp(s) {
logfile.write("utime=" + getTime() + " " + s + "\n"); logfile.write("utime=" + getTime() + " " + s + "\n");
} }
function loggps(fix) { function loggps(fix) {
logfile.write(fix.lat + " " + fix.lon + " "); logfile.write(fix.lat + " " + fix.lon + " ");
logstamp(""); logstamp("");
} }
function hourly() { function hourly() {
print("hourly"); print("hourly");
s = ' T'; s = ' T';
@ -215,31 +378,35 @@ function hourly() {
buzz += toMorse(s); buzz += toMorse(s);
logstamp(""); logstamp("");
} }
function show(msg, timeout) {
note = msg;
}
function fivemin() { function fivemin() {
print("fivemin"); print("fivemin");
s = ' B'; s = ' B';
bat = E.getBattery(); bat = E.getBattery();
if (bat < 45) { if (bat < 25) {
s = s+(bat/5);
if (is_active) if (is_active)
buzz += toMorse(s); buzz += toMorse(s);
show("Bat "+bat+"%", 60);
} }
if (0) try {
Bangle.getPressure().then((x) => { cur_altitude = x.altitude; Bangle.getPressure().then((x) => { cur_altitude = x.altitude;
cur_temperature = x.temperature; }, cur_temperature = x.temperature; },
print) print);
.catch(print); } catch (e) {
print("Altimeter error", e);
}
} }
function every(now) { function every(now) {
if ((mode > 0) && (mode_time - getTime() > 60)) { if ((mode > 0) && (getTime() - mode_time > 10)) {
if (mode == 1) { if (mode == 1) {
logstamp(">" + note); entryDone();
} }
mode = 0; mode = 0;
} }
if (gps_on && getTime() - gps_on > gps_limit) { if (gps_on && getTime() > gps_limit && getTime() > gps_speed_limit) {
Bangle.setGPSPower(0, "sixths"); Bangle.setGPSPower(0, "sixths");
gps_on = 0; gps_on = 0;
} }
@ -255,96 +422,136 @@ function every(now) {
} }
function radians(a) { return a*Math.PI/180; }
function degrees(a) { return a*180/Math.PI; }
// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km // distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km
// https://www.movable-type.co.uk/scripts/latlong.html // https://www.movable-type.co.uk/scripts/latlong.html
// (Equirectangular approximation) // (Equirectangular approximation)
function calcDistance(a,b) { function calcDistance(a,b) {
function radians(a) { return a*Math.PI/180; }
var x = radians(b.lon-a.lon) * Math.cos(radians((a.lat+b.lat)/2)); var x = radians(b.lon-a.lon) * Math.cos(radians((a.lat+b.lat)/2));
var y = radians(b.lat-a.lat); var y = radians(b.lat-a.lat);
return Math.sqrt(x*x + y*y) * 6371000; return Math.sqrt(x*x + y*y) * 6371000;
} }
// thanks to waypointer
function calcBearing(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 testBearing() {
let p1 = {}, p2 = {};
p1.lat = 40; p2.lat = 50;
p1.lon = 14; p2.lon = 14;
print("bearing = ", calcBearing(p1, p2));
}
function radA(p) { return p*(Math.PI*2); }
function radD(d) { return d*(H/2); }
function radX(p, d) {
let a = radA(p);
return H/2 + Math.sin(a)*radD(d);
}
function radY(p, d) {
let a = radA(p);
return W/2 - Math.cos(a)*radD(d);
}
function drawDot(h, d, s) {
let x = radX(h/360, d);
let y = radY(h/360, d);
g.fillCircle(x,y, 10);
}
function drawBackground() {
acc = Bangle.getAccel();
is_level = (acc.z < -0.95);
if (is_level) {
let obj = Bangle.getCompass();
if (obj) {
let h = 360-obj.heading;
print("Compass", h);
g.setColor(0.5, 0.5, 1);
drawDot(h, 0.7, 10);
}
}
if (prev_fix && prev_fix.fix) {
g.setColor(0.5, 1, 0.5);
drawDot(prev_fix.course, 0.5, 6);
}
if (mark_heading != -1) {
g.setColor(1, 0.5, 0.5);
drawDot(mark_heading, 0.6, 8);
}
}
function drawTime(now) {
if (disp_mode == 0)
g.setFont('Vector', 60);
else
g.setFont('Vector', 26);
g.setFontAlign(1, 1);
g.drawString(now.getHours() + ":" + add0(now.getMinutes()), W, 90);
}
function draw() { function draw() {
if (disp_mode == 2) {
draw_all();
return;
}
g.setColor(1, 1, 1); g.setColor(1, 1, 1);
g.fillRect(0, 25, W, H); g.fillRect(0, 24, W, H);
g.setFont('Vector', 60);
if (0) {
g.setColor(0.25, 1, 1);
g.fillPoly([ W/2, 24, W, 80, 0, 80 ]);
}
let msg = "";
if (gps_on) {
msg = gpsHandle();
} else {
msg = note;
}
drawBackground();
g.setColor(0, 0, 0);
g.setFontAlign(-1, 1);
let now = new Date(); let now = new Date();
g.drawString(now.getHours() + ":" + add0(now.getMinutes()), 10, 90); g.setColor(0, 0, 0);
drawTime(now);
every(now); every(now);
let km = 0.001 * 0.719 * Bangle.getHealthStatus("day").steps; let km = 0.001 * 0.719 * Bangle.getHealthStatus("day").steps;
g.setFontAlign(-1, 1);
g.setFont('Vector', 26); g.setFont('Vector', 26);
const weekday = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; const weekday = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
g.drawString(weekday[now.getDay()] + "" + now.getDate() + ". " + km.toFixed(1) + "km", 10, 115); g.drawString(weekday[now.getDay()] + "" + now.getDate() + ". "
+ fmtSteps(Bangle.getHealthStatus("day").steps), 10, 115);
if (gps_on) {
if (!last_restart) {
d = (getTime()-last_pause);
if (last_fix)
msg = "PL"+ (getTime()-last_fix).toFixed(0);
else
msg = "PN"+ (getTime()-gps_on).toFixed(0);
print("gps on, paused ", d, gps_needed);
if (d > gps_needed * 2) {
gpsRestart();
}
} else {
fix = Bangle.getGPSFix();
if (fix.fix && fix.lat) {
if (!prev_fix) {
prev_fix = fix;
}
msg = fix.speed.toFixed(1) + " km/h";
if (!last_fstart)
last_fstart = getTime();
last_fix = getTime();
gps_needed = 60;
loggps(fix);
print("GPS FIX", msg);
d = calcDistance(fix, prev_fix);
if (d > 30) {
prev_fix = fix;
gps_dist += d/1000;
}
} else {
if (last_fix)
msg = "L"+ (getTime()-last_fix).toFixed(0);
else
msg = "N"+ (getTime()-gps_on).toFixed(0);
}
d = (getTime()-last_restart);
d2 = (getTime()-last_fstart);
print("gps on, restarted ", d, gps_needed, d2, fix.lat);
if (d > gps_needed || (last_fstart && d2 > 10)) {
gpsPause();
gps_needed = gps_needed * 1.5;
print("Pausing, next try", gps_needed);
}
}
msg += " "+gps_dist.toFixed(1)+"km";
} else {
msg = note;
}
g.drawString(msg, 10, 145); g.drawString(msg, 10, 145);
if (is_active) {
g.drawString("act " + (cur_altitude - alt_adjust).toFixed(0), 10, 175); if (getTime() - last_active > 15*60) {
} else {
alt_adjust = cur_altitude - rest_altitude; alt_adjust = cur_altitude - rest_altitude;
g.drawString(alt_adjust.toFixed(0) + "m " + cur_temperature.toFixed(1)+"C", 10, 175); alt_adjust_mode = "h";
msg = "H)" + fmtAlt(alt_adjust);
} else {
msg = alt_adjust_mode+")"+fmtAlt(cur_altitude - alt_adjust);
} }
msg = msg + " " + cur_temperature.toFixed(1)+icon_c;
if (cur_mark) {
msg = markHandle();
}
g.drawString(msg, 10, 175);
if (disp_mode == 1) {
g.drawString(debug, 10, 45);
g.drawString(debug2, 10, 65);
g.drawString(debug3, 10, 85);
}
queueDraw(); queueDraw();
} }
function draw_all() { function draw_all() {
g.setColor(0, 0, 0); g.setColor(0, 0, 0);
g.fillRect(0, 0, W, H); g.fillRect(0, 0, W, H);
@ -394,14 +601,13 @@ function draw_all() {
g.setFont('Vector', 22); g.setFont('Vector', 22);
g.drawString(now.getDate()+"."+(now.getMonth()+1)+" "+now.getDay(), 3, 60); g.drawString(now.getDate()+"."+(now.getMonth()+1)+" "+now.getDay(), 3, 60);
g.drawString(msg, 3, 80); g.drawString("(message here)", 3, 80);
g.drawString("S" + step + " B" + Math.round(bat/10) + (Bangle.isCharging()?"c":""), 3, 100); g.drawString("S" + step + " B" + Math.round(bat/10) + (Bangle.isCharging()?"c":""), 3, 100);
g.drawString("A" + Math.round(alt) + " T" + Math.round(temp), 3, 120); g.drawString("A" + Math.round(alt) + " T" + Math.round(temp), 3, 120);
g.drawString("C" + Math.round(co.heading) + " B" + bpm, 3, 140); g.drawString("C" + Math.round(co.heading) + " B" + bpm, 3, 140);
queueDraw(); queueDraw();
} }
function accelTask() { function accelTask() {
tm = 100; tm = 100;
acc = Bangle.getAccel(); acc = Bangle.getAccel();
@ -424,7 +630,6 @@ function accelTask() {
setTimeout(accelTask, tm); setTimeout(accelTask, tm);
} }
function buzzTask() { function buzzTask() {
if (buzz != "") { if (buzz != "") {
now = buzz[0]; now = buzz[0];
@ -442,9 +647,8 @@ function buzzTask() {
setTimeout(buzzTask, 6*dot); setTimeout(buzzTask, 6*dot);
} else print("Unknown character -- ", now, buzz); } else print("Unknown character -- ", now, buzz);
} else } else
setTimeout(buzzTask, 60000); setTimeout(buzzTask, 1000);
} }
function aliveTask() { function aliveTask() {
function cmp(s) { function cmp(s) {
let d = acc[s] - last_acc[s]; let d = acc[s] - last_acc[s];
@ -456,6 +660,7 @@ function aliveTask() {
if (cmp("x") || cmp("y") || cmp("z")) { if (cmp("x") || cmp("y") || cmp("z")) {
print("active"); print("active");
is_active = true; is_active = true;
last_active = getTime();
} }
last_acc = acc; last_acc = acc;
@ -476,14 +681,15 @@ function queueDraw() {
}, next - (Date.now() % next)); }, next - (Date.now() % next));
} }
function start() { function start() {
Bangle.on("drag", touchHandler); Bangle.on("drag", touchHandler);
if (0) if (0)
Bangle.on("accel", accelHandler); Bangle.on("accel", accelHandler);
if (0) { if (1) {
Bangle.setCompassPower(1, "sixths"); Bangle.setCompassPower(1, "sixths");
Bangle.setBarometerPower(1, "sixths"); Bangle.setBarometerPower(1, "sixths");
}
if (0) {
Bangle.setHRMPower(1, "sixths"); Bangle.setHRMPower(1, "sixths");
Bangle.setGPSPower(1, "sixths"); Bangle.setGPSPower(1, "sixths");
Bangle.on("HRM", (hrm) => { bpm = hrm.bpm; } ); Bangle.on("HRM", (hrm) => { bpm = hrm.bpm; } );
@ -500,9 +706,14 @@ function start() {
} }
g.reset(); g.reset();
Bangle.setUI(); Bangle.setUI({
mode : "clock"
});
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
let logfile = require("Storage").open("sixths.egt", "a"); let logfile = require("Storage").open("sixths.egt", "a");
start(); if (0) {
testBearing();
} else
start();

View File

@ -5,7 +5,8 @@ A simple game of stacking cubes.
## Usage ## Usage
Press the button to stack! Boxes move horizontally. Use button to stack them on top of existing
boxes. You win when you reach top of the screen.
## Creator ## Creator

View File

@ -6,6 +6,7 @@
"icon": "app.png", "icon": "app.png",
"tags": "game", "tags": "game",
"supports" : ["BANGLEJS", "BANGLEJS2"], "supports" : ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"stacker.app.js","url":"app.js"}, {"name":"stacker.app.js","url":"app.js"},

View File

@ -1,2 +1,3 @@
0.01: New app! 0.01: New app!
0.02: Better controls, implement game over. 0.02: Better controls, implement game over.
0.03: Implement mode and level selection screens.

View File

@ -1,7 +1,7 @@
{ "id": "tetris", { "id": "tetris",
"name": "Tetris", "name": "Tetris",
"shortName":"Tetris", "shortName":"Tetris",
"version":"0.02", "version":"0.03",
"description": "Tetris", "description": "Tetris",
"icon": "tetris.png", "icon": "tetris.png",
"readme": "README.md", "readme": "README.md",

View File

@ -36,11 +36,27 @@ const tiles = [
const ox = 176/2 - 5*8; const ox = 176/2 - 5*8;
const oy = 8; const oy = 8;
var pf = Array(23).fill().map(()=>Array(12).fill(0)); // field is really 10x20, but adding a border for collision checks /* 0 .. simulated arrows
pf[20].fill(1); 1 .. drag piece
pf[21].fill(1); 2 .. accelerometer. 12 lines record.
pf[22].fill(1); 3 .. altimeter
pf.forEach((x,i) => { pf[i][0] = 1; pf[i][11] = 1; }); */
var control = 0, level = 0;
var alt_start = -9999; /* For altimeter control */
/* 0 .. menu
1 .. game
2 .. game over */
var state = 0;
var pf;
function initGame() {
pf = Array(23).fill().map(()=>Array(12).fill(0)); // field is really 10x20, but adding a border for collision checks
pf[20].fill(1);
pf[21].fill(1);
pf[22].fill(1);
pf.forEach((x,i) => { pf[i][0] = 1; pf[i][11] = 1; });
}
function rotateTile(t, r) { function rotateTile(t, r) {
var nt = JSON.parse(JSON.stringify(t)); var nt = JSON.parse(JSON.stringify(t));
@ -98,6 +114,8 @@ function redrawPF(ly) {
function gameOver() { function gameOver() {
g.setColor(1, 1, 1).setFontAlign(0, 1, 0).setFont("Vector",22) g.setColor(1, 1, 1).setFontAlign(0, 1, 0).setFont("Vector",22)
.drawString("Game Over", 176/2, 76); .drawString("Game Over", 176/2, 76);
state = 0;
E.showAlert("Game Over").then(selectGame, print);
} }
function insertAndCheck() { function insertAndCheck() {
@ -138,6 +156,8 @@ function moveOk(t, dx, dy) {
} }
function gameStep() { function gameStep() {
if (state != 1)
return;
if (Date.now()-time > dropInterval) { // drop one step if (Date.now()-time > dropInterval) { // drop one step
time = Date.now(); time = Date.now();
if (moveOk(ct, 0, 1)) { if (moveOk(ct, 0, 1)) {
@ -169,12 +189,50 @@ function move(x, y) {
} }
} }
Bangle.setUI(); function linear(x) {
Bangle.on("drag", (e) => { print("Linear: ", x);
let h = 176/2; let now = px / 10;
if (!e.b) if (x < now-0.06)
move(-1, 0);
if (x > now+0.06)
move(1, 0);
}
function newGame() {
E.showMenu();
Bangle.setUI();
if (control == 2) {
Bangle.on("accel", (e) => {
if (state != 1) return;
if (control != 2) return;
print(e.x);
linear((0.2-e.x) * 2.5);
});
}
if (control == 3) {
Bangle.setBarometerPower(true);
Bangle.on("pressure", (e) => {
if (state != 1) return;
if (control != 3) return;
let a = e.altitude;
if (alt_start == -9999)
alt_start = a;
a = a - alt_start;
print(e.altitude, a);
linear(a);
});
}
Bangle.on("drag", (e) => {
let h = 176/2;
if (state == 2) {
if (e.b)
selectGame();
return;
}
if (!e.b)
return; return;
if (e.y < h) { if (state == 0) return;
if (e.y < h) {
if (e.x < h) if (e.x < h)
rotate(); rotate();
else { else {
@ -184,21 +242,60 @@ Bangle.on("drag", (e) => {
g.flip(); g.flip();
} }
} }
} else { } else {
if (control == 1)
linear((e.x - 20) / 156);
if (control != 0)
return;
if (e.x < h) if (e.x < h)
move(-1, 0); move(-1, 0);
else else
move(1, 0); move(1, 0);
} }
}); });
Bangle.on("swipe", (x,y) => { initGame();
if (y<0) y = 0; drawGame();
move(x, y); state = 1;
}); var step = 450 - 50*level;
if (control == 3)
step = step*2;
dropInterval = step;
var gi = setInterval(gameStep, 50);
}
drawBoundingBox(); function drawGame() {
g.setColor(1, 1, 1).setFontAlign(0, 1, 0).setFont("6x15", 1).drawString("Lines", 22, 30).drawString("Next", 176-22, 30); drawBoundingBox();
showNext(ntn, ntr); g.setColor(1, 1, 1).setFontAlign(0, 1, 0)
g.setColor(0).fillRect(5, 30, 41, 80).setColor(1, 1, 1).drawString(nlines.toString(), 22, 50); .setFont("6x15", 1).drawString("Lines", 22, 30)
var gi = setInterval(gameStep, 20); .drawString("Next", 176-22, 30);
showNext(ntn, ntr);
g.setColor(0).fillRect(5, 30, 41, 80)
.setColor(1, 1, 1).drawString(nlines.toString(), 22, 50);
}
function selectLevel() {
print("Level selection menu");
var menu = {};
menu["Level 1"] = () => { level = 0; selectGame(); };
menu["Level 2"] = () => { level = 1; selectGame(); };
menu["Level 3"] = () => { level = 2; selectGame(); };
E.showMenu(menu);
}
function selectGame() {
state = 0;
print("Game selection menu");
//for (let i = 0; i < 100000; i++) ;
var menu = {};
menu["Normal"] = () => { control = 0; newGame(); };
menu["Drag"] = () => { control = 1; newGame(); };
menu["Tilt"] = () => { control = 2; newGame(); };
menu["Move"] = () => { control = 3; newGame(); };
menu["Level"] = () => { selectLevel(); };
E.showMenu(menu);
}
selectGame();

View File

@ -1,2 +1,3 @@
0.01: New App! 0.01: New App!
0.02: Display waypoint name instead of its index in remove menu and fix icon 0.02: Display waypoint name instead of its index in remove menu and fix icon
0.03: Use text input for waypoint names, allow marking waypoint with current GPS position

View File

@ -13,6 +13,10 @@ var wp = require('Storage').readJSON("waypoints.json", true) || [];
2 .. DD MM'ss" 2 .. DD MM'ss"
*/ */
var mode = 1; var mode = 1;
var key; /* Shared between functions, typically wp name */
var fix; /* GPS fix */
var cancel_gps;
var gps_start;
function writeWP() { function writeWP() {
require('Storage').writeJSON("waypoints.json", wp); require('Storage').writeJSON("waypoints.json", wp);
@ -22,28 +26,94 @@ function mainMenu() {
var menu = { var menu = {
"< Back" : Bangle.load "< Back" : Bangle.load
}; };
if (Object.keys(wp).length==0) Object.assign(menu, {"NO WPs":""}); if (Object.keys(wp).length==0) {
else for (let id in wp) { //Object.assign(menu, {"NO WPs":""});
print("(no waypoints)");
} else for (let id in wp) {
let i = id; let i = id;
menu[wp[id]["name"]]=()=>{ decode(i); }; menu[wp[id]["name"]]=()=>{ show(i); };
} }
menu["Add"]=addCard; menu["Add"]=addCard;
menu["Remove"]=removeCard; menu["Remove"]=removeCard;
menu["Format"]=setFormat; menu["Format"]=setFormat;
menu["Mark GPS"]=markGps;
g.clear(); g.clear();
E.showMenu(menu); E.showMenu(menu);
} }
function setFormat() { function updateGps() {
var confirmRemove = new Layout ( let have = false, lat = "lat", lon = "lon", alt = "alt", speed = "speed";
{type:"v", c: [
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Format"}, if (cancel_gps)
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "DD.dddd", cb:l=>{ mode = 0; mainMenu(); }}, return;
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "DD MM.mmm'", cb:l=>{ mode = 1; mainMenu(); }}, fix = Bangle.getGPSFix();
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "DD MM'ss"+'"', cb:l=>{ mode = 2; mainMenu(); }},
], lazy:true}); speed = "no fix for " + (getTime() - gps_start).toFixed(0) + "s";
if (fix && fix.fix && fix.lat) {
lat = "" + lat(fix.lat);
lon = "" + lon(fix.lon);
alt = "alt " + fix.alt.toFixed(0) + "m";
speed = "speed " + fix.speed.toFixed(1) + "kt";
have = true;
}
g.reset().setFont("Vector", 20)
.setColor(1,1,1)
.fillRect(0, 0, 176, 120)
.setColor(0,0,0)
.drawString(key, 0, 0)
.drawString(lat, 0, 20)
.drawString(lon, 0, 40)
.drawString(alt, 0, 60)
.drawString(speed, 0, 80);
setTimeout(updateGps, 100);
}
function stopGps() {
cancel_gps=true;
Bangle.setGPSPower(0, "waypoint_editor");
}
function confirmGps(s) {
key = s;
var la = new Layout (
{type:"v", c: [
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:""},
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:""},
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:""},
{type:"h", c: [
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
print("should mark", key, fix); createWP(fix.lat, fix.lon, key); cancel_gps=true; mainMenu();
}},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{ cancel_gps=true; mainMenu(); }}
]}
], lazy:true});
g.clear(); g.clear();
confirmRemove.render(); la.render();
updateGps();
}
function markGps() {
cancel_gps = false;
Bangle.setGPSPower(1, "waypoint_editor");
gps_start = getTime();
require("textinput").input({text:"wp"}).then(key => {
confirmGps(key);
});
}
function setFormat() {
var la = new Layout (
{type:"v", c: [
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Format"},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "DD.dddd", cb:l=>{ mode = 0; mainMenu(); }},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "DD MM.mmm'", cb:l=>{ mode = 1; mainMenu(); }},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "DD MM'ss"+'"', cb:l=>{ mode = 2; mainMenu(); }},
], lazy:true});
g.clear();
la.render();
} }
function format(x) { function format(x) {
@ -65,7 +135,6 @@ function format(x) {
return "" + d + " " + mf + "'" + s + '"'; return "" + d + " " + mf + "'" + s + '"';
} }
} }
function lat(x) { function lat(x) {
c = "N"; c = "N";
if (x<0) { if (x<0) {
@ -74,7 +143,6 @@ function lat(x) {
} }
return c+format(x); return c+format(x);
} }
function lon(x) { function lon(x) {
c = "E"; c = "E";
if (x<0) { if (x<0) {
@ -84,17 +152,17 @@ function lon(x) {
return c+format(x); return c+format(x);
} }
function decode(pin) { function show(pin) {
print(pin); print(pin);
var i = wp[pin]; var i = wp[pin];
var pinDecrypted=i["name"] + "\n" + lat(i["lat"]) + "\n" + lon(i["lon"]); var l = i["name"] + "\n" + lat(i["lat"]) + "\n" + lon(i["lon"]);
var showPin = new Layout ({ var la = new Layout ({
type:"v", c: [ type:"v", c: [
{type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: pinDecrypted}, {type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: l},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}} {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}}
], lazy:true}); ], lazy:true});
g.clear(); g.clear();
showPin.render(); la.render();
} }
function showNumpad(text, key_, callback) { function showNumpad(text, key_, callback) {
@ -155,10 +223,10 @@ function showNumpad(text, key_, callback) {
function removeCard() { function removeCard() {
var menu = { var menu = {
"" : {title : "select card"}, "" : {title : "Select WP"},
"< Back" : mainMenu "< Back" : mainMenu
}; };
if (Object.keys(wp).length==0) Object.assign(menu, {"NO CARDS":""}); if (Object.keys(wp).length==0) Object.assign(menu, {"No WPs":""});
else { else {
wp.forEach((val, card) => { wp.forEach((val, card) => {
const name = wp[card].name; const name = wp[card].name;
@ -186,17 +254,16 @@ function removeCard() {
} }
function ask01(t, cb) { function ask01(t, cb) {
var confirmRemove = new Layout ( var la = new Layout (
{type:"v", c: [ {type:"v", c: [
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Format"}, {type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Select"},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: t[0], cb:l=>{ cb(1); }}, {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: t[0], cb:l=>{ cb(1); }},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: t[1], cb:l=>{ cb(-1); }}, {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: t[1], cb:l=>{ cb(-1); }},
], lazy:true}); ], lazy:true});
g.clear(); g.clear();
confirmRemove.render(); la.render();
} }
function askCoordinate(t1, t2, callback) { function askCoordinate(t1, t2, callback) {
let sign = 1; let sign = 1;
ask01(t1, function(sign) { ask01(t1, function(sign) {
@ -237,8 +304,27 @@ function askPosition(callback) {
}); });
} }
function createWP(lat, lon, name) {
let n = {};
n["name"] = name;
n["lat"] = lat;
n["lon"] = lon;
wp.push(n);
print("add -- waypoints", wp);
writeWP();
}
function addCardName(name) {
g.clear();
askPosition(function(lat, lon) {
print("position -- ", lat, lon);
createWP(lat, lon, result);
mainMenu();
});
}
function addCard() { function addCard() {
showNumpad("wpXX", "wp", function() { require("textinput").input({text:"wp"}).then(key => {
result = key; result = key;
if (wp[result]!=undefined) { if (wp[result]!=undefined) {
E.showMenu(); E.showMenu();
@ -247,29 +333,17 @@ function addCard() {
{type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result}, {type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result},
{type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."}, {type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
{type:"h", c: [ {type:"h", c: [
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{encodeCard(result);}}, {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{addCardName(result);}},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}} {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
]} ]}
], lazy:true}); ], lazy:true});
g.clear(); g.clear();
alreadyExists.render(); alreadyExists.render();
} }
g.clear(); addCardName(result);
askPosition(function(lat, lon) {
print("position -- ", lat, lon);
let n = {};
n["name"] = result;
n["lat"] = lat;
n["lon"] = lon;
wp.push(n);
print("add -- waypoints", wp);
writeWP();
mainMenu();
});
}); });
} }
g.reset(); g.reset();
Bangle.setUI(); Bangle.setUI();
mainMenu(); mainMenu();

View File

@ -1,11 +1,13 @@
{ "id": "waypoint_editor", { "id": "waypoint_editor",
"name": "Waypoint editor", "name": "Waypoint editor",
"version":"0.02", "version":"0.03",
"description": "Allows editing waypoints on device", "description": "Allows editing waypoints on device",
"icon": "app.png", "icon": "app.png",
"readme": "README.md", "readme": "README.md",
"supports" : ["BANGLEJS2"], "supports" : ["BANGLEJS2"],
"allow_emulator": true,
"tags": "tool,outdoors,gps", "tags": "tool,outdoors,gps",
"dependencies": {"textinput":"type"},
"storage": [ "storage": [
{"name":"waypoint_editor.app.js","url":"app.js"}, {"name":"waypoint_editor.app.js","url":"app.js"},
{"name":"waypoint_editor.img","url":"app-icon.js","evaluate":true} {"name":"waypoint_editor.img","url":"app-icon.js","evaluate":true}