diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 4576237a5..e07e748d4 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -14,3 +14,4 @@
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
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
new file mode 100644
index 000000000..7bc4b7155
--- /dev/null
+++ b/apps/alarm/README.md
@@ -0,0 +1,82 @@
+Default Alarm & Timer
+======================
+
+This provides an app, widget, 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 `alarm.json`, and take the form:
+
+```
+{
+ id : "mytimer", // optional ID for this alarm/timer, so apps can easily find *their* timers
+ 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
+ msg : "Eat chocolate", // 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
+ vibrate : "...", // pattern of '.', '-' and ' ' to use for when buzzing out this alarm (defaults to '..' if not set)
+ 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 'alarm.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
+}
+```
+
+You app
+
+The [`alarm` library](https://github.com/espruino/BangleApps/blob/master/apps/alarm/lib.js) contains
+a few helpful functions for getting/setting alarms, but is intentionally sparse so as not to
+use too much RAM.
+
+It can be used as follows:
+
+```
+// add/update an existing alarm
+require("alarm").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("alarm").reload();
+
+// Get the time to the next alarm for us
+var timeToNext = require("alarm").getTimeToAlarm(require("alarm").getAlarm("mytimer"));
+// timeToNext===undefined if no alarm or alarm disabled
+
+// delete an alarm
+require("alarm").setAlarm("mytimer", undefined);
+// reload after deleting...
+require("alarm").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("alarm").setAlarm("customrunner", {
+ js : "load('setting.app.js')",
+ timer : 1*60*1000, // 1 Minute
+});
+```
+
+
+
+If your app requires alarms, you can specify that the alarms app needs to
+be installed by adding `"dependencies": {"alarm":"app"},` to your metadata.
diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js
index a655dad1e..7ec8d0b73 100644
--- a/apps/alarm/alarm.js
+++ b/apps/alarm/alarm.js
@@ -1,20 +1,38 @@
// 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);
+if (Bangle.ALARM) {
+ clearInterval(Bangle.ALARM);
+ delete Bangle.ALARM;
}
-function getCurrentHr() {
+// 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 o = decodeTime(t);
+ return o.hrs+":"+("0"+o.mins).substr(-2);
+}
+
+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 showAlarm(alarm) {
- var msg = formatTime(alarm.hr);
+ var msg = alarm.timer ? formatTime(alarm.timer) : formatTime(alarm.t);
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
@@ -26,13 +44,13 @@ function showAlarm(alarm) {
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
- if(alarm.ohr===undefined) alarm.ohr = alarm.hr;
- alarm.hr += 10/60; // 10 minutes
+ if(alarm.ot===undefined) alarm.ot = alarm.t;
+ alarm.t += 10*60*1000; // 10 minutes
} else {
alarm.last = (new Date()).getDate();
- if (alarm.ohr!==undefined) {
- alarm.hr = alarm.ohr;
- delete alarm.ohr;
+ if (alarm.ot!==undefined) {
+ alarm.t = alarm.ot;
+ delete alarm.ot;
}
if (!alarm.rp) alarm.on = false;
}
@@ -40,31 +58,27 @@ function showAlarm(alarm) {
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);
+ 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 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 currentTime = getCurrentTime()+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
a.on&&(a.ta.hr-b.hr);
+ active = active.sort((a,b)=>a.t-b.t);
showAlarm(active[0]);
} else {
// otherwise just go back to default app
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index 56184edf1..e83e1c3d5 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -2,35 +2,42 @@ 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
- }
-];*/
+// An array of alarm objects (see 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("Storage").write("alarm.json",JSON.stringify(alarms));
+ require("alarm").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< v ? "Yes" : "No",
+ onchange: v => v ? dow |= 1< 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< 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();
};
}
diff --git a/apps/alarm/boot.js b/apps/alarm/boot.js
index 47dae5361..cb3a82a7e 100644
--- a/apps/alarm/boot.js
+++ b/apps/alarm/boot.js
@@ -1,25 +1,32 @@
// check for alarms
(function() {
+ if (Bangle.ALARM) {
+ clearTimeout(Bangle.ALARM);
+ delete Bangle.ALARM;
+ }
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
- var active = alarms.filter(a=>a.on);
+ var active = alarms.filter(a=>a.on && (a.dow>>time.getDay())&1);
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);
+ 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);
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;
+ 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. */
- setTimeout(function() {
- load("alarm.js");
- },t);
+ normally.
+ If active[0].js is defined, just run that code as-is and not alarm.js */
+ Bangle.ALARM = setTimeout(active[0].js||'load("alarm.js")',t);
}
+ } else { // check for new alarms at midnight (so day of week works)
+ Bangle.ALARM = setTimeout(() => {
+ eval(require("Storage").read("alarm.boot.js"));
+ }, 86400000 - (Date.now()%86400000));
}
})();
diff --git a/apps/alarm/lib.js b/apps/alarm/lib.js
new file mode 100644
index 000000000..a0d8b3938
--- /dev/null
+++ b/apps/alarm/lib.js
@@ -0,0 +1,45 @@
+// Return an array of all alarms
+exports.getAlarms = function() {
+ return require("Storage").readJSON("alarm.json",1)||[];
+};
+// Return an alarm object based on ID
+exports.getAlarm = function(id) {
+ var alarms = require("Storage").readJSON("alarm.json",1)||[];
+ return alarms.find(a=>a.id==id);
+};
+// Set an alarm object based on ID. Leave 'alarm' undefined to remove it
+exports.setAlarm = function(id, alarm) {
+ var alarms = require("Storage").readJSON("alarm.json",1)||[];
+ alarms = alarms.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);
+ require("Storage").writeJSON("alarm.json", 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;
+ 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("alarm.boot.js"));
+ if (WIDGETS["alarm"]) {
+ WIDGETS["alarm"].reload();
+ Bangle.drawWidgets();
+ }
+};
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index d29298309..c10f64a5c 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,17 +2,19 @@
"id": "alarm",
"name": "Default Alarm & Timer",
"shortName": "Alarms",
- "version": "0.15",
+ "version": "0.16",
"description": "Set and respond to alarms and timers",
"icon": "app.png",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
"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"}
+ {"name":"alarm.wid.js","url":"widget.js"},
+ {"name":"alarm","url":"lib.js"}
],
"data": [{"name":"alarm.json"}]
}
diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js
index e1dec5ea2..54c17dc6d 100644
--- a/apps/alarm/widget.js
+++ b/apps/alarm/widget.js
@@ -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;
-}
+ 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() {
+ // don't include library here as we're trying to use as little RAM as possible
+ WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
+ }
};
WIDGETS["alarm"].reload();
diff --git a/apps/messages/settings.js b/apps/messages/settings.js
index 589d603da..754347f19 100644
--- a/apps/messages/settings.js
+++ b/apps/messages/settings.js
@@ -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,
diff --git a/apps/messages/widget.js b/apps/messages/widget.js
index 7abb415c3..3ac726e77 100644
--- a/apps/messages/widget.js
+++ b/apps/messages/widget.js
@@ -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.xw.x+w.width||c.yw.y+w.iconwidth) return;
diff --git a/modules/buzz.js b/modules/buzz.js
new file mode 100644
index 000000000..488d0228d
--- /dev/null
+++ b/modules/buzz.js
@@ -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();
+});
diff --git a/modules/buzz_menu.js b/modules/buzz_menu.js
new file mode 100644
index 000000000..64b225343
--- /dev/null
+++ b/modules/buzz_menu.js
@@ -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]);
+ }
+ };
+}