Merge pull request #3 from espruino/master

update
pull/621/head
Ben Jabituya 2021-01-01 15:51:51 +00:00 committed by GitHub
commit f7ca693ff4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 374 additions and 0 deletions

View File

@ -1,9 +1,14 @@
Monitor Heart Rate Variability using the Bangle.JS
===================================================
One-time mode:
-------------
This will take a HRV measurement over a single approx 30 second period. It will also provide you with a HR reading based on the post-processing of the signal.
Continuous mode:
----------------
This will continually take measurements over 30 second periods every 3 and half minutes and log them to a CSV file on the Bangle until the watch is reset; this file can then be reviewed in Excel or other apps. The log file is reset each time you restart and select this mode to save on storage. The log file is just 1 line per each 3 minute cycle showing: timestamp, HR, HRV and sample count.
Note that in both modes, if the watch seems unresponsive, just wait for it to finish its cycle of processing the data, which shouldnt take longer than a couple of minutes.

2
apps/dtlaunch/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Initial version
0.02: Multiple pages

16
apps/dtlaunch/README.md Normal file
View File

@ -0,0 +1,16 @@
# Desktop style App Launcher
![](screenshot.jpg)
In the picture above, the Settings app is selected.
## Controls
**BTN1** - move backward through app icons on a page
**BTN2** - run the selected app
**BTN3** - move forward through app icons
**Swipe Left** - move to next page of app icons
**Swipe Right** - move to previous page of app icons

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4ATxAAQC+2N7vd7AX/C/6/7a/4X/a/4X/C/4X/C/4Xfl3iC6vu9wXtI653WAH4A/ABg"))

73
apps/dtlaunch/app.js Normal file
View File

@ -0,0 +1,73 @@
/* Desktop launcher
*
*/
var s = require("Storage");
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
apps.sort((a,b)=>{
var 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;
});
var Napps = apps.length;
var Npages = Math.ceil(Napps/6);
var maxPage = Npages-1;
var selected = -1;
var oldselected = -1;
var page = 0;
function draw_icon(p,n,selected) {
var x = (n%3)*80;
var y = n>2?130:40;
(selected?g.setColor(0.3,0.3,0.3):g.setColor(0,0,0)).fillRect(x,y,x+79,y+89);
g.drawImage(s.read(apps[p*6+n].icon),x+10,y+10,{scale:1.25});
g.setColor(-1).setFontAlign(0,-1,0).setFont("6x8",1);
var txt = apps[p*6+n].name.split(" ");
for (var i = 0; i < txt.length; i++) {
txt[i] = txt[i].trim();
g.drawString(txt[i],x+40,y+70+i*8);
}
}
function drawPage(p){
g.setColor(0,0,0).fillRect(0,0,239,239);
g.setFont("6x8",2).setFontAlign(0,-1,0).setColor(1,1,1).drawString("Bangle ("+(p+1)+"/"+Npages+")",120,12);
for (var i=0;i<6;i++) {
if (!apps[p*6+i]) return i;
draw_icon(p,i,selected==i);
}
}
Bangle.on("swipe",(dir)=>{
selected = 0;
oldselected=-1;
if (dir<0){
++page; if (page>maxPage) page=maxPage;
drawPage(page);
} else {
--page; if (page<0) page=0;
drawPage(page);
}
});
function nextapp(d){
oldselected = selected;
selected+=d;
selected = selected<0?5:selected>5?0:selected;
selected = (page*6+selected)>=Napps?0:selected;
draw_icon(page,selected,true);
if (oldselected>=0) draw_icon(page,oldselected,false);
}
function doselect(){
load(apps[page*6+selected].src);
}
setWatch(nextapp.bind(null,-1), BTN1, {repeat:true,edge:"falling"});
setWatch(doselect, BTN2, {repeat:true,edge:"falling"});
setWatch(nextapp.bind(null,1), BTN3, {repeat:true,edge:"falling"});
drawPage(0);

BIN
apps/dtlaunch/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

