Merge branch 'master' of github.com:espruino/BangleApps
|
|
@ -38,3 +38,6 @@
|
||||||
0.35: Add automatic translation of more strings
|
0.35: Add automatic translation of more strings
|
||||||
0.36: alarm widget moved out of app
|
0.36: alarm widget moved out of app
|
||||||
0.37: add message input and dated Events
|
0.37: add message input and dated Events
|
||||||
|
0.38: Display date in locale
|
||||||
|
When switching 'repeat' from 'Workdays', 'Weekends' to 'Custom' preset Custom menu with previous selection
|
||||||
|
Display alarm label in delete prompt
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,14 @@ function handleFirstDayOfWeek(dow) {
|
||||||
// Check the first day of week and update the dow field accordingly (alarms only!)
|
// Check the first day of week and update the dow field accordingly (alarms only!)
|
||||||
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
|
|
||||||
|
function getLabel(e) {
|
||||||
|
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
|
||||||
|
return (e.timer
|
||||||
|
? require("time_utils").formatDuration(e.timer)
|
||||||
|
: (dateStr ? `${dateStr} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
|
||||||
|
) + (e.msg ? " " + e.msg : "");
|
||||||
|
}
|
||||||
|
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": /*LANG*/"Alarms & Timers" },
|
"": { "title": /*LANG*/"Alarms & Timers" },
|
||||||
|
|
@ -48,11 +56,7 @@ function showMainMenu() {
|
||||||
};
|
};
|
||||||
|
|
||||||
alarms.forEach((e, index) => {
|
alarms.forEach((e, index) => {
|
||||||
var label = (e.timer
|
menu[getLabel(e)] = {
|
||||||
? require("time_utils").formatDuration(e.timer)
|
|
||||||
: (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
|
|
||||||
) + (e.msg ? " " + e.msg : "");
|
|
||||||
menu[label] = {
|
|
||||||
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||||
};
|
};
|
||||||
|
|
@ -184,7 +188,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
menu[/*LANG*/"Delete"] = () => {
|
menu[/*LANG*/"Delete"] = () => {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
E.showPrompt(getLabel(alarm) + "\n" + /*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
alarms.splice(alarmIndex, 1);
|
alarms.splice(alarmIndex, 1);
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
|
|
@ -264,7 +268,7 @@ function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
||||||
},
|
},
|
||||||
/*LANG*/"Custom": {
|
/*LANG*/"Custom": {
|
||||||
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
|
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
|
||||||
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
|
onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, originalRepeat, originalDow)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -372,7 +376,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
menu[/*LANG*/"Delete"] = () => {
|
menu[/*LANG*/"Delete"] = () => {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
E.showPrompt(getLabel(timer) + "\n" + /*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
alarms.splice(timerIndex, 1);
|
alarms.splice(timerIndex, 1);
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.37",
|
"version": "0.38",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm",
|
"tags": "tool,alarm",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ sub-items simply swipe up/down. To run an action (e.g. trigger home assistant),
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
Note: Check out the settings to change different themes.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: First version
|
0.01: First version
|
||||||
0.02: Support BangleJS2
|
0.02: Support BangleJS2
|
||||||
|
0.03: Added threshold
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ The first stage of charging Li-ion ends at ~80% capacity when the charge voltage
|
||||||
|
|
||||||
This app has no UI and no configuration. To disable the app, you have to uninstall it.
|
This app has no UI and no configuration. To disable the app, you have to uninstall it.
|
||||||
|
|
||||||
|
New in v0.03: before the very first buzz, the average value after the peak is written to chargent.json and used as threshold for future charges. This reduces the time spent in the second charge stage.
|
||||||
|
|
||||||
Side notes
|
Side notes
|
||||||
- Full capacity is reached after charge current drops to an insignificant level. This is quite some time after charge voltage reached its peak / `E.getBattery()` returns 100.
|
- Full capacity is reached after charge current drops to an insignificant level. This is quite some time after charge voltage reached its peak / `E.getBattery()` returns 100.
|
||||||
- This app starts buzzing some time after `E.getBattery()` returns 100 (~15min on my watch), and at least 5min after the peak to account for noise.
|
- This app starts buzzing some time after `E.getBattery()` returns 100 (~15min on my watch), and at least 5min after the peak to account for noise.
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,27 @@
|
||||||
if (charging) {
|
if (charging) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
var max = 0;
|
var max = 0;
|
||||||
var count = 0;
|
var cnt = 0;
|
||||||
|
var sum = 0;
|
||||||
|
var lim = (require('Storage').readJSON('chargent.json', true) || {}).limit || 0;
|
||||||
id = setInterval(() => {
|
id = setInterval(() => {
|
||||||
var battlvl = analogRead(pin);
|
var val = analogRead(pin);
|
||||||
if (max < battlvl) {
|
if (max < val) {
|
||||||
max = battlvl;
|
max = val;
|
||||||
count = 0;
|
cnt = 1;
|
||||||
|
sum = val;
|
||||||
} else {
|
} else {
|
||||||
count++;
|
cnt++;
|
||||||
if (10 <= count) { // 10 * 30s == 5 min // TODO ? customizable
|
sum += val;
|
||||||
// TODO ? customizable
|
}
|
||||||
Bangle.buzz(500);
|
if (10 < cnt || (lim && lim <= max)) { // 10 * 30s == 5 min // TODO ? customizable
|
||||||
setTimeout(() => Bangle.buzz(500), 1000);
|
if (!lim) {
|
||||||
|
lim = sum / cnt;
|
||||||
|
require('Storage').writeJSON('chargent.json', {limit: lim});
|
||||||
}
|
}
|
||||||
|
// TODO ? customizable
|
||||||
|
Bangle.buzz(500);
|
||||||
|
setTimeout(() => Bangle.buzz(500), 1000);
|
||||||
}
|
}
|
||||||
}, 30*1000);
|
}, 30*1000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "chargent",
|
{ "id": "chargent",
|
||||||
"name": "Charge Gently",
|
"name": "Charge Gently",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
@ -9,5 +9,8 @@
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name": "chargent.boot.js", "url": "boot.js"}
|
{"name": "chargent.boot.js", "url": "boot.js"}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name": "chargent.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: First release
|
0.01: First release
|
||||||
0.02: Move translations to locale module (removed watch settings, now pick language in Bangle App Loader, More..., Settings)
|
0.02: Move translations to locale module (removed watch settings, now pick language in Bangle App Loader, More..., Settings)
|
||||||
0.03: Change for fast loading, use widget_utils to hide widgets
|
0.03: Change for fast loading, use widget_utils to hide widgets
|
||||||
|
0.04: Add animation when display changes
|
||||||
|
|
@ -16,7 +16,6 @@ Most translations are taken from the original Fuzzy Text International code.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
* Bold hour word (as the pebble version has)
|
* Bold hour word (as the pebble version has)
|
||||||
* Animation when changing time?
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
Based on Pebble app Fuzzy Text International: https://github.com/hallettj/Fuzzy-Text-International
|
Based on Pebble app Fuzzy Text International: https://github.com/hallettj/Fuzzy-Text-International
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,17 @@
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
let text_scale = 3.5;
|
let text_scale = 4;
|
||||||
let timeout = 2.5*60;
|
let timeout = 2.5*60;
|
||||||
let drawTimeout;
|
let drawTimeout;
|
||||||
|
let animInterval;
|
||||||
|
let time_string = "";
|
||||||
|
let time_string_old = "";
|
||||||
|
let time_string_old_wrapped = "";
|
||||||
|
|
||||||
let loadSettings = function() {
|
let loadSettings = function() {
|
||||||
settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'showWidgets': false};
|
settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'showWidgets': false};
|
||||||
}
|
};
|
||||||
|
|
||||||
let queueDraw = function(seconds) {
|
let queueDraw = function(seconds) {
|
||||||
let millisecs = seconds * 1000;
|
let millisecs = seconds * 1000;
|
||||||
|
|
@ -48,10 +52,7 @@ let queueDraw = function(seconds) {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
draw();
|
draw();
|
||||||
}, millisecs - (Date.now() % millisecs));
|
}, millisecs - (Date.now() % millisecs));
|
||||||
}
|
};
|
||||||
|
|
||||||
const h = g.getHeight();
|
|
||||||
const w = g.getWidth();
|
|
||||||
|
|
||||||
let getTimeString = function(date) {
|
let getTimeString = function(date) {
|
||||||
let segment = Math.round((date.getMinutes()*60 + date.getSeconds() + 1)/300);
|
let segment = Math.round((date.getMinutes()*60 + date.getSeconds() + 1)/300);
|
||||||
|
|
@ -63,18 +64,47 @@ let getTimeString = function(date) {
|
||||||
f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 12]);
|
f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 12]);
|
||||||
}
|
}
|
||||||
return f_string;
|
return f_string;
|
||||||
}
|
};
|
||||||
|
|
||||||
let draw = function() {
|
let draw = function() {
|
||||||
let time_string = getTimeString(new Date()).replace('*', '');
|
time_string = getTimeString(new Date()).replace('*', '');
|
||||||
// print(time_string);
|
//print(time_string);
|
||||||
g.setFont('Vector', (h-24*2)/text_scale);
|
if (time_string != time_string_old) {
|
||||||
g.setFontAlign(0, 0);
|
g.setFont('Vector', R.h/text_scale).setFontAlign(0, 0);
|
||||||
g.clearRect(0, 24, w, h-24);
|
animate(3);
|
||||||
g.setColor(g.theme.fg);
|
}
|
||||||
g.drawString(g.wrapString(time_string, w).join("\n"), w/2, h/2);
|
|
||||||
queueDraw(timeout);
|
queueDraw(timeout);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let animate = function(step) {
|
||||||
|
if (animInterval) clearInterval(animInterval);
|
||||||
|
let time_string_new_wrapped = g.wrapString(time_string, R.w).join("\n");
|
||||||
|
slideX = 0;
|
||||||
|
animInterval = setInterval(function() {
|
||||||
|
let time_start = getTime()
|
||||||
|
//blank old time
|
||||||
|
g.setColor(g.theme.bg);
|
||||||
|
g.drawString(time_string_old_wrapped, R.x + R.w/2 + slideX, R.y + R.h/2);
|
||||||
|
g.drawString(time_string_new_wrapped, R.x - R.w/2 + slideX, R.y + R.h/2);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
slideX += step;
|
||||||
|
let stop = false;
|
||||||
|
if (slideX>=R.w) {
|
||||||
|
slideX=R.w;
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
//draw shifted new time
|
||||||
|
g.drawString(time_string_old_wrapped, R.x + R.w/2 + slideX, R.y + R.h/2);
|
||||||
|
g.drawString(time_string_new_wrapped, R.x - R.w/2 + slideX, R.y + R.h/2);
|
||||||
|
if (stop) {
|
||||||
|
time_string_old = time_string;
|
||||||
|
clearInterval(animInterval);
|
||||||
|
animInterval=undefined;
|
||||||
|
time_string_old_wrapped = time_string_new_wrapped;
|
||||||
|
}
|
||||||
|
print(Math.round((getTime() - time_start)*1000))
|
||||||
|
}, 30);
|
||||||
|
};
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
@ -95,6 +125,8 @@ Bangle.setUI({
|
||||||
// Called to unload all of the clock app
|
// Called to unload all of the clock app
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
if (animInterval) clearInterval(animInterval);
|
||||||
|
animInterval = undefined;
|
||||||
require('widget_utils').show(); // re-show widgets
|
require('widget_utils').show(); // re-show widgets
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -106,5 +138,6 @@ if (settings.showWidgets) {
|
||||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R = Bangle.appRect;
|
||||||
draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id":"fuzzyw",
|
"id":"fuzzyw",
|
||||||
"name":"Fuzzy Text Clock",
|
"name":"Fuzzy Text Clock",
|
||||||
"shortName": "Fuzzy Text",
|
"shortName": "Fuzzy Text",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "An imprecise clock for when you're not in a rush",
|
"description": "An imprecise clock for when you're not in a rush",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon":"fuzzyw.png",
|
"icon":"fuzzyw.png",
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: New app!
|
0.01: New app!
|
||||||
|
0.02: Added settings to show/hide widgets and settings for different styles.
|
||||||
|
|
@ -14,6 +14,10 @@ Here you can see an example of a locked bangle with a low battery:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Settings
|
||||||
|
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
||||||
|
- Theme: Select your custom theme, independent of system settings.
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
- [David Peer](https://github.com/peerdavid).
|
- [David Peer](https://github.com/peerdavid).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,82 @@
|
||||||
/************************************************
|
/************************************************
|
||||||
* Happy Clock
|
* Happy Clock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const storage = require('Storage');
|
||||||
|
const widget_utils = require("widget_utils");
|
||||||
|
|
||||||
|
|
||||||
|
/************************************************
|
||||||
|
* Settings
|
||||||
|
*/
|
||||||
|
const SETTINGS_FILE = "happyclk.setting.json";
|
||||||
|
|
||||||
|
let settings = {
|
||||||
|
color: "Dark",
|
||||||
|
screen: "Dynamic"
|
||||||
|
};
|
||||||
|
|
||||||
|
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||||
|
for (const key in saved_settings) {
|
||||||
|
settings[key] = saved_settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
var color_map = {
|
||||||
|
"Dark":{
|
||||||
|
fg: "#fff",
|
||||||
|
bg: "#000",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
},
|
||||||
|
"Black":{
|
||||||
|
fg: "#fff",
|
||||||
|
bg: "#000",
|
||||||
|
eye: "#000",
|
||||||
|
eyePupils: "#fff"
|
||||||
|
},
|
||||||
|
"White":{
|
||||||
|
fg: "#000",
|
||||||
|
bg: "#fff",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
},
|
||||||
|
"Blue":{
|
||||||
|
fg: "#fff",
|
||||||
|
bg: "#00f",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
},
|
||||||
|
"Green":{
|
||||||
|
fg: "#000",
|
||||||
|
bg: "#0f0",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
},
|
||||||
|
"Red":{
|
||||||
|
fg: "#fff",
|
||||||
|
bg: "#f00",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
},
|
||||||
|
"Purple":{
|
||||||
|
fg: "#fff",
|
||||||
|
bg: "#f0f",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
},
|
||||||
|
"Yellow":{
|
||||||
|
fg: "#000",
|
||||||
|
bg: "#ff0",
|
||||||
|
eye: "#fff",
|
||||||
|
eyePupils: "#000"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var colors = color_map[settings.color];
|
||||||
|
|
||||||
|
/************************************************
|
||||||
|
* Globals
|
||||||
|
*/
|
||||||
var W = g.getWidth(),R=W/2;
|
var W = g.getWidth(),R=W/2;
|
||||||
var H = g.getHeight();
|
var H = g.getHeight();
|
||||||
var drawTimeout;
|
var drawTimeout;
|
||||||
|
|
@ -10,6 +86,16 @@ var drawTimeout;
|
||||||
* HELPER
|
* HELPER
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
let isFullscreen = function() {
|
||||||
|
var s = settings.screen.toLowerCase();
|
||||||
|
if(s == "dynamic"){
|
||||||
|
return Bangle.isLocked();
|
||||||
|
} else {
|
||||||
|
return s == "full";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
// Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
||||||
Graphics.prototype.drawPupils = function(cx, cy, r1, dx, dy, angle) {
|
Graphics.prototype.drawPupils = function(cx, cy, r1, dx, dy, angle) {
|
||||||
angle = angle % 360;
|
angle = angle % 360;
|
||||||
|
|
@ -19,11 +105,12 @@ Graphics.prototype.drawPupils = function(cx, cy, r1, dx, dy, angle) {
|
||||||
|
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.fillCircle(cx, cy, 32);
|
g.fillCircle(cx, cy, 32);
|
||||||
g.setColor(g.theme.bg);
|
|
||||||
|
g.setColor(colors.eye);
|
||||||
g.fillCircle(cx, cy, 27);
|
g.fillCircle(cx, cy, 27);
|
||||||
g.fillCircle(cx+dx, cy+dy, 28);
|
g.fillCircle(cx+dx, cy+dy, 28);
|
||||||
|
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(colors.eyePupils);
|
||||||
g.fillCircle(x, y, 8);
|
g.fillCircle(x, y, 8);
|
||||||
g.fillCircle(x+1, y, 8);
|
g.fillCircle(x+1, y, 8);
|
||||||
};
|
};
|
||||||
|
|
@ -85,6 +172,7 @@ let drawEyes = function(){
|
||||||
|
|
||||||
|
|
||||||
let drawSmile = function(isLocked){
|
let drawSmile = function(isLocked){
|
||||||
|
g.setColor(colors.fg);
|
||||||
var y = 120;
|
var y = 120;
|
||||||
var o = parseInt(E.getBattery()*0.8);
|
var o = parseInt(E.getBattery()*0.8);
|
||||||
|
|
||||||
|
|
@ -100,6 +188,9 @@ let drawSmile = function(isLocked){
|
||||||
}
|
}
|
||||||
|
|
||||||
let drawEyeBrow = function(){
|
let drawEyeBrow = function(){
|
||||||
|
if(!isFullscreen()) return;
|
||||||
|
|
||||||
|
g.setColor(colors.fg);
|
||||||
var w = 6;
|
var w = 6;
|
||||||
for(var i = 0; i < w; i++){
|
for(var i = 0; i < w; i++){
|
||||||
g.drawLine(25, 25+i, 70, 15+i%3);
|
g.drawLine(25, 25+i, 70, 15+i%3);
|
||||||
|
|
@ -108,6 +199,15 @@ let drawEyeBrow = function(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let drawWidgets = function(){
|
||||||
|
if (isFullscreen()) {
|
||||||
|
widget_utils.hide();
|
||||||
|
} else {
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let draw = function(){
|
let draw = function(){
|
||||||
// Queue draw in one minute
|
// Queue draw in one minute
|
||||||
|
|
@ -118,12 +218,16 @@ let draw = function(){
|
||||||
}
|
}
|
||||||
|
|
||||||
let drawHelper = function(isLocked){
|
let drawHelper = function(isLocked){
|
||||||
|
g.setColor(g.theme.bg);
|
||||||
|
|
||||||
|
g.fillRect(0, isFullscreen() ? 0 : 24, W, H);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.reset().clear();
|
|
||||||
|
|
||||||
drawEyes();
|
drawEyes();
|
||||||
drawEyeBrow();
|
drawEyeBrow();
|
||||||
drawSmile(isLocked);
|
drawSmile(isLocked);
|
||||||
|
|
||||||
|
drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -140,6 +244,15 @@ Bangle.on('lcdPower',on=>{
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.on('lock', function(isLocked) {
|
Bangle.on('lock', function(isLocked) {
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
|
||||||
|
if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
|
||||||
|
// If we have to show the widgets again, we load it from our
|
||||||
|
// cache and not through Bangle.loadWidgets as its much faster!
|
||||||
|
widget_utils.show();
|
||||||
|
}
|
||||||
|
|
||||||
draw(isLocked);
|
draw(isLocked);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -162,15 +275,9 @@ let queueDraw = function() {
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
/*
|
|
||||||
* we are not drawing the widgets as we are taking over the whole screen
|
|
||||||
* so we will blank out the draw() functions of each widget and change the
|
|
||||||
* area to the top bar doesn't get cleared.
|
|
||||||
*/
|
|
||||||
require('widget_utils').hide();
|
|
||||||
|
|
||||||
// Clear the screen once, at startup and draw clock
|
// Clear the screen once, at startup and draw clock
|
||||||
// g.setTheme({bg:"#fff",fg:"#000",dark:false});
|
g.setTheme({bg:colors.bg,fg:colors.fg,dark:false});
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
// After drawing the watch face, we can draw the widgets
|
// After drawing the watch face, we can draw the widgets
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
(function(back) {
|
||||||
|
const SETTINGS_FILE = "happyclk.setting.json";
|
||||||
|
|
||||||
|
// initialize with default settings...
|
||||||
|
const storage = require('Storage')
|
||||||
|
let settings = {
|
||||||
|
color: "Dark",
|
||||||
|
screen: "Dynamic"
|
||||||
|
};
|
||||||
|
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||||
|
for (const key in saved_settings) {
|
||||||
|
settings[key] = saved_settings[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
storage.write(SETTINGS_FILE, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
var colorOptions = ["Dark", "Black", "White", "Blue", "Green", "Red", "Purple", "Yellow"];
|
||||||
|
var screenOptions = ["Normal", "Dynamic", "Full"];
|
||||||
|
E.showMenu({
|
||||||
|
'': { 'title': 'Happy Clock' },
|
||||||
|
'< Back': back,
|
||||||
|
'Screen': {
|
||||||
|
value: 0 | screenOptions.indexOf(settings.screen),
|
||||||
|
min: 0, max: screenOptions.length-1,
|
||||||
|
format: v => screenOptions[v],
|
||||||
|
onchange: v => {
|
||||||
|
settings.screen = screenOptions[v];
|
||||||
|
save();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Theme': {
|
||||||
|
value: 0 | colorOptions.indexOf(settings.color),
|
||||||
|
min: 0, max: colorOptions.length-1,
|
||||||
|
format: v => colorOptions[v],
|
||||||
|
onchange: v => {
|
||||||
|
settings.color = colorOptions[v];
|
||||||
|
save();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Happy Clock",
|
"name": "Happy Clock",
|
||||||
"shortName":"Happy Clock",
|
"shortName":"Happy Clock",
|
||||||
"icon": "happyclk.png",
|
"icon": "happyclk.png",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "A happy clock :)",
|
"description": "A happy clock :)",
|
||||||
|
|
@ -12,10 +12,13 @@
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
{"url":"screenshot_1.png"},
|
{"url":"screenshot_1.png"},
|
||||||
{"url":"screenshot_2.png"},
|
{"url":"screenshot_2.png"},
|
||||||
{"url":"screenshot_3.png"}
|
{"url":"screenshot_3.png"},
|
||||||
|
{"url":"screenshot_4.png"},
|
||||||
|
{"url":"screenshot_5.png"}
|
||||||
],
|
],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"happyclk.app.js","url":"happyclk.app.js"},
|
{"name":"happyclk.app.js","url":"happyclk.app.js"},
|
||||||
{"name":"happyclk.img","url":"happyclk.icon.js","evaluate":true}
|
{"name":"happyclk.img","url":"happyclk.icon.js","evaluate":true},
|
||||||
|
{"name":"happyclk.settings.js","url":"happyclk.settings.js"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -20,3 +20,4 @@
|
||||||
still be loaded when they weren't supposed to.
|
still be loaded when they weren't supposed to.
|
||||||
0.15: Ensure that we hide widgets if in fullscreen mode
|
0.15: Ensure that we hide widgets if in fullscreen mode
|
||||||
(So that widgets are still hidden if launcher is fast-loaded)
|
(So that widgets are still hidden if launcher is fast-loaded)
|
||||||
|
0.16: Use firmware provided E.showScroller method
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "iconlaunch",
|
"id": "iconlaunch",
|
||||||
"name": "Icon Launcher",
|
"name": "Icon Launcher",
|
||||||
"shortName" : "Icon launcher",
|
"shortName" : "Icon launcher",
|
||||||
"version": "0.15",
|
"version": "0.16",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||||
"tags": "tool,system,launcher",
|
"tags": "tool,system,launcher",
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: First public version
|
0.01: First public version
|
||||||
|
0.02: Disable screen lock when breathing
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
A minimalistic app that will help you practive breathing.
|
A minimalistic app that will help you practive breathing.
|
||||||
|
|
||||||
Author: Written by pancake in 2022, powered by insomnia
|
Author: Written by pancake in 2022, updated in 2023, powered by insomnia
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
@ -10,6 +10,7 @@ Author: Written by pancake in 2022, powered by insomnia
|
||||||
* [x] Tap to start
|
* [x] Tap to start
|
||||||
* [x] Subtle vibrations
|
* [x] Subtle vibrations
|
||||||
* [x] Drag to pause breathing
|
* [x] Drag to pause breathing
|
||||||
|
* [x] Dont lock screen while breathing
|
||||||
* [ ] Automatic buzz every hour during day
|
* [ ] Automatic buzz every hour during day
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ var mode = 0;
|
||||||
var sin = 0;
|
var sin = 0;
|
||||||
var dragged = 0;
|
var dragged = 0;
|
||||||
var lastTime = Date.now();
|
var lastTime = Date.now();
|
||||||
|
|
||||||
function breath(t) {
|
function breath(t) {
|
||||||
var r = Math.abs(Math.sin(t / 100)) * w2;
|
var r = Math.abs(Math.sin(t / 100)) * w2;
|
||||||
g.fillCircle(w/2,h/2, r);
|
g.fillCircle(w/2,h/2, r);
|
||||||
|
|
@ -26,7 +27,7 @@ setTimeout(()=>{Bangle.buzz(60);}, 500);
|
||||||
|
|
||||||
function showTouchScreen() {
|
function showTouchScreen() {
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
g.fillCircle (w2, h2, h2-5);
|
g.fillCircle(w2, h2, h2-5);
|
||||||
g.setColor(0,0,0);
|
g.setColor(0,0,0);
|
||||||
g.setFont("Vector", 32);
|
g.setFont("Vector", 32);
|
||||||
g.drawString("Tap to", w/6, h2-fs);
|
g.drawString("Tap to", w/6, h2-fs);
|
||||||
|
|
@ -40,7 +41,7 @@ g.clear();
|
||||||
function animateCircle() {
|
function animateCircle() {
|
||||||
g.clear();
|
g.clear();
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
g.fillCircle (w2, h2, radius);
|
g.fillCircle(w2, h2, radius);
|
||||||
radius-=2;
|
radius-=2;
|
||||||
if (radius < 40) {
|
if (radius < 40) {
|
||||||
breathing = true;
|
breathing = true;
|
||||||
|
|
@ -68,6 +69,9 @@ function main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
started = true;
|
started = true;
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
|
Bangle.setLocked(0);
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
animateCircle();
|
animateCircle();
|
||||||
Bangle.buzz(40);
|
Bangle.buzz(40);
|
||||||
}
|
}
|
||||||
|
|
@ -78,48 +82,48 @@ function main() {
|
||||||
main();
|
main();
|
||||||
|
|
||||||
function startBreathing() {
|
function startBreathing() {
|
||||||
var cicles = 3;
|
var cicles = 3;
|
||||||
g.setFont("Vector", fs);
|
g.setFont("Vector", fs);
|
||||||
|
|
||||||
var interval = setInterval(function() {
|
function breathTime() {
|
||||||
if (lastTime + 10 > Date.now()) {
|
if (lastTime + 10 > Date.now()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastTime = Date.now();
|
lastTime = Date.now();
|
||||||
g.setColor(0, 0, 0);
|
g.setColor(0, 0, 0);
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
||||||
g.setColor(0, 0.5, 1);
|
g.setColor(0, 0.5, 1);
|
||||||
var b = breath(count);
|
var b = breath(count);
|
||||||
g.setColor(0.5, 0.5, 1);
|
g.setColor(0.5, 0.5, 1);
|
||||||
var c = breath(count + 50);
|
var c = breath(count + 50);
|
||||||
count++;
|
count++;
|
||||||
g.setColor(1, 1, 1);
|
g.setColor(1, 1, 1);
|
||||||
if (b < c) {
|
if (b < c) {
|
||||||
g.drawString("inspire",8,ty);
|
g.drawString("inspire",8,ty);
|
||||||
if (mode) {
|
if (mode) {
|
||||||
mode = 0;
|
mode = 0;
|
||||||
Bangle.buzz(20);
|
Bangle.buzz(20);
|
||||||
if (!dragged ) {
|
if (!dragged ) {
|
||||||
cicles--;
|
cicles--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.drawString("expire",8,ty);
|
||||||
|
if (!mode) {
|
||||||
|
mode = 1;
|
||||||
|
Bangle.buzz(20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
g.drawString(cicles, w-fs, ty);
|
||||||
g.drawString("expire",8,ty);
|
if (cicles < 1) {
|
||||||
if (!mode) {
|
clearInterval(interval);
|
||||||
mode = 1;
|
g.clear();
|
||||||
Bangle.buzz(20);
|
g.drawString("Thanks for",20,h/3);
|
||||||
|
g.drawString(" breathing!",20,(h/3) + 16);
|
||||||
|
Bangle.showClock();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
g.drawString(cicles, w-fs, ty);
|
|
||||||
if (cicles < 1) {
|
|
||||||
clearInterval(interval);
|
|
||||||
g.clear();
|
|
||||||
g.drawString("Thanks for",20,h/3);
|
|
||||||
g.drawString(" breathing!",20,(h/3) + 16);
|
|
||||||
Bangle.showClock();
|
|
||||||
}
|
|
||||||
dragged = 0;
|
dragged = 0;
|
||||||
|
}
|
||||||
}, 4);
|
var interval = setInterval(breathTime, 4);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "inspire",
|
"id": "inspire",
|
||||||
"name": "Inspire Breathing",
|
"name": "Inspire Breathing",
|
||||||
"shortName": "Inspire",
|
"shortName": "Inspire",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "exercise breathing every now and then",
|
"description": "exercise breathing every now and then",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"tags": "tools,health",
|
"tags": "tools,health",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Based on the Pebble watchface Weather Land.
|
||||||
|
|
||||||
Mountain Pass Clock changes depending on time (day/night) and weather conditions.
|
Mountain Pass Clock changes depending on time (day/night) and weather conditions.
|
||||||
|
|
||||||
This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification). To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather.
|
This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification), or a Bangle app that will update weather.json such as OWM Weather. To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather.
|
||||||
|
|
||||||
The scene will change according to the following OpenWeatherMap conditions: clear, cloudy, overcast, lightning, drizzle, rain, fog and snow. Each weather condition has night/day scenes.
|
The scene will change according to the following OpenWeatherMap conditions: clear, cloudy, overcast, lightning, drizzle, rain, fog and snow. Each weather condition has night/day scenes.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -323,11 +323,28 @@ function setWeather() {
|
||||||
draw(a);
|
draw(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readWeather() {
|
||||||
|
var weatherJson = require("Storage").readJSON('weather.json', 1);
|
||||||
|
// save updated weather data if available and it has been an hour since last updated
|
||||||
|
if (weatherJson !== undefined && (data.time === undefined || (data.time + 3600000) < weatherJson.weather.time)) {
|
||||||
|
data = {
|
||||||
|
time: weatherJson.weather.time,
|
||||||
|
temp: weatherJson.weather.temp,
|
||||||
|
code: weatherJson.weather.code
|
||||||
|
};
|
||||||
|
require("Storage").writeJSON('mtnclock.json', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const _GB = global.GB;
|
const _GB = global.GB;
|
||||||
global.GB = (event) => {
|
global.GB = (event) => {
|
||||||
if (event.t==="weather") {
|
if (event.t==="weather") {
|
||||||
data = event;
|
data = {
|
||||||
require("Storage").write('mtnclock.json', event);
|
temp: event.temp,
|
||||||
|
code: event.code,
|
||||||
|
time: Date.now()
|
||||||
|
};
|
||||||
|
require("Storage").writeJSON('mtnclock.json', data);
|
||||||
setWeather();
|
setWeather();
|
||||||
}
|
}
|
||||||
if (_GB) setTimeout(_GB, 0, event);
|
if (_GB) setTimeout(_GB, 0, event);
|
||||||
|
|
@ -340,11 +357,13 @@ function queueDraw() {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = setTimeout(function() {
|
drawTimeout = setTimeout(function() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
readWeather();
|
||||||
setWeather();
|
setWeather();
|
||||||
queueDraw();
|
queueDraw();
|
||||||
}, 60000 - (Date.now() % 60000));
|
}, 60000 - (Date.now() % 60000));
|
||||||
}
|
}
|
||||||
|
|
||||||
queueDraw();
|
queueDraw();
|
||||||
|
readWeather();
|
||||||
setWeather();
|
setWeather();
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "mtnclock",
|
"id": "mtnclock",
|
||||||
"name": "Mountain Pass Clock",
|
"name": "Mountain Pass Clock",
|
||||||
"shortName": "Mtn Clock",
|
"shortName": "Mtn Clock",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "A clock that changes scenery based on time and weather.",
|
"description": "A clock that changes scenery based on time and weather.",
|
||||||
"readme":"README.md",
|
"readme":"README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Use default Bangle formatter for booleans
|
0.03: Use default Bangle formatter for booleans
|
||||||
0.04: Remove calibration with current voltage (Calibrate->Auto) as it is now handled by settings app
|
0.04: Remove calibration with current voltage (Calibrate->Auto) as it is now handled by settings app
|
||||||
Allow automatic calibration on every charge longer than 3 hours
|
Allow automatic calibration on every charge longer than 3 hours
|
||||||
|
0.05: Add back button to settings menu.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "powermanager",
|
"id": "powermanager",
|
||||||
"name": "Power Manager",
|
"name": "Power Manager",
|
||||||
"shortName": "Power Manager",
|
"shortName": "Power Manager",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
|
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
'': {
|
'': {
|
||||||
'title': 'Power Manager'
|
'title': 'Power Manager'
|
||||||
},
|
},
|
||||||
|
"< Back" : back,
|
||||||
'Monotonic percentage': {
|
'Monotonic percentage': {
|
||||||
value: !!settings.forceMonoPercentage,
|
value: !!settings.forceMonoPercentage,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fix fast loading on swipe to clock
|
0.02: Fix fast loading on swipe to clock
|
||||||
|
0.03: Adds a setting for going back to clock on a timeout
|
||||||
|
0.04: Fix timeouts closing fast loaded apps
|
||||||
|
|
@ -110,6 +110,8 @@ let layout = new Layout({
|
||||||
}, {
|
}, {
|
||||||
remove: ()=>{
|
remove: ()=>{
|
||||||
Bangle.removeListener("swipe", onSwipe);
|
Bangle.removeListener("swipe", onSwipe);
|
||||||
|
Bangle.removeListener("touch", updateTimeout);
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
delete Graphics.prototype.setFont8x12;
|
delete Graphics.prototype.setFont8x12;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -117,6 +119,16 @@ g.clear();
|
||||||
layout.render();
|
layout.render();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
const updateTimeout = function(){
|
||||||
|
if (settings.timeout){
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(Bangle.showClock,settings.timeout*1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTimeout();
|
||||||
|
|
||||||
// swipe event listener for exit gesture
|
// swipe event listener for exit gesture
|
||||||
let onSwipe = function (lr, ud) {
|
let onSwipe = function (lr, ud) {
|
||||||
if(exitGesture == "swipeup" && ud == -1) Bangle.showClock();
|
if(exitGesture == "swipeup" && ud == -1) Bangle.showClock();
|
||||||
|
|
@ -126,4 +138,5 @@ let onSwipe = function (lr, ud) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on("swipe", onSwipe);
|
Bangle.on("swipe", onSwipe);
|
||||||
|
Bangle.on("touch", updateTimeout);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "qcenter",
|
"id": "qcenter",
|
||||||
"name": "Quick Center",
|
"name": "Quick Center",
|
||||||
"shortName": "QCenter",
|
"shortName": "QCenter",
|
||||||
"version": "0.02",
|
"version": "0.04",
|
||||||
"description": "An app for quickly launching your favourite apps, inspired by the control centres of other watches.",
|
"description": "An app for quickly launching your favourite apps, inspired by the control centres of other watches.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,11 @@
|
||||||
E.showMenu(exitGestureMenu);
|
E.showMenu(exitGestureMenu);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Set Timeout
|
||||||
|
mainmenu["Timeout: " + (settings.timeout ? (settings.timeout+"s") : "Off")] = function () {
|
||||||
|
E.showMenu(timeoutMenu);
|
||||||
|
};
|
||||||
|
|
||||||
//List all pinned apps, redirecting to menu with options to unpin and reorder
|
//List all pinned apps, redirecting to menu with options to unpin and reorder
|
||||||
pinnedApps.forEach((app, i) => {
|
pinnedApps.forEach((app, i) => {
|
||||||
mainmenu[app.name] = function () {
|
mainmenu[app.name] = function () {
|
||||||
|
|
@ -129,5 +134,22 @@
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// menu for setting timeout
|
||||||
|
var timeoutMenu = {
|
||||||
|
"": { title: "Timeout", back: showMainMenu }
|
||||||
|
};
|
||||||
|
timeoutMenu["Off"] = function () {
|
||||||
|
save("timeout", 0);
|
||||||
|
showMainMenu();
|
||||||
|
};
|
||||||
|
let timeoutvalues = [10,20,30,60];
|
||||||
|
for (c in timeoutvalues){
|
||||||
|
let v = timeoutvalues[c];
|
||||||
|
timeoutMenu[v+"s"] = function () {
|
||||||
|
save("timeout", v);
|
||||||
|
showMainMenu();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/0.0.3/ical.min.js"></script>
|
||||||
|
<script>
|
||||||
|
let dataElement = document.getElementById("data");
|
||||||
|
let alarms;
|
||||||
|
let schedSettings;
|
||||||
|
|
||||||
|
function readFile(input) {
|
||||||
|
document.getElementById('upload').disabled = true;
|
||||||
|
const offsetMinutes = document.getElementById("offsetMinutes").value;
|
||||||
|
|
||||||
|
for(let i=0; i<input.files.length; i++) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => {
|
||||||
|
const jCalData = ICAL.parse(reader.result);
|
||||||
|
const comp = new ICAL.Component(jCalData[1]);
|
||||||
|
// Fetch the VEVENT part
|
||||||
|
comp.getAllSubcomponents('vevent').forEach(vevent => {
|
||||||
|
event = new ICAL.Event(vevent);
|
||||||
|
const exists = alarms.some(alarm => alarm.id === event.uid);
|
||||||
|
|
||||||
|
const alarm = eventToAlarm(event, offsetMinutes*60*1000);
|
||||||
|
renderAlarm(alarm, exists);
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
alarms = alarms.filter(alarm => alarm.id !== event.uid); // remove if already exists
|
||||||
|
const tr = document.querySelector(`.event-row[data-uid='${event.uid}']`);
|
||||||
|
document.getElementById('events').removeChild(tr);
|
||||||
|
}
|
||||||
|
alarms.push(alarm);
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
reader.readAsText(input.files[i], "UTF-8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateToMsSinceMidnight(date) {
|
||||||
|
const dateMidnight = new Date(date);
|
||||||
|
dateMidnight.setHours(0,0,0,0);
|
||||||
|
return date - dateMidnight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateFromAlarm(alarm) {
|
||||||
|
const date = new Date(alarm.date);
|
||||||
|
return new Date(date.getTime() + alarm.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlarmDefaults() {
|
||||||
|
const date = new Date();
|
||||||
|
return {
|
||||||
|
on: true,
|
||||||
|
t: dateToMsSinceMidnight(date),
|
||||||
|
dow: 127,
|
||||||
|
date: date.toISOString().substring(0,10),
|
||||||
|
last: 0,
|
||||||
|
rp: "defaultRepeat" in schedSettings ? schedSettings.defaultRepeat : false,
|
||||||
|
vibrate: "defaultAlarmPattern" in schedSettings ? schedSettings.defaultAlarmPattern : "::",
|
||||||
|
as: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventToAlarm(event, offsetMs) {
|
||||||
|
const dateOrig = event.startDate.toJSDate();
|
||||||
|
const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig;
|
||||||
|
|
||||||
|
const alarm = {...getAlarmDefaults(), ...{
|
||||||
|
id: event.uid,
|
||||||
|
msg: event.summary,
|
||||||
|
t: dateToMsSinceMidnight(date),
|
||||||
|
date: date.toISOString().substring(0,10),
|
||||||
|
data: {end: event.endDate.toJSDate().toISOString()}
|
||||||
|
}};
|
||||||
|
if (offsetMs) { // Alarm time is not real event time, so do a backup
|
||||||
|
alarm.data.time = dateOrig.toISOString();
|
||||||
|
}
|
||||||
|
return alarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload() {
|
||||||
|
Util.showModal("Saving...");
|
||||||
|
Util.writeStorage("sched.json", JSON.stringify(alarms), () => {
|
||||||
|
location.reload(); // reload so we see current data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAlarm(alarm, exists) {
|
||||||
|
const localDate = dateFromAlarm(alarm);
|
||||||
|
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.classList.add('event-row');
|
||||||
|
tr.dataset.uid = alarm.id;
|
||||||
|
const tdTime = document.createElement('td');
|
||||||
|
tr.appendChild(tdTime);
|
||||||
|
const inputTime = document.createElement('input');
|
||||||
|
inputTime.type = "datetime-local";
|
||||||
|
inputTime.classList.add('event-date');
|
||||||
|
inputTime.classList.add('form-input');
|
||||||
|
inputTime.dataset.uid = alarm.id;
|
||||||
|
inputTime.value = localDate.toISOString().slice(0,16);
|
||||||
|
inputTime.onchange = (e => {
|
||||||
|
const date = new Date(inputTime.value);
|
||||||
|
alarm.t = dateToMsSinceMidnight(date);
|
||||||
|
alarm.date = date.toISOString().substring(0,10);
|
||||||
|
});
|
||||||
|
tdTime.appendChild(inputTime);
|
||||||
|
|
||||||
|
const tdSummary = document.createElement('td');
|
||||||
|
tr.appendChild(tdSummary);
|
||||||
|
const inputSummary = document.createElement('input');
|
||||||
|
inputSummary.type = "text";
|
||||||
|
inputSummary.classList.add('event-summary');
|
||||||
|
inputSummary.classList.add('form-input');
|
||||||
|
inputSummary.dataset.uid = alarm.id;
|
||||||
|
inputSummary.maxLength=40;
|
||||||
|
const realHumanStartTime = alarm.data?.time ? ' ' + (new Date(alarm.data.time)).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '';
|
||||||
|
const summary = (alarm.msg?.substring(0, inputSummary.maxLength) || "");
|
||||||
|
inputSummary.value = summary.endsWith(realHumanStartTime) ? summary : summary + realHumanStartTime;
|
||||||
|
inputSummary.onchange = (e => {
|
||||||
|
alarm.msg = inputSummary.value;
|
||||||
|
});
|
||||||
|
tdSummary.appendChild(inputSummary);
|
||||||
|
inputSummary.onchange();
|
||||||
|
|
||||||
|
const tdInfo = document.createElement('td');
|
||||||
|
tr.appendChild(tdInfo);
|
||||||
|
|
||||||
|
const buttonDelete = document.createElement('button');
|
||||||
|
buttonDelete.classList.add('btn');
|
||||||
|
buttonDelete.classList.add('btn-action');
|
||||||
|
tdInfo.prepend(buttonDelete);
|
||||||
|
const iconDelete = document.createElement('i');
|
||||||
|
iconDelete.classList.add('icon');
|
||||||
|
iconDelete.classList.add('icon-delete');
|
||||||
|
buttonDelete.appendChild(iconDelete);
|
||||||
|
buttonDelete.onclick = (e => {
|
||||||
|
alarms = alarms.filter(a => a !== alarm);
|
||||||
|
document.getElementById('events').removeChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('events').appendChild(tr);
|
||||||
|
document.getElementById('upload').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAlarm() {
|
||||||
|
const alarm = getAlarmDefaults();
|
||||||
|
renderAlarm(alarm);
|
||||||
|
alarms.push(alarm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData() {
|
||||||
|
Util.showModal("Loading...");
|
||||||
|
Util.readStorage('sched.json',data=>{
|
||||||
|
alarms = JSON.parse(data || "[]") || [];
|
||||||
|
|
||||||
|
Util.readStorage('sched.settings.json',data=>{
|
||||||
|
schedSettings = JSON.parse(data || "{}") || {};
|
||||||
|
Util.hideModal();
|
||||||
|
alarms.forEach(alarm => {
|
||||||
|
if (alarm.date) {
|
||||||
|
renderAlarm(alarm, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when app starts
|
||||||
|
function onInit() {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h4>Manage dated events</h4>
|
||||||
|
|
||||||
|
<div class="float-right">
|
||||||
|
<button class="btn" onclick="addAlarm()">
|
||||||
|
<i class="icon icon-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="events">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-5 col-xs-12">
|
||||||
|
<label class="form-label" for="fileinput">Add from iCalendar file</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-7 col-xs-12">
|
||||||
|
<input id="fileinput" class="form-input" type="file" onchange="readFile(this)" accept=".ics,.ifb,.ical,.ifbf" multiple/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-5 col-xs-12">
|
||||||
|
<label class="form-label" for="fileinput">Minutes to alarm in advance</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-7 col-xs-12">
|
||||||
|
<input id="offsetMinutes" class="form-input" type="number" value="0" min="0" step="5"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<button id="upload" class="btn btn-primary" onClick="upload()" disabled>Upload</button>
|
||||||
|
<button id="reload" class="btn" onClick="location.reload()">Reload</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"provides_modules" : ["sched"],
|
"provides_modules" : ["sched"],
|
||||||
"default" : true,
|
"default" : true,
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
"interface": "interface.html",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"sched.boot.js","url":"boot.js"},
|
{"name":"sched.boot.js","url":"boot.js"},
|
||||||
{"name":"sched.js","url":"sched.js"},
|
{"name":"sched.js","url":"sched.js"},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: 3/Feb/2023 Added 'Temperature Graph' app to depository.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Temperature Graph
|
||||||
|
|
||||||
|
**Temperature Graph** (tempgraph) is a Bangle.js 2 app for recording graphs of the temperature for various time periods from 10 minutes to 7 days long. It samples the watch's temperature sensor 150 times while creating a graph, regardless of the time period selected.
|
||||||
|
|
||||||
|
### Menu Options
|
||||||
|
* **Widgets** Toggles the watch's widgets on and off. With them off gives you a bigger graph when viewing it.
|
||||||
|
|
||||||
|
* **Duration** Select the time period for drawing the graph, from 10 minutes to 7 days long.
|
||||||
|
|
||||||
|
* **Draw Graph** Draws the graph.
|
||||||
|
* Tapping the screen toggles the graph between Celsius (red) and Fahrenheit (blue).
|
||||||
|
* Pressing the watch button takes you back to the menu. **Note:** While the graph can still be viewed after returning to the menu, you can't continue recording it if you had returned to the menu before the time period was up. The graph is saved in the watch though so it's still there the next time you start the app.
|
||||||
|
|
||||||
|
* **Show Graph** Shows the last drawn graph.
|
||||||
|
* Tapping the screen toggles the graph between Celsius (red) and Fahrenheit (blue).
|
||||||
|
* Pressing the watch button takes you back to the menu.
|
||||||
|
|
||||||
|
* **Save Graph** Sends a screengrab of the graph to the Espruino Web IDE from where you can save it as you would any image on a webpage.
|
||||||
|
|
||||||
|
* **Save Data** Sends a CSV file of the graph's temperature data to the Espruino Web IDE where you can save it for further use. I suggest you use the Espruino Web IDE's Terminal Logger (selected in the IDE's Settings/General) to record the data as it's sent. This is the easiest way to save it as a text file.
|
||||||
|
|
||||||
|
* **Show Temp** Shows the current temperature.
|
||||||
|
|
||||||
|
### Note
|
||||||
|
Using the watch in a normal fashion can raise the temperature it's sensing to quite a few degrees above the surrounding temperature and it may take half an hour or so to drop to close to the surrounding temperature. After that it seems to give quite accurate readings, assuming the thermometer I've been comparing it to is itself reasonably accurate. So best to load the app then not touch the watch for half an hour before starting a recording. This is assuming you're not wearing the app and are just using it to record the temperature where you've put the watch. You could of course wear it and it'll still draw a graph, which might also be useful.
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### Creator
|
||||||
|
Carl Read ([mail](mailto:cread98@orcon.net.nz), [github](https://github.com/CarlR9))
|
||||||
|
|
||||||
|
#### License
|
||||||
|
[MIT License](LICENSE)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+goA/AH4AgrHXABFYF0XXkYAK64utGENYFxoABSTxeHXYJglF+UAAIQvEBApfhE4UAF4IDBFwZf/X7hfsR4K/tL96/vRwpf/X/5fJGYK/tL9u02i/tF4KOFL/6/XF4ZftR4K/tL96/vRwpf/X/5fJGYK/tL96/vRwpf/X7gADF8ouBGA4v/F/6/urAmGABFYF7pgIL0owPF0KSC64AIRj4A/AH4ACA="))
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
// Temperature Graph
|
||||||
|
// BangleJS Script
|
||||||
|
|
||||||
|
Bangle.setBarometerPower(true,"tempgraph");
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
var wids=WIDGETS;
|
||||||
|
var widsOn=true;
|
||||||
|
var rm=null;
|
||||||
|
var gt=null;
|
||||||
|
var dg=null;
|
||||||
|
var Layout=require("Layout");
|
||||||
|
var C=true;
|
||||||
|
var temp,tempMode,readErrCnt,watchButton2;
|
||||||
|
|
||||||
|
var graph=require("Storage").readJSON("tempgraph.json",true);
|
||||||
|
if(graph==undefined) {
|
||||||
|
graph=[];
|
||||||
|
}
|
||||||
|
|
||||||
|
var timesData=[
|
||||||
|
// dur=duration, u=time units, d=divisions on graph, s=seconds per unit.
|
||||||
|
{dur:10,u:"Mins",d:5,s:60},
|
||||||
|
{dur:20,u:"Mins",d:4,s:60},
|
||||||
|
{dur:30,u:"Mins",d:3,s:60},
|
||||||
|
{dur:40,u:"Mins",d:4,s:60},
|
||||||
|
{dur:1,u:"Hr",d:4,s:3600},
|
||||||
|
{dur:2,u:"Hrs",d:4,s:3600},
|
||||||
|
{dur:3,u:"Hrs",d:3,s:3600},
|
||||||
|
{dur:4,u:"Hrs",d:4,s:3600},
|
||||||
|
{dur:6,u:"Hrs",d:6,s:3600},
|
||||||
|
{dur:8,u:"Hrs",d:4,s:3600},
|
||||||
|
{dur:12,u:"Hrs",d:6,s:3600},
|
||||||
|
{dur:16,u:"Hrs",d:4,s:3600},
|
||||||
|
{dur:20,u:"Hrs",d:5,s:3600},
|
||||||
|
{dur:1,u:"Day",d:4,s:3600},
|
||||||
|
{dur:2,u:"Days",d:4,s:86400},
|
||||||
|
{dur:3,u:"Days",d:3,s:86400},
|
||||||
|
{dur:4,u:"Days",d:4,s:86400},
|
||||||
|
{dur:5,u:"Days",d:5,s:86400},
|
||||||
|
{dur:6,u:"Days",d:6,s:86400},
|
||||||
|
{dur:7,u:"Days",d:7,s:86400}
|
||||||
|
];
|
||||||
|
var times=[];
|
||||||
|
for(n=0;n<timesData.length;n++){
|
||||||
|
times.push(timesData[n].dur+" "+timesData[n].u);
|
||||||
|
}
|
||||||
|
var durInd=0;
|
||||||
|
var duration=times[durInd];
|
||||||
|
|
||||||
|
function drawWids(){
|
||||||
|
g.clear();
|
||||||
|
if(widsOn){
|
||||||
|
WIDGETS=wids;
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
} else {
|
||||||
|
WIDGETS={};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMenu(){
|
||||||
|
drawWids();
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redoMenu(){
|
||||||
|
clearInterval(rm);
|
||||||
|
E.showMenu();
|
||||||
|
openMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshMenu(){
|
||||||
|
rm = setInterval(redoMenu,100);
|
||||||
|
}
|
||||||
|
function getF(c){
|
||||||
|
// Get Fahrenheit temperature from Celsius.
|
||||||
|
return c*1.8+32;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getT(){
|
||||||
|
Bangle.getPressure().then(p=>{
|
||||||
|
temp=p.temperature;
|
||||||
|
if(tempMode=="drawGraph"&&graph.length>0&&Math.abs(graph[graph.length-1].temp-temp)>10&&readErrCnt<2){
|
||||||
|
// A large change in temperature may be a reading error. ie. A 0C or less reading after
|
||||||
|
// a 20C reading. So if this happens, the reading is repeated up to 2 times to hopefully
|
||||||
|
// skip such errors.
|
||||||
|
readErrCnt++;
|
||||||
|
print("readErrCnt "+readErrCnt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearInterval(gt);
|
||||||
|
readErrCnt=0;
|
||||||
|
switch (tempMode){
|
||||||
|
case "showTemp":
|
||||||
|
showT();
|
||||||
|
break;
|
||||||
|
case "drawGraph":
|
||||||
|
var date=new Date();
|
||||||
|
var dateStr=require("locale").date(date).trim();
|
||||||
|
var hrs=date.getHours();
|
||||||
|
var mins=date.getMinutes();
|
||||||
|
var secs=date.getSeconds();
|
||||||
|
graph.push({
|
||||||
|
temp:temp,
|
||||||
|
date:dateStr,
|
||||||
|
hrs:hrs,
|
||||||
|
mins:mins,
|
||||||
|
secs:secs
|
||||||
|
});
|
||||||
|
if(graph.length==1){
|
||||||
|
graph[0].dur=durInd;
|
||||||
|
}
|
||||||
|
require("Storage").writeJSON("tempgraph.json", graph);
|
||||||
|
if(graph.length==150){
|
||||||
|
clearInterval(dg);
|
||||||
|
}
|
||||||
|
drawG();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemp(){
|
||||||
|
readErrCnt=0;
|
||||||
|
gt = setInterval(getT,800);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButton(){
|
||||||
|
var watchButton=setWatch(function(){
|
||||||
|
clearInterval(gt);
|
||||||
|
clearInterval(dg);
|
||||||
|
clearWatch(watchButton);
|
||||||
|
Bangle.removeListener("touch",screenTouch);
|
||||||
|
openMenu();
|
||||||
|
},BTN);
|
||||||
|
Bangle.on('touch',screenTouch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButton2(){
|
||||||
|
watchButton2=setWatch(function(){
|
||||||
|
clearWatch(watchButton2);
|
||||||
|
openMenu();
|
||||||
|
},BTN);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zPad(n){
|
||||||
|
return n.toString().padStart(2,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function screenTouch(n,ev){
|
||||||
|
if(ev.y>23&&ev.y<152){
|
||||||
|
C=C==false;
|
||||||
|
drawG(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawG(){
|
||||||
|
function cf(t){
|
||||||
|
if(C){
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
return getF(t);
|
||||||
|
}
|
||||||
|
drawWids();
|
||||||
|
var top=1;
|
||||||
|
var bar=21;
|
||||||
|
var barBot=175-22;
|
||||||
|
if(widsOn){
|
||||||
|
top=25;
|
||||||
|
bar=bar+24;
|
||||||
|
barBot=barBot-24;
|
||||||
|
}
|
||||||
|
var low=graph[0].temp;
|
||||||
|
var hi=low;
|
||||||
|
for(n=0;n<graph.length;n++){
|
||||||
|
var t=graph[n].temp;
|
||||||
|
if(low>t){
|
||||||
|
low=t;
|
||||||
|
}
|
||||||
|
if(hi<t){
|
||||||
|
hi=t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var tempHi=Math.ceil((cf(hi)+2)/10)*10;
|
||||||
|
var tempLow=Math.floor((cf(low)-2)/10)*10;
|
||||||
|
var div=2;
|
||||||
|
if(tempHi-tempLow>10){
|
||||||
|
div=5;
|
||||||
|
}
|
||||||
|
if(C){
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
}else{
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
}
|
||||||
|
var step=(barBot-bar)/((tempHi-tempLow)/div);
|
||||||
|
for(n=0;n<graph.length;n++){
|
||||||
|
var pos=tempLow-cf(graph[n].temp);
|
||||||
|
g.drawLine(n+3,pos*(step/div)+barBot,n+3,barBot+3);
|
||||||
|
}
|
||||||
|
g.fillRect(161,barBot+5,174,barBot+20);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.setFont("6x8:2");
|
||||||
|
if(C){
|
||||||
|
g.drawString("C",163,barBot+5);
|
||||||
|
}else{
|
||||||
|
g.drawString("F",163,barBot+5);
|
||||||
|
}
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.setFont6x15();
|
||||||
|
g.drawString("Temperature Graph - "+times[graph[0].dur],1,top);
|
||||||
|
g.drawRect(2,bar-4,153,barBot+4);
|
||||||
|
g.setFont("6x8:1");
|
||||||
|
var num=tempHi;
|
||||||
|
for(n=bar;n<=barBot;n=n+step){
|
||||||
|
g.drawLine(3,n,152,n);
|
||||||
|
g.drawString(num.toString().padStart(3," "),155,n-4);
|
||||||
|
num=num-div;
|
||||||
|
}
|
||||||
|
step=151/timesData[graph[0].dur].d;
|
||||||
|
for(n=step+2;n<152;n=n+step){
|
||||||
|
g.drawLine(n,bar-4,n,barBot+4);
|
||||||
|
}
|
||||||
|
grSt=graph[0];
|
||||||
|
g.drawString("Start: "+grSt.date+" "+grSt.hrs+":"+zPad(grSt.mins),1,barBot+6);
|
||||||
|
var lastT=graph[graph.length-1].temp;
|
||||||
|
g.drawString("Last Reading:",1,barBot+14);
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
g.drawString(lastT.toFixed(1)+"C",85,barBot+14);
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.drawString(getF(lastT).toFixed(1)+"F",121,barBot+14);
|
||||||
|
process.memory(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGraph(){
|
||||||
|
setButton();
|
||||||
|
tempMode="drawGraph";
|
||||||
|
durInd=times.indexOf(duration);
|
||||||
|
graph=[];
|
||||||
|
getTemp();
|
||||||
|
dg=setInterval(getTemp,1000*timesData[durInd].dur*timesData[durInd].s/150);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showGraph(){
|
||||||
|
setButton();
|
||||||
|
drawG();
|
||||||
|
}
|
||||||
|
|
||||||
|
function noBluetooth(){
|
||||||
|
if(NRF.getSecurityStatus().connected){
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
message("Error! Your\nBangle Watch\ncurrently has\nno Bluetooth\nconnection.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGraph(){
|
||||||
|
if(noBluetooth()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drawG();
|
||||||
|
g.flip();
|
||||||
|
g.dump();
|
||||||
|
message("Graph has\nbeen sent\nto Web IDE\nfor saving.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveData(){
|
||||||
|
if(noBluetooth()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drawG();
|
||||||
|
g.flip();
|
||||||
|
print("Temperature Graph - "+times[graph[0].dur]+"\n");
|
||||||
|
print("\"Date\",\"Time\",\"Celsius\",\"Fahrenheit\"");
|
||||||
|
for(n=0;n<graph.length;n++){
|
||||||
|
var gr=graph[n];
|
||||||
|
print("\""+gr.date+"\",\""+gr.hrs+":"+zPad(gr.mins)+":"+zPad(gr.secs)+"\","+gr.temp+","+getF(gr.temp));
|
||||||
|
}
|
||||||
|
message("Data has\nbeen sent\nto Web IDE\nfor saving.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function message(mes){
|
||||||
|
setButton2();
|
||||||
|
var messageLO=new Layout({
|
||||||
|
type:"v",c:[
|
||||||
|
{type:"txt",font:"6x8:2",width:171,label:mes,id:"label"},
|
||||||
|
{type:"btn",font:"6x8:2",pad:3,label:"OK",cb:l=>exit()},
|
||||||
|
],lazy:true
|
||||||
|
});
|
||||||
|
drawWids();
|
||||||
|
messageLO.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showT(){
|
||||||
|
tempLO.lab1.label=tempLO.lab3.label;
|
||||||
|
tempLO.lab2.label=tempLO.lab4.label;
|
||||||
|
tempLO.lab3.label=tempLO.lab5.label;
|
||||||
|
tempLO.lab4.label=tempLO.lab6.label;
|
||||||
|
tempLO.lab5.label=temp.toFixed(2)+"C";
|
||||||
|
tempLO.lab6.label=getF(temp).toFixed(2)+"F";
|
||||||
|
tempLO.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exit(){
|
||||||
|
clearWatch(watchButton2);
|
||||||
|
openMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTemp(){
|
||||||
|
tempMode="showTemp";
|
||||||
|
setButton2();
|
||||||
|
tempLO=new Layout({
|
||||||
|
type:"v",c:[
|
||||||
|
{type:"h",c:[
|
||||||
|
{type:"txt",pad:5,col:"#f77",font:"6x8:2",label:" ",id:"lab1"},
|
||||||
|
{type:"txt",pad:5,col:"#77f",font:"6x8:2",label:" ",id:"lab2"}
|
||||||
|
]},
|
||||||
|
{type:"h",c:[
|
||||||
|
{type:"txt",pad:5,col:"#f77",font:"6x8:2",label:" ",id:"lab3"},
|
||||||
|
{type:"txt",pad:5,col:"#77f",font:"6x8:2",label:" ",id:"lab4"}
|
||||||
|
]},
|
||||||
|
{type:"h",c:[
|
||||||
|
{type:"txt",pad:5,col:"#f00",font:"6x8:2",label:" ",id:"lab5"},
|
||||||
|
{type:"txt",pad:5,col:"#00f",font:"6x8:2",label:" ",id:"lab6"}
|
||||||
|
]},
|
||||||
|
{type:"h",c:[
|
||||||
|
{type:"btn",pad:2,font:"6x8:2",label:"Temp",cb:l=>getTemp()},
|
||||||
|
{type:"btn",pad:2,font:"6x8:2",label:"Exit",cb:l=>exit()}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
},{lazy:true});
|
||||||
|
tempLO.render();
|
||||||
|
getTemp();
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu={
|
||||||
|
"":{
|
||||||
|
"title":" Temp. Graph"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Widgets":{
|
||||||
|
value:widsOn,
|
||||||
|
format:vis=>vis?"Hide":"Show",
|
||||||
|
onchange:vis=>{
|
||||||
|
widsOn=vis;
|
||||||
|
refreshMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Duration":{
|
||||||
|
value:times.indexOf(duration),
|
||||||
|
min:0,max:times.length-1,step:1,wrap:true,
|
||||||
|
format:tim=>times[tim],
|
||||||
|
onchange:(dur)=>{
|
||||||
|
duration=times[dur];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Draw Graph":function(){
|
||||||
|
E.showMenu();
|
||||||
|
drawGraph();
|
||||||
|
},
|
||||||
|
|
||||||
|
"Show Graph" : function(){
|
||||||
|
E.showMenu();
|
||||||
|
if(graph.length>0){
|
||||||
|
showGraph();
|
||||||
|
}else{
|
||||||
|
message("No graph to\nshow as no\ngraph has been\ndrawn yet.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Save Graph" : function(){
|
||||||
|
E.showMenu();
|
||||||
|
if(graph.length>0){
|
||||||
|
saveGraph();
|
||||||
|
}else{
|
||||||
|
message("No graph to\nsave as no\ngraph has been\ndrawn yet.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Save Data" : function(){
|
||||||
|
E.showMenu();
|
||||||
|
if(graph.length>0){
|
||||||
|
saveData();
|
||||||
|
}else{
|
||||||
|
message("No data to\nsave as no\ngraph has been\ndrawn yet.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Show Temp":function(){
|
||||||
|
E.showMenu();
|
||||||
|
showTemp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openMenu();
|
||||||
|
After Width: | Height: | Size: 6.8 KiB |
|
|
@ -0,0 +1,19 @@
|
||||||
|
{ "id": "tempgraph",
|
||||||
|
"name": "Temperature Graph",
|
||||||
|
"shortName":"Temp Graph",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "An app for recording the temperature for time periods ranging from 10 minutes to 7 days.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "temperature,tempgraph,graph",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"tempgraph.app.js","url":"app.js"},
|
||||||
|
{"name":"tempgraph.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"tempgraph.json"}
|
||||||
|
],
|
||||||
|
"screenshots": [{"url":"screenshot_1.png"},{"url":"screenshot_2.png"},{"url":"screenshot_3.png"}]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -4,3 +4,4 @@
|
||||||
Add option to show seconds
|
Add option to show seconds
|
||||||
0.03: Fix Bell not appearing on alarms > 24h and redrawing interval
|
0.03: Fix Bell not appearing on alarms > 24h and redrawing interval
|
||||||
Update to match the default alarm widget, and not show itself when an alarm is hidden.
|
Update to match the default alarm widget, and not show itself when an alarm is hidden.
|
||||||
|
0.04: Fix check for active alarm
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "widalarmeta",
|
"id": "widalarmeta",
|
||||||
"name": "Alarm & Timer ETA",
|
"name": "Alarm & Timer ETA",
|
||||||
"shortName": "Alarm ETA",
|
"shortName": "Alarm ETA",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
|
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
const times = alarms
|
const times = alarms
|
||||||
.map(alarm => {
|
.map(alarm =>
|
||||||
alarm.hidden !== true
|
alarm.hidden !== true
|
||||||
&& require("sched").getTimeToAlarm(alarm)
|
&& require("sched").getTimeToAlarm(alarm)
|
||||||
})
|
)
|
||||||
.filter(a => a !== undefined);
|
.filter(a => a !== undefined);
|
||||||
const next = times.length > 0 ? Math.min.apply(null, times) : 0;
|
const next = times.length > 0 ? Math.min.apply(null, times) : 0;
|
||||||
let calcWidth = 0;
|
let calcWidth = 0;
|
||||||
|
|
|
||||||