1
0
Fork 0

Merge branch 'new_alarm'

master
Gordon Williams 2022-04-06 11:45:22 +01:00
commit 90bf384355
20 changed files with 434 additions and 231 deletions

View File

@ -14,3 +14,5 @@
0.13: Alarm widget state now updates when setting/resetting an alarm
0.14: Order of 'back' menu item
0.15: Fix hour/minute wrapping code for new menu system
0.16: Adding alarm library
0.17: Moving alarm internals to 'sched' library

6
apps/alarm/README.md Normal file
View File

@ -0,0 +1,6 @@
Default Alarm & Timer
======================
This allows you to add/modify any running timers.
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.

View File

@ -1,72 +0,0 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(alarm.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 showAlarm(alarm) {
var msg = formatTime(alarm.hr);
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showPrompt(msg,{
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
if(alarm.ohr===undefined) alarm.ohr = alarm.hr;
alarm.hr += 10/60; // 10 minutes
} else {
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("alarm.json",JSON.stringify(alarms));
load();
});
function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
Bangle.buzz(100).then(()=>{
setTimeout(()=>{
Bangle.buzz(100).then(function() {
if (buzzCount--)
setTimeout(buzz, 3000);
else if(alarm.as) { // auto-snooze
buzzCount = 10;
setTimeout(buzz, 600000);
}
});
},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("alarm.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);
}

View File

@ -1,36 +1,43 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var alarms = require("Storage").readJSON("alarm.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
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
}
];*/
var alarms = require("sched").getAlarms();
// An array of alarm objects (see sched/README.md)
// time in ms -> { hrs, mins }
function decodeTime(t) {
t = 0|t; // sanitise
var hrs = 0|(t/3600000);
return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
}
// time in { hrs, mins } -> ms
function encodeTime(o) {
return o.hrs*3600000 + o.mins*60000;
}
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
var o = decodeTime(t);
return o.hrs+":"+("0"+o.mins).substr(-2);
}
function formatMins(t) {
mins = (0|t)%60;
hrs = 0|(t/60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
function getCurrentTime() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
return (
time.getHours() * 3600000 +
time.getMinutes() * 60000 +
time.getSeconds() * 1000
);
}
function saveAndReload() {
require("sched").setAlarms(alarms);
require("sched").reload();
}
function showMainMenu() {
// Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
'': { 'title': 'Alarm/Timer' },
/*LANG*/'< Back' : ()=>{load();},
@ -38,140 +45,151 @@ function showMainMenu() {
/*LANG*/'New Timer': ()=>editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
if (alarm.timer) {
txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
} else {
txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += /*LANG*/" (repeat)";
}
menu[txt] = function() {
if (alarm.timer) editTimer(idx);
else editAlarm(idx);
var txt; // a leading space is currently required (JS error in Espruino 2v12)
if (alarm.timer)
txt = /*LANG*/"Timer"+" "+formatTime(alarm.timer);
else
txt = /*LANG*/"Alarm"+" "+formatTime(alarm.t);
if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
menu[txt] = {
value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
onchange : function() {
if (alarm.timer) editTimer(idx, alarm);
else editAlarm(idx, alarm);
}
};
});
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
}
function editAlarm(alarmIndex) {
function editDOW(dow, onchange) {
const menu = {
'': { 'title': /*LANG*/'Days of Week' },
'< Back' : () => onchange(dow)
};
for (var i = 0; i < 7; i++) (i => {
var dayOfWeek = require("locale").dow({ getDay: () => i });
menu[dayOfWeek] = {
value: !!(dow&(1<<i)),
format: v => v ? "Yes" : "No",
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
};
})(i);
E.showMenu(menu);
}
function editAlarm(alarmIndex, alarm) {
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;
var a = {
t : 12*3600000, // 12 o clock default
on : true,
rp : true,
as : false,
dow : 0b1111111,
last : 0,
vibrate : ".."
}
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
var t = decodeTime(a.t);
const menu = {
'': { 'title': /*LANG*/'Alarm' },
/*LANG*/'< Back' : showMainMenu,
'< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
value: hrs, min : 0, max : 23, wrap : true,
onchange: v => hrs=v
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
value: mins, min : 0, max : 59, wrap : true,
onchange: v => mins=v
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
value: en,
value: a.on,
format: v=>v?"On":"Off",
onchange: v=>en=v
onchange: v=>a.on=v
},
/*LANG*/'Repeat': {
value: en,
value: a.rp,
format: v=>v?"Yes":"No",
onchange: v=>repeat=v
onchange: v=>a.rp=v
},
/*LANG*/'Days': {
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
onchange: () => editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)})
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
/*LANG*/'Auto snooze': {
value: as,
value: a.as,
format: v=>v?"Yes":"No",
onchange: v=>as=v
onchange: v=>a.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[/*LANG*/"> Save"] = function() {
if (newAlarm) alarms.push(getAlarm());
else alarms[alarmIndex] = getAlarm();
require("Storage").write("alarm.json",JSON.stringify(alarms));
menu[/*LANG*/"Save"] = function() {
a.t = encodeTime(t);
if (a.t < getCurrentTime())
a.day = (new Date()).getDate();
if (newAlarm) alarms.push(a);
else alarms[alarmIndex] = a;
saveAndReload();
showMainMenu();
};
if (!newAlarm) {
menu[/*LANG*/"> Delete"] = function() {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
function editTimer(alarmIndex) {
function editTimer(alarmIndex, alarm) {
var newAlarm = alarmIndex<0;
var hrs = 0;
var mins = 5;
var en = true;
if (!newAlarm) {
var a = alarms[alarmIndex];
mins = (0|a.timer)%60;
hrs = 0|(a.timer/60);
en = a.on;
var a = {
timer : 5*60*1000, // 5 minutes
on : true,
rp : false,
as : false,
dow : 0b1111111,
last : 0,
vibrate : ".."
}
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
var t = decodeTime(a.timer);
const menu = {
'': { 'title': /*LANG*/'Timer' },
'< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
value: hrs, min : 0, max : 23, wrap : true,
onchange: v => hrs=v
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
value: mins, min : 0, max : 59, wrap : true,
onchange: v => mins=v
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
value: en,
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
onchange: v=>en=v
}
value: a.on,
format: v=>v?"On":"Off",
onchange: v=>a.on=v
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
};
function getTimer() {
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
// Save alarm
return {
on : en,
timer : (hrs*60)+mins,
hr : hr,
rp : false, as: false
};
}
menu["> Save"] = function() {
if (newAlarm) alarms.push(getTimer());
else alarms[alarmIndex] = getTimer();
require("Storage").write("alarm.json",JSON.stringify(alarms));
menu[/*LANG*/"Save"] = function() {
a.timer = encodeTime(t);
a.t = getCurrentTime() + a.timer;
if (newAlarm) alarms.push(a);
else alarms[alarmIndex] = a;
saveAndReload();
showMainMenu();
};
if (!newAlarm) {
menu["> Delete"] = function() {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
saveAndReload();
showMainMenu();
};
}

View File

@ -1,25 +0,0 @@
// check for alarms
(function() {
var alarms = require('Storage').readJSON('alarm.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("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.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 alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
})();

View File

@ -1,18 +1,17 @@
{
"id": "alarm",
"name": "Default Alarm & Timer",
"name": "Alarm & Timer",
"shortName": "Alarms",
"version": "0.15",
"description": "Set and respond to alarms and timers",
"version": "0.17",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"dependencies": {"scheduler":"type"},
"storage": [
{"name":"alarm.app.js","url":"app.js"},
{"name":"alarm.boot.js","url":"boot.js"},
{"name":"alarm.js","url":"alarm.js"},
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
{"name":"alarm.wid.js","url":"widget.js"}
],
"data": [{"name":"alarm.json"}]
]
}

View File

@ -1,7 +1,8 @@
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
// don't include library here as we're trying to use as little RAM as possible
WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();

View File

@ -15,18 +15,10 @@
require('Storage').writeJSON("messages.settings.json", settings);
}
var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"];
var mainmenu = {
"" : { "title" : /*LANG*/"Messages" },
"< Back" : back,
/*LANG*/'Vibrate': {
value: Math.max(0,vibPatterns.indexOf(settings().vibrate)),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off",
onchange: v => {
updateSetting("vibrate", vibPatterns[v]);
}
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(settings().vibrate, v => updateSetting("vibrate", v)),
/*LANG*/'Repeat': {
value: settings().repeat,
min: 0, max: 10,

View File

@ -32,14 +32,7 @@ draw:function() {
Bangle.drawWidgets();
},buzz:function() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode
let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
function b() {
var c = v[0];
v = v.substr(1);
if (c==".") Bangle.buzz().then(()=>setTimeout(b,100));
if (c=="-") Bangle.buzz(500).then(()=>setTimeout(b,100));
}
b();
require("buzz").pattern((require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".");
},touch:function(b,c) {
var w=WIDGETS["messages"];
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+w.iconwidth) return;

View File

@ -4,7 +4,7 @@
"shortName": "Q Alarm",
"icon": "app.png",
"version": "0.04",
"description": "Alarm and timer app with days of week and 'hard' option.",
"description": "[Not recommended - use 'Alarm & Timer' app] Alarm and timer app with days of week and 'hard' option.",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [

1
apps/sched/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

89
apps/sched/README.md Normal file
View File

@ -0,0 +1,89 @@
Sched: Scheduling library for alarms and timers
====================================
This provides boot code, a library and tools for alarms and timers.
Other apps can use this to provide alarm functionality.
App
---
The Alarm app allows you to add/modify any running timers.
Internals / Library
-------------------
Alarms are stored in an array in `sched.json`, and take the form:
```
{
id : "mytimer", // optional ID for this alarm/timer, so apps can easily find *their* timers
appid : "myappid", // optional app ID for alarms that you set/use for your app
on : true, // is the alarm enabled?
t : 23400000, // Time of day since midnight in ms (if a timer, this is set automatically when timer starts)
dow : 0b1111111, // Binary encoding for days of the week to run alarm on
// SUN = 1
// MON = 2
// TUE = 4
// WED = 8
// THU = 16
// FRI = 32
// SAT = 64
date : "2022-04-04", // OPTIONAL date for the alarm, in YYYY-MM-DD format
// eg (new Date()).toISOString().substr(0,10)
msg : "Eat food", // message to display.
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
rp : true, // repeat the alarm every day?
vibrate : "...", // OPTIONAL pattern of '.', '-' and ' ' to use for when buzzing out this alarm (defaults to '..' if not set)
hidden : false, // OPTIONAL if false, the widget should not show an icon for this alarm
as : false, // auto snooze
timer : 5*60*1000, // OPTIONAL - if set, this is a timer and it's the time in ms
js : "load('myapp.js')" // OPTIONAL - a JS command to execute when the alarm activates (*instead* of loading 'sched.js')
// when this code is run, you're responsible for setting alarm.on=false (or removing the alarm)
data : { ... } // OPTIONAL - your app can store custom data in here if needed (don't store a lot of data here)
}
```
The [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched/lib.js) contains
a few helpful functions for getting/setting alarms and timers, but is intentionally sparse so as not to
use too much RAM.
It can be used as follows:
```
// add/update an existing alarm
require("sched").setAlarm("mytimer", {
msg : "Wake up",
timer : 10*60*1000, // 10 Minutes
});
// Ensure the widget and alarm timer updates to schedule the new alarm properly
require("sched").reload();
// Get the time to the next alarm for us
var timeToNext = require("sched").getTimeToAlarm(require("sched").getAlarm("mytimer"));
// timeToNext===undefined if no alarm or alarm disabled
// delete an alarm
require("sched").setAlarm("mytimer", undefined);
// reload after deleting...
require("sched").reload();
// Or add an alarm that runs your own code - in this case
// loading the settings app. The alarm will not be removed/stopped
// automatically.
require("sched").setAlarm("customrunner", {
appid : "myapp",
js : "load('setting.app.js')",
timer : 1*60*1000, // 1 Minute
});
// If you have been specifying `appid` you can also find any alarms that
// your app has created with the following:
require("sched").getAlarms().filter(a=>a.appid=="myapp");
```
If your app requires alarms, you can specify that the alarms app needs to
be installed by adding `"dependencies": {"scheduler":"type"},` to your app's
metadata.

1
apps/sched/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkGswAhiMRCCAREAo4eHBIQLEAgwYHsIJDiwHB5gACBpIhHCoYZEGA4gFCw4ABGA4HEjgXJ4IXGAwcUB4VEmf//8zogICoJIFAodMBoNDCoIADmgJB4gXIFwXDCwoABngwFC4guB4k/CQXwh4EC+YMCC44iBp4qDC4n/+gNBC41sEIJCEC4v/GAPGC4dhXYRdFC4xhCCYIXCdQRdDC5HzegQXCsxGHC45IDCwQXCUgwXHJAIXGRogXJSIIXcOw4XIPAYXcBwv/mEDBAwXOgtQC65QGC5vzoEAJAx3Nmk/mEABIiPN+dDAQIwFC4zXGFwKRCGAjvMFwQECGAgXI4YuGGAUvAgU8C4/EFwwGCAgdMC4p4EFwobFOwoXDJAIoEAApGBC4xIEABJGHGAapEAAqNBFwwXD4heI+YuBC5BIBVQhdHIw4wD5inFS4IKCCxFmigNCokzCoMzogICoIWIsMRjgPCAA3BiMWC48RBQIXJEgMRFxAJCCw4lEC44IECooOIBAaBJKwhgIAH4ACA=="))

BIN
apps/sched/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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

@ -0,0 +1,25 @@
// check for alarms
(function() { // run in closure to ensure allocated vars get removed
if (Bangle.SCHED) {
clearTimeout(Bangle.SCHED);
delete Bangle.SCHED;
}
var alarms = require('Storage').readJSON('sched.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on && (a.dow>>time.getDay())&1 && (!a.date || a.date==time.toISOString().substr(0,10)));
if (active.length) {
active = active.sort((a,b)=>(a.t-b.t)+(a.last-b.last)*86400000);
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
var t = active[0].t-currentTime;
if (active[0].last == time.getDate() || t < -60000) t += 86400000;
if (t<1000) t=1000; // start alarm min 1 sec from now
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally.
If active[0].js is defined, just run that code as-is and not alarm.js */
Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t);
} else { // check for new alarms at midnight (so day of week works)
Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000));
}
})();

54
apps/sched/lib.js Normal file
View File

@ -0,0 +1,54 @@
// Return an array of all alarms
exports.getAlarms = function() {
return require("Storage").readJSON("sched.json",1)||[];
};
// Write a list of alarms back to storage
exports.setAlarms = function(alarms) {
return require("Storage").writeJSON("sched.json",alarms);
};
// Return an alarm object based on ID
exports.getAlarm = function(id) {
return exports.getAlarms().find(a=>a.id==id);
};
// Given a list of alarms from getAlarms, return a list of active alarms for the given time (or current time if time not specified)
exports.getActiveAlarms = function(alarms, time) {
if (!time) time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000)
+10000;// get current time - 10s in future to ensure we alarm if we've started the app a tad early
return alarms.filter(a=>a.on&&(a.t<currentTime)&&(a.last!=time.getDate()) && (!a.date || a.date==time.toISOString().substr(0,10))).sort((a,b)=>a.t-b.t);
}
// Set an alarm object based on ID. Leave 'alarm' undefined to remove it
exports.setAlarm = function(id, alarm) {
var alarms = exports.getAlarms().filter(a=>a.id!=id);
if (alarm !== undefined) {
alarm.id = id;
if (alarm.dow===undefined) alarm.dow = 0b1111111;
if (alarm.on!==false) alarm.on=true;
if (alarm.timer) { // if it's a timer, set the start time as a time from *now*
var time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
alarm.t = currentTime + alarm.timer;
}
alarms.push(alarm);
}
exports.setAlarms(alarms);
};
/// Get time until the given alarm (object). Return undefined if alarm not enabled, or if 86400000 or more, alarm could me *more* than a day in the future
exports.getTimeToAlarm = function(alarm, time) {
if (!alarm) return undefined;
if (!time) time = new Date();
var active = alarm.on && (alarm.dow>>time.getDay())&1 && (!alarm.date || alarm.date==time.toISOString().substr(0,10));
if (!active) return undefined;
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
var t = alarm.t-currentTime;
if (alarm.last == time.getDate() || t < -60000) t += 86400000;
return t;
};
/// Force a reload of the current alarms and widget
exports.reload = function() {
eval(require("Storage").read("sched.boot.js"));
if (WIDGETS["alarm"]) {
WIDGETS["alarm"].reload();
Bangle.drawWidgets();
}
};

18
apps/sched/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "sched",
"name": "Scheduler",
"version": "0.01",
"description": "Scheduling library for alarms and timers",
"icon": "app.png",
"type": "scheduler",
"tags": "tool,system,alarm",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"sched.boot.js","url":"boot.js"},
{"name":"sched.js","url":"sched.js"},
{"name":"sched.img","url":"app-icon.js","evaluate":true},
{"name":"sched","url":"lib.js"}
],
"data": [{"name":"sched.json"}]
}

74
apps/sched/sched.js Normal file
View File

@ -0,0 +1,74 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(sched.js)' - so let's remove it first!
if (Bangle.SCHED) {
clearInterval(Bangle.SCHED);
delete Bangle.SCHED;
}
// time in ms -> { hrs, mins }
function decodeTime(t) {
t = 0|t; // sanitise
var hrs = 0|(t/3600000);
return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
}
function formatTime(t) {
var o = decodeTime(t);
return o.hrs+":"+("0"+o.mins).substr(-2);
}
function showAlarm(alarm) {
var msg = "";
msg += alarm.timer ? formatTime(alarm.timer) : formatTime(alarm.t);
if (alarm.msg) {
msg += "\n"+alarm.msg;
} else {
if (alarm.timer)
msg = atob("ACQswgD//33vRcGHIQAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAAAP/wAAAAAAAAAP/wAAAAAAAAAqqoAPAAAAAAqqqqoP8AAAAKqqqqqv/AAACqqqqqqq/wAAKqqqlWqqvwAAqqqqlVaqrAACqqqqlVVqqAAKqqqqlVVaqgAKqaqqlVVWqgAqpWqqlVVVqoAqlWqqlVVVaoCqlV6qlVVVaqCqVVfqlVVVWqCqVVf6lVVVWqKpVVX/lVVVVqqpVVV/+VVVVqqpVVV//lVVVqqpVVVfr1VVVqqpVVVfr1VVVqqpVVVb/lVVVqqpVVVW+VVVVqqpVVVVVVVVVqiqVVVVVVVVWqCqVVVVVVVVWqCqlVVVVVVVaqAqlVVVVVVVaoAqpVVVVVVVqoAKqVVVVVVWqgAKqlVVVVVaqgACqpVVVVVqqAAAqqlVVVaqoAAAKqqVVWqqgAAACqqqqqqqAAAAAKqqqqqgAAAAAAqqqqoAAAAAAAAqqoAAAAA==")+" "+msg;
else
msg = atob("AC0swgF97///RcEpMlVVVVVVf9VVVVVVVVX/9VVf9VVf/1VVV///1Vf9VX///VVX///VWqqlV///1Vf//9aqqqqpf//9V///2qqqqqqn///V///6qqqqqqr///X//+qqoAAKqqv//3//6qoAAAAKqr//3//qqAAAAAAqq//3/+qoAADwAAKqv/3/+qgAADwAACqv/3/aqAAADwAAAqp/19qoAAADwAAAKqfV1qgAAADwAAACqXVWqgAAADwAAACqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAAOsAAAAKpVaoAAAAOsAAAAKpVaoAAAAL/AAAAKpVaoAAAAgPwAAAKpVaoAAACAD8AAAKpVWqAAAIAA/AAAqlVWqAAAgAAPwAAqlVWqAACAAADwAAqlVWqgAIAAAAAACqlVVqgAgAAAAAACqVVVqoAAAAAAAAKqVVVaqAAAAAAAAqpVVVWqgAAAAAACqlVVVWqoAAAAAAKqlVVVVqqAAAAAAqqVVVVVaqoAAAAKqpVVVVVeqqoAAKqqtVVVVV/6qqqqqqr/VVVVX/2qqqqqqn/1VVVf/VaqqqqpV/9VVVf9VVWqqlVVf9VVVf1VVVVVVVVX9VQ==")+" "+msg;
}
Bangle.loadWidgets();
Bangle.drawWidgets();
var buzzCount = 10;
E.showPrompt(msg,{
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
buttons : {/*LANG*/"Snooze":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
if(alarm.ot===undefined) alarm.ot = alarm.t;
alarm.t += 10*60*1000; // 10 minutes
} else {
alarm.last = (new Date()).getDate();
if (alarm.ot!==undefined) {
alarm.t = alarm.ot;
delete alarm.ot;
}
if (!alarm.rp) alarm.on = false;
}
// alarm is still a member of 'alarms', so writing to array writes changes back directly
require("sched").setAlarms(alarms);
load();
});
function buzz() {
require("buzz").pattern(alarm.vibrate===undefined?"..":alarm.vibrate).then(function() {
if (buzzCount--)
setTimeout(buzz, 3000);
else if(alarm.as) { // auto-snooze
buzzCount = 10;
setTimeout(buzz, 600000);
}
});
}
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return;
buzz();
}
// Check for alarms
var alarms = require("sched").getAlarms();
var active = require("sched").getActiveAlarms(alarms);
if (active.length) // if there's an alarm, show it
showAlarm(active[0]);
else // otherwise just go back to default app
setTimeout(load, 100);

14
modules/buzz.js Normal file
View File

@ -0,0 +1,14 @@
/* Call this with a pattern like '.-.', '.. .' or '..' to buzz that pattern
out on the internal vibration motor. use buzz_menu to display a menu
where the patterns can be chosen. */
exports.pattern = pattern => new Promise(resolve => {
function b() {
if (pattern=="") resolve();
var c = pattern[0];
pattern = pattern.substr(1);
if (c==".") Bangle.buzz().then(()=>setTimeout(b,100));
else if (c=="-") Bangle.buzz(500).then(()=>setTimeout(b,100));
else setTimeout(b,100);
}
b();
});

13
modules/buzz_menu.js Normal file
View File

@ -0,0 +1,13 @@
/* Display a menu to select from various vibration patterns for use with buzz.js */
exports.pattern = function(value, callback) {
var vibPatterns = ["", ".", "..", "-", "--", "-.-", "---"];
return {
value: Math.max(0,vibPatterns.indexOf(value)),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
callback(vibPatterns[v]);
}
};
}