1
apps/hardalarm/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Add a number to match to turn off alarm

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkE5gA/AF9cBZXFQYIOGBIUMC5PATgQJFqAIBgovMBwISDAYQ5HGBAAGFxQ/FgMzmcgJ5BIKgAXFIxYuBgMxCIMjmcQgECiBHLFwITBFYIvBiBLBmQwLqECCYMziICBmIeBD4IwKFwQAIGAJGJRQUhgIbDAocQJBHAgYlCOQIrDAoUwhhGIiZZBX4gFEAgIXICIMwX4gFFC45eDF5RgI4pZHAo0gC43AXoaPJC4J4GRwQAMSA4XfmKuBC6kQRYQXMO4YACXYYADO46nDIwcCiBIFU47XDBwcTkBQFa4/MH4sAkYxBBAoWGC4I/DmQ1BdwJPEC5CQEmAECGQKOKMA0gCYRgELxBICCYUyFQZgCJgIWI5lQYIxjCGYMFC5PFLAiKDFwRGJGATCFLoYuKGAYYGiAuMDAkiCoMiCx6SCAAq7IF48F4tQCoMFqAXP4AQF4B1MSAZXFMwIXPA41VC5wA/ADAA=="))

112
apps/hardalarm/app.js Normal file
View File

@ -0,0 +1,112 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var alarms = require("Storage").readJSON("hardalarm.json",1)||[];
/*alarms = [
{ on : true,
hr : 6.5, // hours + minutes/60
msg : "Eat chocolate",
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
rp : true, // repeat
as : false, // auto snooze
}
];*/
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showMainMenu() {
const menu = {
'': { 'title': 'Alarms' },
'New Alarm': ()=>editAlarm(-1)
};
alarms.forEach((alarm,idx)=>{
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += " (repeat)";
menu[txt] = function() {
editAlarm(idx);
};
});
menu['< Back'] = ()=>{load();};
return E.showMenu(menu);
}
function editAlarm(alarmIndex) {
var newAlarm = alarmIndex<0;
var hrs = 12;
var mins = 0;
var en = true;
var repeat = true;
var as = false;
if (!newAlarm) {
var a = alarms[alarmIndex];
hrs = 0|a.hr;
mins = Math.round((a.hr-hrs)*60);
en = a.on;
repeat = a.rp;
as = a.as;
}
const menu = {
'': { 'title': 'Alarms' },
'Hours': {
value: hrs,
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Minutes': {
value: mins,
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Enabled': {
value: en,
format: v=>v?"On":"Off",
onchange: v=>en=v
},
'Repeat': {
value: en,
format: v=>v?"Yes":"No",
onchange: v=>repeat=v
},
'Auto snooze': {
value: as,
format: v=>v?"Yes":"No",
onchange: v=>as=v
}
};
function getAlarm() {
var hr = hrs+(mins/60);
var day = 0;
// If alarm is for tomorrow not today (eg, in the past), set day
if (hr < getCurrentHr())
day = (new Date()).getDate();
// Save alarm
return {
on : en, hr : hr,
last : day, rp : repeat, as: as
};
}
menu["> Save"] = function() {
if (newAlarm) alarms.push(getAlarm());
else alarms[alarmIndex] = getAlarm();
require("Storage").write("hardalarm.json",JSON.stringify(alarms));
showMainMenu();
};
if (!newAlarm) {
menu["> Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("hardalarm.json",JSON.stringify(alarms));
showMainMenu();
};
}
menu['< Back'] = showMainMenu;
return E.showMenu(menu);
}
showMainMenu();

BIN
apps/hardalarm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

25
apps/hardalarm/boot.js Normal file
View File

@ -0,0 +1,25 @@
// check for alarms
(function() {
var alarms = require('Storage').readJSON('hardalarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on);
if (active.length) {
active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("hardalarm.js")) {
console.log("No alarm app!");
require('Storage').write('hardalarm.json',"[]");
} else {
var t = 3600000*(active[0].hr-hr);
if (active[0].last == time.getDate() || t < 0) t += 86400000;
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when hardalarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("hardalarm.js");
},t);
}
}
})();

127
apps/hardalarm/hardalarm.js Normal file
View File

@ -0,0 +1,127 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(hardalarm.js)' - so let's remove it first!
clearInterval();
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function getRandomFromRange(lowerRangeMin, lowerRangeMax, higherRangeMin, higherRangeMax) {
var lowerRange = lowerRangeMax - lowerRangeMin;
var higherRange = higherRangeMax - higherRangeMin;
var fullRange = lowerRange + higherRange;
var randomNum = getRandomInt(fullRange);
if(randomNum <= (lowerRangeMax - lowerRangeMin)) {
return randomNum + lowerRangeMin;
} else {
return randomNum + (higherRangeMin - lowerRangeMax);
}
}
function showNumberPicker(currentGuess, randomNum) {
if(currentGuess == randomNum) {
E.showMessage("" + currentGuess + "\n PRESS ENTER", "Get to " + randomNum);
} else {
E.showMessage("" + currentGuess, "Get to " + randomNum);
}
}
function showPrompt(msg, buzzCount, alarm) {
E.showPrompt(msg,{
title:"STAY AWAKE!",
buttons : {"Sleep":0,"Stop":1} // default is sleep so it'll come back in 10 mins
}).then(function(choice) {
buzzCount = 0;
if (choice==0) {
if(alarm.ohr===undefined) alarm.ohr = alarm.hr;
alarm.hr += 10/60; // 10 minutes
require("Storage").write("hardalarm.json",JSON.stringify(alarms));
load();
} else if(choice==1) {
alarm.last = (new Date()).getDate();
if (alarm.ohr!==undefined) {
alarm.hr = alarm.ohr;
delete alarm.ohr;
}
if (!alarm.rp) alarm.on = false;
require("Storage").write("hardalarm.json",JSON.stringify(alarms));
load();
}
});
}
function showAlarm(alarm) {
var msg = formatTime(alarm.hr);
var buzzCount = 20;
if (alarm.msg)
msg += "\n"+alarm.msg;
var okClicked = false;
var currentGuess = 10;
var randomNum = getRandomFromRange(0, 7, 13, 20);
showNumberPicker(currentGuess, randomNum)
setWatch(o => {
if(!okClicked && currentGuess < 20) {
currentGuess = currentGuess + 1;
showNumberPicker(currentGuess, randomNum);
}
}, BTN1, {repeat: true, edge: 'rising'});
setWatch(o => {
if(currentGuess == randomNum) {
okClicked = true;
showPrompt(msg, buzzCount, alarm);
}
}, BTN2, {repeat: true, edge: 'rising'});
setWatch(o => {
if(!okClicked && currentGuess > 0) {
currentGuess = currentGuess - 1;
showNumberPicker(currentGuess, randomNum);
}
}, BTN3, {repeat: true, edge: 'rising'});
function buzz() {
Bangle.buzz(500).then(()=>{
setTimeout(()=>{
Bangle.buzz(500).then(function() {
setTimeout(()=>{
Bangle.buzz(2000).then(function() {
if (buzzCount--)
setTimeout(buzz, 2000);
else if(alarm.as) { // auto-snooze
buzzCount = 20;
setTimeout(buzz, 600000); // 10 minutes
}
});
},100);
});
},100);
});
}
buzz();
}
// Check for alarms
var day = (new Date()).getDate();
var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
var alarms = require("Storage").readJSON("hardalarm.json",1)||[];
var active = alarms.filter(a=>a.on&&(a.hr<hr)&&(a.last!=day));
if (active.length) {
// if there's an alarm, show it
active = active.sort((a,b)=>a.hr-b.hr);
showAlarm(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

11
apps/hardalarm/widget.js Normal file
View File

@ -0,0 +1,11 @@
(() => {
var alarms = require('Storage').readJSON('hardalarm.json',1)||[];
alarms = alarms.filter(alarm=>alarm.on);
if (!alarms.length) return; // no alarms, no widget!
delete alarms;
// add the widget
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
g.setColor(-1);
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
}};
})()