Merge branch 'espruino:master' into pongclock
|
@ -4,3 +4,4 @@
|
|||
0.04: Obey system quiet mode
|
||||
0.05: Battery optimisation, add the pause option, bug fixes
|
||||
0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night
|
||||
0.07: Fix bug on the cutting edge firmware
|
|
@ -1,3 +1,10 @@
|
|||
(function () {
|
||||
// load variable before defining functions cause it can trigger a ReferenceError
|
||||
const activityreminder = require("activityreminder");
|
||||
const storage = require("Storage");
|
||||
const activityreminder_settings = activityreminder.loadSettings();
|
||||
let activityreminder_data = activityreminder.loadData();
|
||||
|
||||
function drawAlert() {
|
||||
E.showPrompt("Inactivity detected", {
|
||||
title: "Activity reminder",
|
||||
|
@ -31,12 +38,9 @@ function run() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const activityreminder = require("activityreminder");
|
||||
const storage = require("Storage");
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
const activityreminder_settings = activityreminder.loadSettings();
|
||||
const activityreminder_data = activityreminder.loadData();
|
||||
run();
|
||||
|
||||
})();
|
|
@ -1,3 +1,14 @@
|
|||
(function () {
|
||||
// load variable before defining functions cause it can trigger a ReferenceError
|
||||
const activityreminder = require("activityreminder");
|
||||
const activityreminder_settings = activityreminder.loadSettings();
|
||||
let activityreminder_data = activityreminder.loadData();
|
||||
|
||||
if (activityreminder_data.firstLoad) {
|
||||
activityreminder_data.firstLoad = false;
|
||||
activityreminder.saveData(activityreminder_data);
|
||||
}
|
||||
|
||||
function run() {
|
||||
if (isNotWorn()) return;
|
||||
let now = new Date();
|
||||
|
@ -29,9 +40,9 @@ function isNotWorn() {
|
|||
|
||||
function isDuringAlertHours(h) {
|
||||
if (activityreminder_settings.startHour < activityreminder_settings.endHour) { // not passing through midnight
|
||||
return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour)
|
||||
return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour);
|
||||
} else { // passing through midnight
|
||||
return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour)
|
||||
return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,18 +59,12 @@ Bangle.on('midnight', function() {
|
|||
}
|
||||
});
|
||||
|
||||
const activityreminder = require("activityreminder");
|
||||
const activityreminder_settings = activityreminder.loadSettings();
|
||||
|
||||
if (activityreminder_settings.enabled) {
|
||||
const activityreminder_data = activityreminder.loadData();
|
||||
if(activityreminder_data.firstLoad){
|
||||
activityreminder_data.firstLoad = false;
|
||||
activityreminder.saveData(activityreminder_data);
|
||||
}
|
||||
setInterval(run, 60000);
|
||||
/* todo in a futur release
|
||||
increase setInterval time to something that is still sensible (5 mins ?)
|
||||
when we added a settimer
|
||||
*/
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const storage = require("Storage");
|
||||
|
||||
exports.loadSettings = function () {
|
||||
return Object.assign({
|
||||
enabled: true,
|
||||
|
@ -10,20 +8,20 @@ exports.loadSettings = function () {
|
|||
pauseDelayMin: 120,
|
||||
minSteps: 50,
|
||||
tempThreshold: 27
|
||||
}, storage.readJSON("activityreminder.s.json", true) || {});
|
||||
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
|
||||
};
|
||||
|
||||
exports.writeSettings = function (settings) {
|
||||
storage.writeJSON("activityreminder.s.json", settings);
|
||||
require("Storage").writeJSON("activityreminder.s.json", settings);
|
||||
};
|
||||
|
||||
exports.saveData = function (data) {
|
||||
storage.writeJSON("activityreminder.data.json", data);
|
||||
require("Storage").writeJSON("activityreminder.data.json", data);
|
||||
};
|
||||
|
||||
exports.loadData = function () {
|
||||
let health = Bangle.getHealthStatus("day");
|
||||
const data = Object.assign({
|
||||
let data = Object.assign({
|
||||
firstLoad: true,
|
||||
stepsDate: new Date(),
|
||||
stepsOnDate: health.steps,
|
||||
|
@ -31,7 +29,7 @@ exports.loadData = function () {
|
|||
dismissDate: new Date(1970),
|
||||
pauseDate: new Date(1970),
|
||||
},
|
||||
storage.readJSON("activityreminder.data.json") || {});
|
||||
require("Storage").readJSON("activityreminder.data.json") || {});
|
||||
|
||||
if(typeof(data.stepsDate) == "string")
|
||||
data.stepsDate = new Date(data.stepsDate);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Activity Reminder",
|
||||
"shortName":"Activity Reminder",
|
||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "tool,activity",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(function (back) {
|
||||
// Load settings
|
||||
const activityreminder = require("activityreminder");
|
||||
const settings = activityreminder.loadSettings();
|
||||
let settings = activityreminder.loadSettings();
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Basic agenda with events from GB
|
|
@ -0,0 +1,3 @@
|
|||
# Agenda
|
||||
|
||||
Basic agenda reading the events synchronised from GadgetBridge
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwg1yhGIxAPMBwIPFhH//GAC5n/C4oHBC5/IGwoXBHQQAKC4OIFAWOxHv9GO9wAKI4XoC4foEIIWLC4IABC4gIBFxnuE4IqBC4gARC4ZzNAAwXaxe7ACO4C625C4m4xIJBzAeCxGbCAOIFgQOBC4pOBxe4AYIPBAYQKCAYYXE3GL/ADBx/oxb3BC4X+xG4xwOBC4uP/YDB54MBf4Po3eM/4XBx/+C4pTBGIIkBLgOYAYIvB9GJBwI6BL45zCL4aCCL4h3GU64ALdYS1CI55bBAAgXFO4mMO4QDBDIO/////YxBU53IxIVB/GfDAWYa5wtC/GPAYWIL4wXBL4oSBC4jcBC4m4QIWYSwWIIQIAG/CnMMAIAC/JLCMIIvMIwZHFJAJfLC5yPHAYIRDAoy/KCIi7BMon4d4+Od4IXBxAZBEQLtB/+YxIXDL4SLCL4WPzAXCNgRFBLIKnKLIrcEI4gXNAAp3CxGZAAzCBC5KnCKAIAICxBlBC4IAJxG/C4/4wAXLhBgD/IcD3AXMGAIqDDgRGNGAoXDFxxhEI4W4FxwwCaoYWBFx4YDAAQWRAEQ"))
|
|
@ -0,0 +1,127 @@
|
|||
/* CALENDAR is a list of:
|
||||
{id:int,
|
||||
type,
|
||||
timestamp,
|
||||
durationInSeconds,
|
||||
title,
|
||||
description,
|
||||
location,
|
||||
allDay: bool,
|
||||
}
|
||||
*/
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var FILE = "android.calendar.json";
|
||||
|
||||
var Locale = require("locale");
|
||||
|
||||
var fontSmall = "6x8";
|
||||
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
|
||||
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
|
||||
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
|
||||
|
||||
//FIXME maybe write the end from GB already? Not durationInSeconds here (or do while receiving?)
|
||||
var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
|
||||
|
||||
CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp)
|
||||
|
||||
function getDate(timestamp) {
|
||||
return new Date(timestamp*1000);
|
||||
}
|
||||
function formatDateLong(date, includeDay) {
|
||||
if(includeDay)
|
||||
return Locale.date(date)+" "+Locale.time(date,1);
|
||||
return Locale.time(date,1);
|
||||
}
|
||||
function formatDateShort(date) {
|
||||
return Locale.date(date).replace(/\d\d\d\d/,"")+Locale.time(date,1);
|
||||
}
|
||||
|
||||
var lines = [];
|
||||
function showEvent(ev) {
|
||||
var bodyFont = fontBig;
|
||||
if(!ev) return;
|
||||
g.setFont(bodyFont);
|
||||
//var lines = [];
|
||||
if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10)
|
||||
var titleCnt = lines.length;
|
||||
var start = getDate(ev.timestamp);
|
||||
var end = getDate((+ev.timestamp) + (+ev.durationInSeconds));
|
||||
var includeDay = true;
|
||||
if (titleCnt) lines.push(""); // add blank line after title
|
||||
if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
|
||||
includeDay = false;
|
||||
if(includeDay) {
|
||||
lines = lines.concat(
|
||||
/*LANG*/"Start:",
|
||||
g.wrapString(formatDateLong(start, includeDay), g.getWidth()-10),
|
||||
/*LANG*/"End:",
|
||||
g.wrapString(formatDateLong(end, includeDay), g.getWidth()-10));
|
||||
} else {
|
||||
lines = lines.concat(
|
||||
g.wrapString(Locale.date(start), g.getWidth()-10),
|
||||
g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay), g.getWidth()-10),
|
||||
g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay), g.getWidth()-10));
|
||||
}
|
||||
if(ev.location)
|
||||
lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10));
|
||||
if(ev.description)
|
||||
lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10));
|
||||
lines = lines.concat(["",/*LANG*/"< Back"]);
|
||||
E.showScroller({
|
||||
h : g.getFontHeight(), // height of each menu item in pixels
|
||||
c : lines.length, // number of menu items
|
||||
// a function to draw a menu item
|
||||
draw : function(idx, r) {
|
||||
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
|
||||
g.setBgColor(idx<titleCnt ? g.theme.bg2 : g.theme.bg).
|
||||
setColor(idx<titleCnt ? g.theme.fg2 : g.theme.fg).
|
||||
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
|
||||
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
|
||||
}, select : function(idx) {
|
||||
if (idx>=lines.length-2)
|
||||
showList();
|
||||
},
|
||||
back : () => showList()
|
||||
});
|
||||
}
|
||||
|
||||
function showList() {
|
||||
if(CALENDAR.length == 0) {
|
||||
E.showMessage("No events");
|
||||
return;
|
||||
}
|
||||
E.showScroller({
|
||||
h : 52,
|
||||
c : Math.max(CALENDAR.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
|
||||
draw : function(idx, r) {"ram"
|
||||
var ev = CALENDAR[idx];
|
||||
g.setColor(g.theme.fg);
|
||||
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
|
||||
if (!ev) return;
|
||||
var isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
|
||||
var x = r.x+2, title = ev.title;
|
||||
var body = formatDateShort(getDate(ev.timestamp))+"\n"+ev.location;
|
||||
var m = ev.title+"\n"+ev.location, longBody=false;
|
||||
if (title) g.setFontAlign(-1,-1).setFont(fontBig)
|
||||
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x,r.y+2);
|
||||
if (body) {
|
||||
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
|
||||
var l = g.wrapString(body, r.w-(x+14));
|
||||
if (l.length>3) {
|
||||
l = l.slice(0,3);
|
||||
l[l.length-1]+="...";
|
||||
}
|
||||
longBody = l.length>2;
|
||||
g.drawString(l.join("\n"), x+10,r.y+20);
|
||||
}
|
||||
//if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2);
|
||||
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
|
||||
},
|
||||
select : idx => showEvent(CALENDAR[idx]),
|
||||
back : () => load()
|
||||
});
|
||||
}
|
||||
showList();
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.02",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
"tags": "agenda",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"agenda.app.js","url":"agenda.js"},
|
||||
{"name":"agenda.settings.js","url":"settings.js"},
|
||||
{"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.7 KiB |
|
@ -0,0 +1,37 @@
|
|||
(function(back) {
|
||||
function gbSend(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
|
||||
var mainmenu = {
|
||||
"" : { "title" : "Agenda" },
|
||||
"< Back" : back,
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
|
||||
/*LANG*/"Force calendar sync" : () => {
|
||||
if(NRF.getSecurityStatus().connected) {
|
||||
E.showPrompt(/*LANG*/"Do you want to also clear the internal database first?", {
|
||||
buttons: {/*LANG*/"Yes": 1, /*LANG*/"No": 2, /*LANG*/"Cancel": 3}
|
||||
}).then((v)=>{
|
||||
switch(v) {
|
||||
case 1:
|
||||
require("Storage").writeJSON("android.calendar.json",[]);
|
||||
CALENDAR = [];
|
||||
/* falls through */
|
||||
case 2:
|
||||
gbSend({t:"force_calendar_sync", ids: CALENDAR.map(e=>e.id)});
|
||||
E.showAlert(/*LANG*/"Request sent to the phone").then(()=>E.showMenu(mainmenu));
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
E.showMenu(mainmenu);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
E.showAlert(/*LANG*/"You are not connected").then(()=>E.showMenu(mainmenu));
|
||||
}
|
||||
},
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
|
@ -90,6 +90,35 @@
|
|||
sched.setAlarms(alarms);
|
||||
sched.reload();
|
||||
},
|
||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||
//simple package with events all together
|
||||
"calendarevents" : function() {
|
||||
require("Storage").writeJSON("android.calendar.json", event.events);
|
||||
},
|
||||
//add and remove events based on activity on phone (pebble-like)
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
var i = cal.findIndex(e=>e.id==event.id);
|
||||
if(i<0)
|
||||
cal.push(event);
|
||||
else
|
||||
cal[i] = event;
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) return;
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
//triggered by GB, send all ids
|
||||
"force_calendar_sync_start" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
@ -15,6 +15,6 @@
|
|||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"android.settings.json"}],
|
||||
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}],
|
||||
"sortorder": -8
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: setTimeout bug fix; no leading zero on date; lightmode; 12 hour format; cleanup
|
||||
0.03: Internationalisation; bug fix - battery icon responds promptly to charging state
|
||||
0.04: bug fix
|
||||
0.05: proper fix for the race condition in queueDraw()
|
||||
|
|
|
@ -12,13 +12,12 @@ Graphics.prototype.setFontOpenSans = function(scale) {
|
|||
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
function queueDraw(millis_now) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function () {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60300 - (Date.now() % 60000)); // We aim for 300ms into the next minute to ensure we make it!
|
||||
}, 60000 - (millis_now % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
|
@ -70,7 +69,7 @@ function draw() {
|
|||
|
||||
// widget redraw
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
queueDraw(date.getTime());
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower', on => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "bigdclock",
|
||||
"name": "Big digit clock containing just the essentials",
|
||||
"shortName":"Big digit clk",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
|
||||
"icon": "bigdclock.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.7: Update Rocket Sequences Scope to not use memory all time
|
||||
0.8: Update Some Variable Scopes to not use memory until need
|
||||
0.9: Remove ESLint spaces
|
||||
0.10: Show daily steps, heartrate and the temperature if weather information is available.
|
|
@ -6,5 +6,6 @@ Clock with Space Cassio Watch Style.
|
|||
|
||||
It displays current temperature,day,steps,battery.heartbeat and weather.
|
||||
|
||||
|
||||
**To-do**:
|
||||
Integrate heartbeat and Weather, Align and change size of some elements.
|
||||
Align and change size of some elements.
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
const storage = require('Storage');
|
||||
|
||||
require("Font6x12").add(Graphics);
|
||||
require("Font8x12").add(Graphics);
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
let ClockInterval;
|
||||
let RocketInterval;
|
||||
let BatteryInterval;
|
||||
|
||||
function bigThenSmall(big, small, x, y) {
|
||||
g.setFont("7x11Numeric7Seg", 2);
|
||||
g.drawString(big, x, y);
|
||||
|
@ -14,16 +12,6 @@ function bigThenSmall(big, small, x, y) {
|
|||
g.drawString(small, x, y);
|
||||
}
|
||||
|
||||
function ClearIntervals(inoreclock) {
|
||||
if (RocketInterval) clearInterval(RocketInterval);
|
||||
if (BatteryInterval) clearInterval(BatteryInterval);
|
||||
RocketInterval = undefined;
|
||||
BatteryInterval = undefined;
|
||||
if (inoreclock) return;
|
||||
if (ClockInterval) clearInterval(ClockInterval);
|
||||
ClockInterval = undefined;
|
||||
}
|
||||
|
||||
function getBackgroundImage() {
|
||||
return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA=="));
|
||||
}
|
||||
|
@ -41,15 +29,31 @@ function getRocketSequences() {
|
|||
};
|
||||
}
|
||||
|
||||
let rocket_sequence = 1;
|
||||
|
||||
let settings = require('Storage').readJSON("cassioWatch.settings.json", true) || {};
|
||||
let rocketSequence = 1;
|
||||
let settings = storage.readJSON("cassioWatch.settings.json", true) || {};
|
||||
let rocketSpeed = settings.rocketSpeed || 700;
|
||||
delete settings;
|
||||
|
||||
g.clear();
|
||||
// schedule a draw for the next minute
|
||||
let rocketInterval;
|
||||
var drawTimeout;
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function DrawClock() {
|
||||
|
||||
function clearIntervals() {
|
||||
if (rocketInterval) clearInterval(rocketInterval);
|
||||
rocketInterval = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
function drawClock() {
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.clearRect(80, 57, 170, 96);
|
||||
g.setColor(0, 255, 255);
|
||||
|
@ -66,23 +70,57 @@ function DrawClock() {
|
|||
g.drawString(time < 10 ? "0" + time : time, 78, 137);
|
||||
}
|
||||
|
||||
function DrawBattery() {
|
||||
function drawBattery() {
|
||||
bigThenSmall(E.getBattery(), "%", 135, 21);
|
||||
}
|
||||
|
||||
function DrawRocket() {
|
||||
function drawRocket() {
|
||||
let Rocket = getRocketSequences();
|
||||
g.clearRect(5, 62, 63, 115);
|
||||
g.setColor(0, 255, 255);
|
||||
g.drawRect(5, 62, 63, 115);
|
||||
g.fillRect(5, 62, 63, 115);
|
||||
g.drawImage(Rocket[rocket_sequence], 5, 65, { scale: 0.7 });
|
||||
g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 });
|
||||
g.setColor(0, 0, 0);
|
||||
rocket_sequence = rocket_sequence + 1;
|
||||
if (rocket_sequence > 8) rocket_sequence = 1;
|
||||
rocketSequence = rocketSequence + 1;
|
||||
if(rocketSequence > 8) rocketSequence = 1;
|
||||
}
|
||||
|
||||
function DrawScene() {
|
||||
function getTemperature(){
|
||||
try {
|
||||
var weatherJson = storage.readJSON('weather.json');
|
||||
var weather = weatherJson.weather;
|
||||
return Math.round(weather.temp-273.15);
|
||||
|
||||
} catch(ex) {
|
||||
print(ex)
|
||||
return "?"
|
||||
}
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
steps = WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
} else {
|
||||
steps = Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
} catch(ex) {
|
||||
// In case we failed, we can only show 0 steps.
|
||||
return "? k";
|
||||
}
|
||||
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
}
|
||||
|
||||
|
||||
function draw() {
|
||||
queueDraw();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
g.setColor(0, 255, 255);
|
||||
|
@ -94,40 +132,44 @@ function DrawScene() {
|
|||
g.drawString("Launching Process", 30, 20);
|
||||
g.setFont("8x12");
|
||||
g.drawString("ACTIVATE", 40, 35);
|
||||
|
||||
g.setFontAlign(0,-1);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString("30", 142, 132);
|
||||
g.drawString("55", 95, 98);
|
||||
g.setFont("8x12", 1);
|
||||
g.drawString(Bangle.getStepCount(), 143, 104);
|
||||
ClockInterval = setInterval(DrawClock, 30000);
|
||||
DrawClock();
|
||||
RocketInterval = setInterval(DrawRocket, rocketSpeed);
|
||||
DrawRocket();
|
||||
BatteryInterval = setInterval(DrawBattery, 5 * 60000);
|
||||
DrawBattery();
|
||||
g.drawString(getTemperature(), 155, 132);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98);
|
||||
g.drawString(getSteps(), 158, 98);
|
||||
|
||||
g.setFontAlign(-1,-1);
|
||||
drawClock();
|
||||
drawRocket();
|
||||
drawBattery();
|
||||
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
}
|
||||
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (!on) {
|
||||
g.clear();
|
||||
ClearIntervals(true);
|
||||
if (on) {
|
||||
draw();
|
||||
} else {
|
||||
clearIntervals();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.on("lock", (locked) => {
|
||||
if (locked) {
|
||||
ClearIntervals(true);
|
||||
} else {
|
||||
ClearIntervals();
|
||||
DrawScene();
|
||||
clearIntervals();
|
||||
draw();
|
||||
if (!locked) {
|
||||
rocketInterval = setInterval(drawRocket, rocketSpeed);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Load widgets, but don't show them
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setUI("clock");
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
Bangle.setUI("clock");
|
||||
DrawScene();
|
||||
|
||||
if (Bangle.isLocked()) {
|
||||
ClearIntervals(true);
|
||||
}
|
||||
draw();
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Animated Clock with Space Cassio Watch Style",
|
||||
"screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
|
||||
"icon": "app.png",
|
||||
"version": "0.9",
|
||||
"version": "0.10",
|
||||
"type": "clock",
|
||||
"tags": "clock, weather, cassio, retro",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
|
|
|
@ -33,8 +33,6 @@ const clock = new ClockFace({
|
|||
this.scale = g.getWidth() / this.viewport.width;
|
||||
this.centerTimeScaleX = this.center.x + 32 * this.scale;
|
||||
this.centerDatesScaleX = this.center.x + 40 * this.scale;
|
||||
|
||||
this.showWeekNum = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true))["showWeekNum"];
|
||||
},
|
||||
draw: function (date) {
|
||||
const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0);
|
||||
|
@ -55,6 +53,7 @@ const clock = new ClockFace({
|
|||
if (this.showWeekNum) g.drawString(format(ISO8601_week_no(date)), this.centerDatesScaleX, this.center.y + 15 * this.scale);
|
||||
g.drawString(monthName, this.centerDatesScaleX, this.center.y + 48 * this.scale);
|
||||
g.drawString(dayName, this.centerDatesScaleX, this.center.y + 66 * this.scale);
|
||||
}
|
||||
},
|
||||
settingsFile: "ffcniftya.json"
|
||||
});
|
||||
clock.start();
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Call setUI before loading widgets
|
||||
Fix bug with black being unselectable
|
||||
Improve settings page
|
||||
0.04: Use ClockFace library
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
|
||||
const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED
|
||||
var scale;
|
||||
var screen;
|
||||
var center;
|
||||
var buf;
|
||||
var img;
|
||||
|
||||
/* Clock *********************************************/
|
||||
const scale = g.getWidth() / 176;
|
||||
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getHeight() - 24,
|
||||
};
|
||||
|
||||
const center = {
|
||||
x: screen.width / 2,
|
||||
y: screen.height / 2,
|
||||
};
|
||||
|
||||
function d02(value) {
|
||||
function format(value) {
|
||||
return ("0" + value).substr(-2);
|
||||
}
|
||||
|
||||
|
@ -22,37 +12,45 @@ function renderEllipse(g) {
|
|||
g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale);
|
||||
}
|
||||
|
||||
function renderText(g) {
|
||||
const now = new Date();
|
||||
function renderText(g, date) {
|
||||
const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0);
|
||||
const month = date.getMonth() + 1;
|
||||
|
||||
const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0));
|
||||
const minutes = d02(now.getMinutes());
|
||||
const day = d02(now.getDate());
|
||||
const month = d02(now.getMonth() + 1);
|
||||
const year = now.getFullYear();
|
||||
|
||||
const month2 = require("locale").month(now, 3);
|
||||
const day2 = require("locale").dow(now, 3);
|
||||
const monthName = require("date_utils").month(month, 1);
|
||||
const dayName = require("date_utils").dow(date.getDay(), 1);
|
||||
|
||||
g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
|
||||
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
|
||||
g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale);
|
||||
g.drawString(format(hour), center.x + 32 * scale, center.y - 31 * scale);
|
||||
g.drawString(format(date.getMinutes()), center.x + 32 * scale, center.y + 46 * scale);
|
||||
|
||||
g.setFontAlign(1, 0).setFont("Vector", 16 * scale);
|
||||
g.drawString(year, center.x + 80 * scale, center.y - 42 * scale);
|
||||
g.drawString(month, center.x + 80 * scale, center.y - 26 * scale);
|
||||
g.drawString(day, center.x + 80 * scale, center.y - 10 * scale);
|
||||
g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale);
|
||||
g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale);
|
||||
g.drawString(date.getFullYear(), center.x + 80 * scale, center.y - 42 * scale);
|
||||
g.drawString(format(month), center.x + 80 * scale, center.y - 26 * scale);
|
||||
g.drawString(format(date.getDate()), center.x + 80 * scale, center.y - 10 * scale);
|
||||
g.drawString(monthName, center.x + 80 * scale, center.y + 44 * scale);
|
||||
g.drawString(dayName, center.x + 80 * scale, center.y + 60 * scale);
|
||||
}
|
||||
|
||||
const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, {
|
||||
msb: true
|
||||
});
|
||||
const ClockFace = require("ClockFace");
|
||||
const clock = new ClockFace({
|
||||
init: function () {
|
||||
const appRect = Bangle.appRect;
|
||||
|
||||
function draw() {
|
||||
screen = {
|
||||
width: appRect.w,
|
||||
height: appRect.h
|
||||
};
|
||||
|
||||
const img = {
|
||||
center = {
|
||||
x: screen.width / 2,
|
||||
y: screen.height / 2
|
||||
};
|
||||
|
||||
buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { msb: true });
|
||||
|
||||
scale = g.getWidth() / screen.width;
|
||||
|
||||
img = {
|
||||
width: screen.width,
|
||||
height: screen.height,
|
||||
transparent: 0,
|
||||
|
@ -60,53 +58,23 @@ function draw() {
|
|||
buffer: buf.buffer
|
||||
};
|
||||
|
||||
// cleat screen area
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
|
||||
// default to RED (see settings.js)
|
||||
// don't use || to default because 0 is a valid color
|
||||
this.color = this.color === undefined ? 63488 : this.color;
|
||||
},
|
||||
draw: function (date) {
|
||||
// render outside text with ellipse
|
||||
buf.clear();
|
||||
renderText(buf.setColor(1));
|
||||
renderText(buf.setColor(1), date);
|
||||
renderEllipse(buf.setColor(0));
|
||||
g.setColor(color).drawImage(img, 0, 24);
|
||||
g.setColor(this.color).drawImage(img, 0, 24);
|
||||
|
||||
// render ellipse with inside text
|
||||
buf.clear();
|
||||
renderEllipse(buf.setColor(1));
|
||||
renderText(buf.setColor(0));
|
||||
g.setColor(color).drawImage(img, 0, 24);
|
||||
}
|
||||
|
||||
|
||||
/* Minute Ticker *************************************/
|
||||
|
||||
let ticker;
|
||||
|
||||
function stopTick() {
|
||||
if (ticker) {
|
||||
clearTimeout(ticker);
|
||||
ticker = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function startTick(run) {
|
||||
stopTick();
|
||||
run();
|
||||
ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
/* Init **********************************************/
|
||||
|
||||
g.clear();
|
||||
startTick(draw);
|
||||
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (on) {
|
||||
startTick(draw);
|
||||
} else {
|
||||
stopTick();
|
||||
}
|
||||
renderText(buf.setColor(0), date);
|
||||
g.setColor(this.color).drawImage(img, 0, 24);
|
||||
},
|
||||
settingsFile: "ffcniftyb.json"
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
clock.start();
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ffcniftyb",
|
||||
"name": "Nifty-B Clock",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A nifty clock (series B) with time, date and colour configuration",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
1.00: Initial implementation
|
||||
1.01: Bug fixes and performance and visual improvements
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
// globals. TODO: move into an object
|
||||
const digit = [];
|
||||
let part = 0;
|
||||
let endInc = 0;
|
||||
var endGame = false;
|
||||
let goalFrame = 0;
|
||||
var stopped = true;
|
||||
let score0 = 0;
|
||||
let score1 = 0;
|
||||
let inc = 0;
|
||||
let msinc = 0;
|
||||
let seq0 = 0;
|
||||
let seq1 = 0;
|
||||
let goaler = -1;
|
||||
const w = g.getWidth();
|
||||
let owner = -1;
|
||||
|
||||
const dash = {
|
||||
width: 75,
|
||||
|
@ -6,6 +21,7 @@ const dash = {
|
|||
bpp: 1,
|
||||
buffer: require('heatshrink').decompress(atob('AH4A/AH4A/AH4A/AH4A/AB0D/4AB+AJEBAX/BAk/CQ8PCQ4kDCQoIDCQgkDCQgkECQgIE4ASHFxH8JRgSEEgYSEPJAkEAH4A/AH4A/AH4A/AH4A/ACg='))
|
||||
};
|
||||
|
||||
function loadDigits () {
|
||||
digit[0] = {
|
||||
width: 80,
|
||||
|
@ -58,8 +74,8 @@ function loadDigits () {
|
|||
digit[7] = {
|
||||
width: 80,
|
||||
height: 128,
|
||||
bpp: 1,
|
||||
buffer: require('heatshrink').decompress(atob('AGUH/4AE/wJBgYJF/gJBgIJF+AeCBJN/BIngsAJBn4JE4HgBIMfBImBBIUPBIkDBIRQE/0HBIRQE/kPBIRQE/EfBIRQE+E/BIZQD8AJEKAfABYIJCKAYsBBIYADIAIJHKgIJHNAIJ/BP4J/BP4Jzg//4AJGgf/wAJGgP/BAwAB/wJIvgJInAJIiAJIAH5PPMZJ3JRZCfJWZLHJfM4J/BP4J/BP4JNg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIeg4JIgYJIgIJIgAJJsAJIkAJIAH4AQA='))
|
||||
bpp: 4,
|
||||
buffer : require("heatshrink").decompress(atob("AH4A/AEtVADdQE5Nf/4AayAnJgoma/J4LKDR2KKDZODUMadChKhiJwefE5RQXJwbxLKCxOEE5hQVJwgnNKCZOFE5pQTJwonOKCJOGE5xQRD4Q8EE5xQPJw4nPgFZzIAMqCdFE6IARJwgnhJwonhJwonhe5In/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4nlr4mE/NQE78FE4n1Ez5QGE0JQEJ0RQETsBQFJ0gABrJOkAH4A/AH4A/ADNZqAmkgv/yAnkr///JQjJwIABypOkAAP5J0oABUMJODKAShgEwhQiE/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/AA+fE80JE8xQGE8JQFE8JQFE8RQEE8RQEE8ZQDE8ZQDE8hQCE8hQCE8pQBE8pQBE80JE80AE84A/AH4A/AH4A/AAQA=="))
|
||||
};
|
||||
|
||||
digit[8] = {
|
||||
|
@ -170,9 +186,6 @@ const gol11 = {
|
|||
|
||||
loadDigits();
|
||||
|
||||
let goalFrame = 0;
|
||||
let score0 = 0;
|
||||
let score1 = 0;
|
||||
|
||||
function printNumber (n, x, y, options) {
|
||||
if (n > 9 || n < -1) {
|
||||
|
@ -197,13 +210,7 @@ function printNumber (n, x, y, options) {
|
|||
g.drawImage(img, x, y, options);
|
||||
}
|
||||
}
|
||||
let inc = 0;
|
||||
let msinc = 0;
|
||||
let seq0 = 0;
|
||||
let seq1 = 0;
|
||||
let goaler = -1;
|
||||
const w = g.getWidth();
|
||||
let owner = -1;
|
||||
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.clear();
|
||||
g.setColor(1, 1, 1);
|
||||
|
@ -247,43 +254,63 @@ function onStop () {
|
|||
refresh();
|
||||
refresh_ms();
|
||||
}
|
||||
var stopped = true;
|
||||
Bangle.on('tap', function (pos) {
|
||||
console.log('touch', pos);
|
||||
if (endGame) {
|
||||
|
||||
function onButtonPress() {
|
||||
console.log('on.tap');
|
||||
setWatch(() => {
|
||||
onButtonPress();
|
||||
}, BTN1);
|
||||
Bangle.beep();
|
||||
if (endGame) {
|
||||
score0 = 0;
|
||||
score1 = 0;
|
||||
seq0 = 0;
|
||||
seq1 = 0;
|
||||
part = 0;
|
||||
inc = 0;
|
||||
msinc = 0;
|
||||
stopped = true;
|
||||
endGame = false;
|
||||
} else {
|
||||
if (inc == 0) {
|
||||
autogame();
|
||||
// autogame();
|
||||
stopped = false;
|
||||
} else {
|
||||
onStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setWatch(() => {
|
||||
onButtonPress();
|
||||
}, BTN1);
|
||||
/*Bangle.on('tap', function () {
|
||||
onButtonPress();
|
||||
});
|
||||
*/
|
||||
g.setFont12x20(3);
|
||||
let part = 0;
|
||||
let endInc = 0;
|
||||
var endGame = false;
|
||||
|
||||
function refresh () {
|
||||
g.clear();
|
||||
if (inc > 59) {
|
||||
inc = 0;
|
||||
part++;
|
||||
}
|
||||
if (part >= 2 && inc > 30) {
|
||||
part = 2;
|
||||
Bangle.buzz();
|
||||
endGame = true;
|
||||
endInc = inc;
|
||||
inc = 0;
|
||||
}
|
||||
if (inc > 44) {
|
||||
inc = 0;
|
||||
if (part < 2) {
|
||||
part++;
|
||||
}
|
||||
if (part >= 2) {
|
||||
if (score0 != score1) {
|
||||
Bangle.buzz();
|
||||
endGame = true;
|
||||
endInc = inc;
|
||||
inc = 0;
|
||||
|
@ -342,6 +369,12 @@ function refresh_pixels () {
|
|||
let bx = (owner == 0) ? w / 3 : w / 2;
|
||||
bx += 2;
|
||||
g.drawImage(frame4 ? ball0 : ball1, bx, 10, { scale: 5 });
|
||||
const liney = 60;
|
||||
if (owner) {
|
||||
g.drawLine(w-8, liney, 2*(w/3), liney);
|
||||
} else {
|
||||
g.drawLine(8, liney, w/3, liney);
|
||||
}
|
||||
}
|
||||
let dots = 0;
|
||||
function refresh_dots () {
|
||||
|
@ -437,4 +470,5 @@ function autogame () {
|
|||
}
|
||||
|
||||
Bangle.setOptions({ lockTimeout: 0, backlightTimeout: 0 });
|
||||
autogame();
|
||||
// autogame();
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ function onInit(device) {
|
|||
if (crc==46757280) version = "2v11.58";
|
||||
if (crc==3508163280 || crc==1418074094) version = "2v12";
|
||||
if (crc==4056371285) version = "2v13";
|
||||
if (crc==1038322422) version = "2v14";
|
||||
if (!ok) {
|
||||
version += `(⚠ update required)`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
0.01: Release for Bangle 2 (2021/11/18)
|
||||
0.02: Internal id update to wid_* as per Gordon's request (2021/11/21)
|
||||
0.03: Support dark themes
|
||||
0.04: Increase screen update rate when charging
|
||||
0.05: Deleting Background - making Font larger
|
||||
0.06: Fixing refresh issues
|
||||
0.07
|
|
@ -0,0 +1,15 @@
|
|||
# A Battery Widget (with percentage)
|
||||
|
||||
Show the current battery level and charging status in the top right of the clock, with charge percentage
|
||||
|
||||
* Works with Bangle 2
|
||||
* Simple design, no settings
|
||||
* Red when the batterly level is below 30%
|
||||
* Blue when charging
|
||||
* 40 pixels wide
|
||||
|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
||||
Mod by Hank
|
After Width: | Height: | Size: 65 KiB |
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "hwid_a_battery_widget",
|
||||
"name": "A Battery Widget (with percentage) - Hanks Mod",
|
||||
"shortName":"H Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.07",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Simple and slim battery widget with charge status and percentage",
|
||||
"tags": "widget,battery",
|
||||
"storage": [
|
||||
{"name":"hwid_a_battery_widget.wid.js","url":"widget.js"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
(function(){
|
||||
const intervalLow = 60000; // update time when not charging
|
||||
const intervalHigh = 2000; // update time when charging
|
||||
var old_l;
|
||||
|
||||
let COLORS = {
|
||||
'white': g.theme.dark ? "#000" : "#fff",
|
||||
'black': g.theme.dark ? "#fff" : "#000",
|
||||
'charging': "#08f",
|
||||
'high': g.theme.dark ? "#fff" : "#000",
|
||||
'low': "#f00",
|
||||
};
|
||||
|
||||
const levelColor = (l) => {
|
||||
if (Bangle.isCharging()) return COLORS.charging;
|
||||
if (l >= 30) return COLORS.high;
|
||||
return COLORS.low;
|
||||
};
|
||||
|
||||
function draw() {
|
||||
var s = 29;
|
||||
var x = this.x, y = this.y;
|
||||
const l = E.getBattery();
|
||||
let xl = x+4+l*(s-12)/100;
|
||||
if (l != old_l){ // Delete the old value from screen
|
||||
old_l = l;
|
||||
let xl_old = x+4+old_l*(s-12)/100;
|
||||
g.setColor(COLORS.white);
|
||||
// g.fillRect(x+2,y+5,x+s-6,y+18);
|
||||
g.fillRect(x,y,xl+4,y+16+3); //Clear
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('Vector',16);
|
||||
g.drawString(old_l, x + 14, y + 10);
|
||||
g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar
|
||||
}
|
||||
|
||||
g.setColor(levelColor(l));
|
||||
g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar
|
||||
g.fillRect((x+4+100*(s-12)/100)-1,y+14+3,x+4+100*(s-12)/100,y+16+3); // charging bar "full mark"
|
||||
// Show percentage
|
||||
g.setColor(COLORS.black);
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('Vector',16);
|
||||
g.drawString(l, x + 14, y + 10);
|
||||
|
||||
if (Bangle.isCharging()) changeInterval(id, intervalHigh);
|
||||
else changeInterval(id, intervalLow);
|
||||
}
|
||||
|
||||
Bangle.on('charging',function(charging) { draw(); });
|
||||
var id = setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), intervalLow);
|
||||
|
||||
WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
||||
})();
|
After Width: | Height: | Size: 877 B |
|
@ -0,0 +1,6 @@
|
|||
0.15: Initial release - be patient as this is the first try :)
|
||||
0.16: Fix timing
|
||||
0.17: Fix hours
|
||||
0.18: Code cleanup and major changes with seconds timing. New feature: if watch is locked, seconds get refreshed every 10 seconds.
|
||||
0.19: Fix PM Hours
|
||||
0.20: Add theme support
|
|
@ -0,0 +1,31 @@
|
|||
# Hanks World Clock - See the time in four locations
|
||||
|
||||
In addition to the main clock and date in your current location, you can add up to three other locations. Great for travel or remote working.
|
||||
Additionally we show the sunset/sunrise and seconds for the current location and the day name is shown in your locale.
|
||||
If watch is locked, seconds get refreshed every 10 seconds.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Provide names and the UTC offsets for up to three other timezones in the app store. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India).
|
||||
|
||||
The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if you have feature requests or notice bugs.
|
||||
|
||||
## Creator
|
||||
|
||||
Created by Hank.
|
||||
|
||||
Based on the great work of
|
||||
=================
|
||||
World Clock - 4 time zones
|
||||
Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock).
|
||||
===== a n d =====
|
||||
Sun Clock
|
||||
[Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock)
|
||||
=================
|
|
@ -0,0 +1,327 @@
|
|||
const big = g.getWidth()>200;
|
||||
// Font for primary time and date
|
||||
const primaryTimeFontSize = big?6:5;
|
||||
const primaryDateFontSize = big?3:2;
|
||||
require("Font5x9Numeric7Seg").add(Graphics);
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
|
||||
// Font for single secondary time
|
||||
const secondaryTimeFontSize = 4;
|
||||
const secondaryTimeZoneFontSize = 2;
|
||||
|
||||
// Font / columns for multiple secondary times
|
||||
const secondaryRowColFontSize = 2;
|
||||
const xcol1 = 10;
|
||||
const xcol2 = g.getWidth() - xcol1;
|
||||
|
||||
const font = "6x8";
|
||||
|
||||
/* TODO: we could totally use 'Layout' here and
|
||||
avoid a whole bunch of hard-coded offsets */
|
||||
|
||||
const xyCenter = g.getWidth() / 2;
|
||||
const xyCenterSeconds = xyCenter + (big ? 85 : 68);
|
||||
const yAmPm = xyCenter - (big ? 70 : 48);
|
||||
const yposTime = big ? 70 : 55;
|
||||
const yposTime2 = yposTime + (big ? 100 : 60);
|
||||
const yposDate = big ? 135 : 95;
|
||||
const yposWorld = big ? 170 : 120;
|
||||
|
||||
const OFFSET_TIME_ZONE = 0;
|
||||
const OFFSET_HOURS = 1;
|
||||
|
||||
var PosInterval = 0;
|
||||
|
||||
var offsets = require("Storage").readJSON("hworldclock.settings.json") || [];
|
||||
|
||||
//=======Sun
|
||||
setting = require("Storage").readJSON("setting.json",1);
|
||||
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
|
||||
SunCalc = require("hsuncalc.js");
|
||||
const LOCATION_FILE = "mylocation.json";
|
||||
var rise = "07:00";
|
||||
var set = "20:00";
|
||||
var pos = {altitude: 20, azimuth: 135};
|
||||
var noonpos = {altitude: 37, azimuth: 180};
|
||||
//=======Sun
|
||||
|
||||
var ampm = "AM";
|
||||
|
||||
// TESTING CODE
|
||||
// Used to test offset array values during development.
|
||||
// Uncomment to override secondary offsets value
|
||||
/*
|
||||
const mockOffsets = {
|
||||
zeroOffsets: [],
|
||||
oneOffset: [["UTC", 0]],
|
||||
twoOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
],
|
||||
fourOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
["Denver", -7],
|
||||
["Miami", -5],
|
||||
],
|
||||
};*/
|
||||
|
||||
// Uncomment one at a time to test various offsets array scenarios
|
||||
//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time
|
||||
//offsets = mockOffsets.oneOffset; // should render larger in two rows
|
||||
//offsets = mockOffsets.twoOffsets; // should render two in columns
|
||||
//offsets = mockOffsets.fourOffsets; // should render in columns
|
||||
|
||||
// END TESTING CODE
|
||||
|
||||
// Check settings for what type our clock should be
|
||||
var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
var drawTimeoutSeconds;
|
||||
var secondsTimeout;
|
||||
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
// schedule a draw for the next second
|
||||
function queueDrawSeconds() {
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeoutSeconds = setTimeout(function() {
|
||||
drawTimeoutSeconds = undefined;
|
||||
drawSeconds();
|
||||
//console.log("TO: " + secondsTimeout);
|
||||
}, secondsTimeout - (Date.now() % secondsTimeout));
|
||||
}
|
||||
|
||||
function doublenum(x) {
|
||||
return x < 10 ? "0" + x : "" + x;
|
||||
}
|
||||
|
||||
function getCurrentTimeFromOffset(dt, offset) {
|
||||
return new Date(dt.getTime() + offset * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function updatePos() {
|
||||
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":53.3,"lon":10.1,"location":"Pattensen"};
|
||||
pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
|
||||
times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon);
|
||||
rise = times.sunrise.toString().split(" ")[4].substr(0,5);
|
||||
set = times.sunset.toString().split(" ")[4].substr(0,5);
|
||||
noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
|
||||
}
|
||||
|
||||
|
||||
function drawSeconds() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset();
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
// drawSting centered
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var seconds = time[2];
|
||||
|
||||
g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3);
|
||||
if (g.theme.dark) {
|
||||
g.setColor("#22ff05");
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
//console.log("---");
|
||||
//console.log(seconds);
|
||||
if (Bangle.isLocked()) seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x
|
||||
//console.log(seconds);
|
||||
g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true);
|
||||
queueDrawSeconds();
|
||||
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset();
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
// drawSting centered
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var hours = time[0],
|
||||
minutes = time[1];
|
||||
|
||||
|
||||
if (_12hour){
|
||||
//do 12 hour stuff
|
||||
if (hours > 12) {
|
||||
ampm = "PM";
|
||||
hours = hours - 12;
|
||||
if (hours < 10) hours = doublenum(hours);
|
||||
} else {
|
||||
ampm = "AM";
|
||||
}
|
||||
}
|
||||
|
||||
//g.setFont(font, primaryTimeFontSize);
|
||||
g.setFont("5x9Numeric7Seg",primaryTimeFontSize);
|
||||
if (g.theme.dark) {
|
||||
g.setColor("#22ff05");
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter-10, yposTime, true);
|
||||
|
||||
// am / PM ?
|
||||
if (_12hour){
|
||||
//do 12 hour stuff
|
||||
//var ampm = require("locale").medidian(new Date()); Not working
|
||||
g.setFont("Vector", 17);
|
||||
g.drawString(ampm, xyCenterSeconds, yAmPm, true);
|
||||
}
|
||||
|
||||
drawSeconds(); // To make sure...
|
||||
|
||||
// draw Day, name of month, Date
|
||||
//DATE
|
||||
var localDate = require("locale").date(new Date(), 1);
|
||||
localDate = localDate.substring(0, localDate.length - 5);
|
||||
g.setFont("Vector", 17);
|
||||
g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true);
|
||||
|
||||
g.setFont(font, primaryDateFontSize);
|
||||
// set gmt to UTC+0
|
||||
var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
|
||||
|
||||
// Loop through offset(s) and render
|
||||
offsets.forEach((offset, index) => {
|
||||
dx = getCurrentTimeFromOffset(gmt, offset[OFFSET_HOURS]);
|
||||
hours = doublenum(dx.getHours());
|
||||
minutes = doublenum(dx.getMinutes());
|
||||
|
||||
|
||||
if (offsets.length === 1) {
|
||||
var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
||||
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
||||
const xOffset = 30;
|
||||
g.setFont(font, secondaryTimeFontSize);
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
|
||||
|
||||
// draw Day, name of month, Date
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
g.drawString(date, xyCenter, yposDate, true);
|
||||
} else if (index < 3) {
|
||||
// For > 1 extra timezones, render as columns / rows
|
||||
g.setFont(font, secondaryRowColFontSize);
|
||||
g.setFontAlign(-1, 0);
|
||||
g.drawString(
|
||||
offset[OFFSET_TIME_ZONE],
|
||||
xcol1,
|
||||
yposWorld + index * 15,
|
||||
true
|
||||
);
|
||||
g.setFontAlign(1, 0);
|
||||
g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
|
||||
}
|
||||
});
|
||||
|
||||
g.setFontAlign(-1, 0);
|
||||
g.setFont("Vector",12);
|
||||
g.drawString(`^${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw riseset
|
||||
g.setFontAlign(1, 0);
|
||||
g.drawString(`v${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw riseset
|
||||
|
||||
queueDraw();
|
||||
queueDrawSeconds();
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
g.clear();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
|
||||
|
||||
if (!Bangle.isLocked()) { // Initial state
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
|
||||
|
||||
secondsTimeout = 1000;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeout = undefined;
|
||||
drawTimeoutSeconds = undefined;
|
||||
|
||||
draw(); // draw immediately, queue redraw
|
||||
updatePos();
|
||||
}else{
|
||||
secondsTimeout = 10 * 1000;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeout = undefined;
|
||||
drawTimeoutSeconds = undefined;
|
||||
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
draw(); // draw immediately, queue redraw
|
||||
updatePos();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Bangle.on('lock',on=>{
|
||||
if (!on) { // UNlocked
|
||||
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
|
||||
|
||||
secondsTimeout = 1000;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeout = undefined;
|
||||
drawTimeoutSeconds = undefined;
|
||||
|
||||
draw(); // draw immediately, queue redraw
|
||||
updatePos();
|
||||
}else{ // locked
|
||||
|
||||
secondsTimeout = 10 * 1000;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeout = undefined;
|
||||
drawTimeoutSeconds = undefined;
|
||||
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
draw(); // draw immediately, queue redraw
|
||||
updatePos();
|
||||
}
|
||||
});
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,76 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>You can add up to 3 timezones. Please give a name and UTC offset in hours.
|
||||
If you want less than 3, clear the checkbox to the left.</p>
|
||||
|
||||
<table id="hworldclock-offsets">
|
||||
<tr>
|
||||
<th>Enabled?</th>
|
||||
<th>Name</th>
|
||||
<th>UTC Offset</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var offsets=[];
|
||||
try{
|
||||
var stored = localStorage.getItem('hworldclock-offset-list')
|
||||
if(stored) offsets = JSON.parse(stored);
|
||||
if (!offsets || offsets.length!=3) {
|
||||
throw "Offsets invalid";
|
||||
}
|
||||
} catch(e){
|
||||
offsets=[
|
||||
[true,"London",0],
|
||||
[true,"NY",-5],
|
||||
[true, "Denver",-6],
|
||||
|
||||
];
|
||||
}
|
||||
console.log(offsets);
|
||||
var tbl=document.getElementById("hworldclock-offsets");
|
||||
for (var i=0; i<3; i++) {
|
||||
var $offset = document.createElement('tr')
|
||||
$offset.innerHTML = `
|
||||
<td><input type="checkbox" id="enabled_${i}" ${offsets[i][0]? "checked" : ""}></td>
|
||||
<td><input type="text" id="name_${i}" value="${offsets[i][1]}"></td>
|
||||
<td><input type="number" id="offset_${i}" value="${offsets[i][2]}"></td>`
|
||||
tbl.append($offset);
|
||||
}
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var storage_offsets=[];
|
||||
var app_offsets=[];
|
||||
for (var i=0; i<3; i++) {
|
||||
var checked=document.getElementById("enabled_"+i).checked;
|
||||
var name=document.getElementById("name_"+i).value;
|
||||
var offset=document.getElementById("offset_"+i).value;
|
||||
if (checked) {
|
||||
app_offsets.push([name,offset]);
|
||||
}
|
||||
storage_offsets.push([checked,name,offset]);
|
||||
}
|
||||
console.log(storage_offsets);
|
||||
console.log(app_offsets);
|
||||
localStorage.setItem('worldclock-offset-list',JSON.stringify(storage_offsets));
|
||||
// send finished app (in addition to contents of app.json)
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"hworldclock.settings.json", content:JSON.stringify(app_offsets)},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,298 @@
|
|||
/* Module suncalc.js
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
|
||||
PB: Usage:
|
||||
E.setTimeZone(2); // 1 = MEZ, 2 = MESZ
|
||||
SunCalc = require("suncalc.js");
|
||||
pos = SunCalc.getPosition(Date.now(), 53.3, 10.1);
|
||||
times = SunCalc.getTimes(Date.now(), 53.3, 10.1);
|
||||
rise = times.sunrise; // Date object
|
||||
rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm
|
||||
*/
|
||||
var exports={};
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
exports.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg
|
||||
altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = [
|
||||
[-0.833, 'sunrise', 'sunset' ]
|
||||
];
|
||||
|
||||
// calculations for sun times
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
exports.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: fromJulian(Jnoon),
|
||||
nadir: fromJulian(Jnoon - 0.5)
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = fromJulian(Jrise);
|
||||
result[time[2]] = fromJulian(Jset);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
getMoonIllumination = function (date) {
|
||||
|
||||
var d = toDays(date || new Date()),
|
||||
s = sunCoords(d),
|
||||
m = moonCoords(d),
|
||||
|
||||
sdist = 149598000, // distance from Earth to Sun in km
|
||||
|
||||
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
|
||||
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
|
||||
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
|
||||
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
|
||||
|
||||
return {
|
||||
fraction: (1 + cos(inc)) / 2,
|
||||
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
|
||||
angle: angle
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = new Date(date);
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/ABEE+EA4EAj9E8HF//gn/gwP///wt/MgF//8gh/8gYLBwEP+EHAofghgFD4EOj//gEPA4ILBGgIxB/wFBgwFB/lsgCKBj/4oxHBvAFBJoV8gP4TQX+gJUBAAN/Aok+AoVgAoXogAfBjkA8AfBAoXAAoUYY4cAiCDEAooA/ABg"))
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.20",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_hworld.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
{"name":"hworldclock.app.js","url":"app.js"},
|
||||
{"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true},
|
||||
{"name":"hsuncalc.js","url":"hsuncalc.js"}
|
||||
],
|
||||
"data": [{"name":"hworldclock.settings.json"}]
|
||||
}
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -6,3 +6,4 @@
|
|||
0.06: Add 12h support and autocycle control
|
||||
0.07: added localization, removed deprecated code
|
||||
0.08: removed unused font, fix autocycle, imported suncalc and trimmed, removed pedometer dependency, "tap to cycle" setting
|
||||
0.09: fix battery icon size
|
|
@ -2,7 +2,7 @@
|
|||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
"shortName": "Rebble",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||
"readme": "README.md",
|
||||
"icon": "rebble.png",
|
||||
|
|
|
@ -236,7 +236,7 @@ function drawBattery(x,y,wi,hi) {
|
|||
g.clearRect(x+2,y+2+2,x+wi-4-2,y+2+hi-2); // centre
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(x+wi-3,y+2+(((hi - 1)/2)-1),x+wi-2,y+2+(((hi - 1)/2)-1)+4); // contact
|
||||
g.fillRect(x+3, y+5, x +4 + E.getBattery()*(wi-12)/100, y+hi-1); // the level
|
||||
g.fillRect(x+3, y+5, x +3 + E.getBattery()*(wi-10)/100, y+hi-1); // the level
|
||||
|
||||
if( Bangle.isCharging() )
|
||||
{
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
0.01: Initial version
|
||||
0.02: Do not warn multiple times for the same exceedance
|
||||
0.02: Do not warn multiple times for the same exceed
|
||||
0.03: Fix crash
|
||||
0.04: Use Prompt with dismiss and pause
|
||||
Improve barometer value median calculation
|
||||
|
|
|
@ -15,7 +15,8 @@ Get a notification when the pressure reaches defined thresholds.
|
|||
0 to disable this alarm.
|
||||
* Show widget: Enable/disable widget visibility
|
||||
* Buzz on alarm: Enable/disable buzzer on alarm
|
||||
|
||||
* Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min
|
||||
* Pause delay: Same as Dismiss delay but longer (useful for meetings and such). From 30 to 240 min
|
||||
|
||||
## Widget
|
||||
The widget shows two rows: pressure value of last measurement and pressure average of the the last three hours.
|
||||
|
|
|
@ -7,5 +7,7 @@
|
|||
"drop3halarm": 2,
|
||||
"raise3halarm": 0,
|
||||
"show": true,
|
||||
"interval": 15
|
||||
"interval": 15,
|
||||
"dismissDelayMin": 15,
|
||||
"pauseDelayMin": 60
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
"id": "widbaroalarm",
|
||||
"name": "Barometer Alarm Widget",
|
||||
"shortName": "Barometer Alarm",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A widget that can alarm on when the pressure reaches defined thresholds.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "tool,barometer",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies": {"notify":"type"},
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widbaroalarm.wid.js","url":"widget.js"},
|
||||
|
|
|
@ -87,6 +87,26 @@
|
|||
},
|
||||
onchange: x => save('buzz', x)
|
||||
},
|
||||
'Dismiss delay': {
|
||||
value: settings.dismissDelayMin,
|
||||
min: 5, max: 60,
|
||||
onchange: v => {
|
||||
save('dismissDelayMin', v)
|
||||
},
|
||||
format: x => {
|
||||
return x + " min";
|
||||
}
|
||||
},
|
||||
'Pause delay': {
|
||||
value: settings.pauseDelayMin,
|
||||
min: 30, max: 240,
|
||||
onchange: v => {
|
||||
save('pauseDelayMin', v)
|
||||
},
|
||||
format: x => {
|
||||
return x + " min";
|
||||
}
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
let medianPressure;
|
||||
let threeHourAvrPressure;
|
||||
let currentPressures = [];
|
||||
let stop = false; // semaphore
|
||||
|
||||
const LOG_FILE = "widbaroalarm.log.json";
|
||||
const SETTINGS_FILE = "widbaroalarm.json";
|
||||
|
@ -32,24 +33,50 @@
|
|||
|
||||
let history3 = storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours
|
||||
|
||||
function showAlarm(body, title) {
|
||||
function showAlarm(body, key) {
|
||||
if (body == undefined) return;
|
||||
stop = true;
|
||||
|
||||
require("notify").show({
|
||||
title: title || "Pressure",
|
||||
body: body,
|
||||
icon: require("heatshrink").decompress(atob("jEY4cA///gH4/++mkK30kiWC4H8x3BGDmSGgYDCgmSoEAg3bsAIDpAIFkmSpMAm3btgIFDQwIGNQpTYkAIJwAHEgMoCA0JgMEyBnBCAW3KoQQDhu3oAIH5JnDBAW24IIBEYm2EYwACBCIACA"))
|
||||
E.showPrompt(body, {
|
||||
title: "Pressure alarm",
|
||||
buttons: {
|
||||
"Ok": 1,
|
||||
"Dismiss": 2,
|
||||
"Pause": 3
|
||||
}
|
||||
}).then(function(v) {
|
||||
const tsNow = Math.round(Date.now() / 1000); // seconds
|
||||
|
||||
if (v == 1) {
|
||||
saveSetting(key, tsNow);
|
||||
}
|
||||
if (v == 2) {
|
||||
// save timestamp of the future so that we do not warn again for the same event until then
|
||||
saveSetting(key, tsNow + 60 * setting('dismissDelayMin'));
|
||||
}
|
||||
if (v == 3) {
|
||||
// save timestamp of the future so that we do not warn again for the same event until then
|
||||
saveSetting(key, tsNow + 60 * setting('pauseDelayMin'));
|
||||
}
|
||||
stop = false;
|
||||
load();
|
||||
});
|
||||
|
||||
if (setting("buzz") &&
|
||||
!(storage.readJSON('setting.json', 1) || {}).quiet) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
stop = false;
|
||||
load();
|
||||
}, 20000);
|
||||
}
|
||||
|
||||
|
||||
function didWeAlreadyWarn(key) {
|
||||
return setting(key) == undefined || setting(key) > 0;
|
||||
function doWeNeedToWarn(key) {
|
||||
const tsNow = Math.round(Date.now() / 1000); // seconds
|
||||
return setting(key) == 0 || setting(key) < tsNow;
|
||||
}
|
||||
|
||||
function checkForAlarms(pressure) {
|
||||
|
@ -77,31 +104,25 @@
|
|||
if (setting("lowalarm")) {
|
||||
// Is below the alarm threshold?
|
||||
if (pressure <= setting("min")) {
|
||||
if (!didWeAlreadyWarn("lastLowWarningTs")) {
|
||||
showAlarm("Pressure low: " + Math.round(pressure) + " hPa");
|
||||
saveSetting("lastLowWarningTs", ts);
|
||||
if (!doWeNeedToWarn("lastLowWarningTs")) {
|
||||
showAlarm("Pressure low: " + Math.round(pressure) + " hPa", "lastLowWarningTs");
|
||||
alreadyWarned = true;
|
||||
}
|
||||
} else {
|
||||
saveSetting("lastLowWarningTs", 0);
|
||||
}
|
||||
} else {
|
||||
saveSetting("lastLowWarningTs", 0);
|
||||
}
|
||||
|
||||
if (setting("highalarm")) {
|
||||
// Is above the alarm threshold?
|
||||
if (pressure >= setting("max")) {
|
||||
if (!didWeAlreadyWarn("lastHighWarningTs")) {
|
||||
showAlarm("Pressure high: " + Math.round(pressure) + " hPa");
|
||||
saveSetting("lastHighWarningTs", ts);
|
||||
if (doWeNeedToWarn("lastHighWarningTs")) {
|
||||
showAlarm("Pressure high: " + Math.round(pressure) + " hPa", "lastHighWarningTs");
|
||||
alreadyWarned = true;
|
||||
}
|
||||
} else {
|
||||
saveSetting("lastHighWarningTs", 0);
|
||||
}
|
||||
} else {
|
||||
saveSetting("lastHighWarningTs", 0);
|
||||
}
|
||||
|
||||
if (history3.length > 0 && !alreadyWarned) {
|
||||
|
@ -110,22 +131,22 @@
|
|||
const raise3halarm = setting("raise3halarm");
|
||||
if (drop3halarm > 0 || raise3halarm > 0) {
|
||||
// we need at least 30min of data for reliable detection
|
||||
if (history3[0]["ts"] > ts - (30 * 60)) {
|
||||
const diffDateAge = Math.abs(history3[0]["ts"] - ts);
|
||||
if (diffDateAge < 10 * 60) { // todo change to 1800
|
||||
return;
|
||||
}
|
||||
|
||||
// Get oldest entry:
|
||||
const oldestPressure = history3[0]["p"];
|
||||
if (oldestPressure != undefined && oldestPressure > 0) {
|
||||
const diff = oldestPressure - pressure;
|
||||
const diffPressure = Math.abs(oldestPressure - pressure);
|
||||
|
||||
// drop alarm
|
||||
if (drop3halarm > 0 && oldestPressure > pressure) {
|
||||
if (Math.abs(diff) > drop3halarm) {
|
||||
if (!didWeAlreadyWarn("lastDropWarningTs")) {
|
||||
showAlarm((Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h from " +
|
||||
Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "Pressure drop");
|
||||
saveSetting("lastDropWarningTs", ts);
|
||||
if (diffPressure > drop3halarm) {
|
||||
if (doWeNeedToWarn("lastDropWarningTs")) {
|
||||
showAlarm((Math.round(diffPressure * 10) / 10) + " hPa/3h from " +
|
||||
Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "lastDropWarningTs");
|
||||
}
|
||||
} else {
|
||||
saveSetting("lastDropWarningTs", 0);
|
||||
|
@ -136,11 +157,10 @@
|
|||
|
||||
// raise alarm
|
||||
if (raise3halarm > 0 && oldestPressure < pressure) {
|
||||
if (Math.abs(diff) > raise3halarm) {
|
||||
if (!didWeAlreadyWarn("lastRaiseWarningTs")) {
|
||||
showAlarm((Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h from " +
|
||||
Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "Pressure raise");
|
||||
saveSetting("lastRaiseWarningTs", ts);
|
||||
if (diffPressure > raise3halarm) {
|
||||
if (doWeNeedToWarn("lastRaiseWarningTs")) {
|
||||
showAlarm((Math.round(diffPressure * 10) / 10) + " hPa/3h from " +
|
||||
Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "lastRaiseWarningTs");
|
||||
}
|
||||
} else {
|
||||
saveSetting("lastRaiseWarningTs", 0);
|
||||
|
@ -157,51 +177,52 @@
|
|||
storage.writeJSON(LOG_FILE, history3);
|
||||
|
||||
// calculate 3h average for widget
|
||||
if (history3.length > 0) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < history3.length; i++) {
|
||||
sum += history3[i]["p"];
|
||||
}
|
||||
threeHourAvrPressure = sum / history3.length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function baroHandler(data) {
|
||||
if (data) {
|
||||
const pressure = Math.round(data.pressure);
|
||||
if (pressure == undefined || pressure <= 0) return;
|
||||
currentPressures.push(pressure);
|
||||
} else {
|
||||
threeHourAvrPressure = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
turn on barometer power
|
||||
take 5 measurements
|
||||
take multiple measurements
|
||||
sort the results
|
||||
take the middle one (median)
|
||||
turn off barometer power
|
||||
*/
|
||||
function check() {
|
||||
if (stop) return;
|
||||
const MEDIANLENGTH = 20;
|
||||
Bangle.setBarometerPower(true, "widbaroalarm");
|
||||
setTimeout(function() {
|
||||
currentPressures = [];
|
||||
Bangle.on('pressure', function(e) {
|
||||
while (currentPressures.length > MEDIANLENGTH) currentPressures.pop();
|
||||
currentPressures.unshift(e.pressure);
|
||||
median = currentPressures.slice().sort();
|
||||
|
||||
Bangle.getPressure().then(baroHandler);
|
||||
Bangle.getPressure().then(baroHandler);
|
||||
Bangle.getPressure().then(baroHandler);
|
||||
Bangle.getPressure().then(baroHandler);
|
||||
Bangle.getPressure().then(baroHandler);
|
||||
|
||||
setTimeout(function() {
|
||||
Bangle.setBarometerPower(false, "widbaroalarm");
|
||||
|
||||
currentPressures.sort();
|
||||
|
||||
// take median value
|
||||
medianPressure = currentPressures[3];
|
||||
if (median.length > 10) {
|
||||
var mid = median.length >> 1;
|
||||
medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9);
|
||||
if (medianPressure > 0) {
|
||||
turnOff();
|
||||
checkForAlarms(medianPressure);
|
||||
}, 1000);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
turnOff();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
function turnOff() {
|
||||
if (Bangle.isBarometerOn())
|
||||
Bangle.setBarometerPower(false, "widbaroalarm");
|
||||
}
|
||||
|
||||
function reload() {
|
||||
|
@ -209,31 +230,39 @@
|
|||
}
|
||||
|
||||
function draw() {
|
||||
if (global.WIDGETS != undefined && typeof WIDGETS === "object") {
|
||||
WIDGETS["baroalarm"] = {
|
||||
if (global.WIDGETS != undefined && typeof global.WIDGETS === "object") {
|
||||
global.WIDGETS["baroalarm"] = {
|
||||
width: setting("show") ? 24 : 0,
|
||||
reload: reload,
|
||||
area: "tr",
|
||||
draw: draw
|
||||
};
|
||||
}
|
||||
|
||||
g.reset();
|
||||
if (setting("show") && medianPressure != undefined) {
|
||||
if (setting("show")) {
|
||||
g.setFont("6x8", 1).setFontAlign(1, 0);
|
||||
if (medianPressure == undefined) {
|
||||
check();
|
||||
const x = this.x,
|
||||
y = this.y;
|
||||
g.drawString("...", x + 24, y + 6);
|
||||
setTimeout(function() {
|
||||
g.setFont("6x8", 1).setFontAlign(1, 0);
|
||||
g.drawString(Math.round(medianPressure), x + 24, y + 6);
|
||||
}, 10000);
|
||||
} else {
|
||||
g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6);
|
||||
}
|
||||
|
||||
if (threeHourAvrPressure != undefined && threeHourAvrPressure > 0) {
|
||||
g.drawString(Math.round(threeHourAvrPressure), this.x + 24, this.y + 6 + 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's delay the first check a bit
|
||||
setTimeout(function() {
|
||||
check();
|
||||
if (interval > 0) {
|
||||
setInterval(check, interval * 60000);
|
||||
}
|
||||
}, 1000);
|
||||
draw();
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/nodejs
|
||||
#!/usr/bin/node
|
||||
/*
|
||||
Mashes together a bunch of different apps into a big binary blob.
|
||||
We then store this *inside* the Bangle.js firmware and can use it
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
// See Layout.md for documentation
|
||||
|
||||
/* Minify to 'Layout.min.js' by:
|
||||
|
||||
* checking out: https://github.com/espruino/EspruinoDocs
|
||||
* run: ../EspruinoDocs/bin/minify.js modules/Layout.js modules/Layout.min.js
|
||||
|
||||
*/
|
||||
|
||||
|
||||
function Layout(layout, options) {
|
||||
this._l = this.l = layout;
|
||||
// Do we have >1 physical buttons?
|
||||
|
@ -71,7 +79,7 @@ function Layout(layout, options) {
|
|||
Layout.prototype.setUI = function() {
|
||||
Bangle.setUI(); // remove all existing input handlers
|
||||
|
||||
var uiSet;
|
||||
let uiSet;
|
||||
if (this.buttons) {
|
||||
// multiple buttons so we'll jus use back/next/select
|
||||
Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
function p(b,k){function d(h){h.id&&(f[h.id]=h);h.type||(h.type="");h.c&&h.c.forEach(d)}this._l=this.l=b;this.physBtns=2==process.env.HWVERSION?1:3;this.options=k||{};this.lazy=this.options.lazy||!1;if(2!=process.env.HWVERSION){var a=[];function h(m){"btn"==m.type&&a.push(m);m.c&&m.c.forEach(h)}h(b);a.length&&(this.physBtns=0,this.buttons=a,this.selectedButton=-1)}if(this.options.btns)if(this.b=b=this.options.btns,this.physBtns>=b.length){let h=Math.floor(Bangle.appRect.h/
|
||||
this.physBtns);for(2<this.physBtns&&1==b.length&&b.unshift({label:""});this.physBtns>b.length;)b.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:b.map(m=>(m.type="txt",m.font="6x8",m.height=h,m.r=1,m))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:b.map(h=>(h.type="btn",h.filly=1,h.width=32,h.r=1,h))}]},a&&a.push.apply(a,this._l.c[1].c);this.setUI();var f=this;d(this._l);this.updateNeeded=!0}function r(b,
|
||||
k,d,a,f){var h=null==b.bgCol?f:g.toColor(b.bgCol);if(h!=f||"txt"==b.type||"btn"==b.type||"img"==b.type||"custom"==b.type){var m=b.c;delete b.c;var c="H"+E.CRC32(E.toJS(b));m&&(b.c=m);delete k[c]||((a[c]=[b.x,b.y,b.x+b.w-1,b.y+b.h-1]).bg=null==f?g.theme.bg:f,d&&(d.push(b),d=null))}if(b.c)for(var l of b.c)r(l,k,d,a,h)}p.prototype.setUI=function(){Bangle.setUI();let b;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back},k=>{var d=this.selectedButton,a=this.buttons.length;if(void 0===k&&
|
||||
this.buttons[d])return this.buttons[d].cb();this.buttons[d]&&(delete this.buttons[d].selected,this.render(this.buttons[d]));d=(d+a+k)%a;this.buttons[d]&&(this.buttons[d].selected=1,this.render(this.buttons[d]));this.selectedButton=d}),b=!0);this.options.back&&!b&&Bangle.setUI({mode:"custom",back:this.options.back});if(this.b){function k(d,a){.75<a.time-a.lastTime&&this.b[d].cbl?this.b[d].cbl(a):this.b[d].cb&&this.b[d].cb(a)}Bangle.btnWatches&&Bangle.btnWatches.forEach(clearWatch);Bangle.btnWatches=
|
||||
[];this.b[0]&&Bangle.btnWatches.push(setWatch(k.bind(this,0),BTN1,{repeat:!0,edge:-1}));this.b[1]&&Bangle.btnWatches.push(setWatch(k.bind(this,1),BTN2,{repeat:!0,edge:-1}));this.b[2]&&Bangle.btnWatches.push(setWatch(k.bind(this,2),BTN3,{repeat:!0,edge:-1}))}if(2==process.env.HWVERSION){function k(d,a){d.cb&&a.x>=d.x&&a.y>=d.y&&a.x<=d.x+d.w&&a.y<=d.y+d.h&&(2==a.type&&d.cbl?d.cbl(a):d.cb&&d.cb(a));d.c&&d.c.forEach(f=>k(f,a))}Bangle.touchHandler=(d,a)=>k(this._l,a);Bangle.on("touch",Bangle.touchHandler)}};
|
||||
p.prototype.render=function(b){function k(c){"ram";g.reset();void 0!==c.col&&g.setColor(c.col);void 0!==c.bgCol&&g.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);d[c.type](c)}b||(b=this._l);this.updateNeeded&&this.update();var d={"":function(){},txt:function(c){if(c.wrap){g.setFont(c.font).setFontAlign(0,-1);var l=g.wrapString(c.label,c.w),e=c.y+(c.h-g.getFontHeight()*l.length>>1);l.forEach((n,q)=>g.drawString(n,c.x+(c.w>>1),e+g.getFontHeight()*q))}else g.setFont(c.font).setFontAlign(0,
|
||||
0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){var l=c.x+(0|c.pad),e=c.y+(0|c.pad),n=c.w-(c.pad<<1),q=c.h-(c.pad<<1);l=[l,e+4,l+4,e,l+n-5,e,l+n-1,e+4,l+n-1,e+q-5,l+n-5,e+q-1,l+4,e+q-1,l,e+q-5,l,e+4];e=c.selected?g.theme.bgH:g.theme.bg2;g.setColor(e).fillPoly(l).setColor(c.selected?g.theme.fgH:g.theme.fg2).drawPoly(l);void 0!==c.col&&g.setColor(c.col);c.src?g.setBgColor(e).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*
|
||||
(c.r||0)}):g.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){g.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){c.render(c)},h:function(c){c.c.forEach(k)},v:function(c){c.c.forEach(k)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),f=[];r(b,a,f,this.rects,null);for(var h in a)delete this.rects[h];b=Object.keys(a).map(c=>a[c]).reverse();
|
||||
for(var m of b)g.setBgColor(m.bg).clearRect.apply(g,m);f.forEach(k)}else k(b)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(b){switch(b.type){case "h":var k=b.x+(0|b.pad),d=0,a=b.c&&b.c.reduce((e,n)=>e+(0|n.fillx),0);a||(k+=b.w-b._w>>1,a=1);var f=k;b.c.forEach(e=>{e.x=0|f;k+=e._w;d+=0|e.fillx;f=k+Math.floor(d*(b.w-b._w)/a);e.w=0|f-e.x;e.h=0|(e.filly?b.h-(b.pad<<1):e._h);e.y=0|b.y+(0|b.pad)+((1+(0|e.valign))*(b.h-(b.pad<<1)-e.h)>>1);e.c&&this.layout(e)});break;
|
||||
case "v":var h=b.y+(0|b.pad),m=0,c=b.c&&b.c.reduce((e,n)=>e+(0|n.filly),0);c||(h+=b.h-b._h>>1,c=1);var l=h;b.c.forEach(e=>{e.y=0|l;h+=e._h;m+=0|e.filly;l=h+Math.floor(m*(b.h-b._h)/c);e.h=0|l-e.y;e.w=0|(e.fillx?b.w-(b.pad<<1):e._w);e.x=0|b.x+(0|b.pad)+((1+(0|e.halign))*(b.w-(b.pad<<1)-e.w)>>1);e.c&&this.layout(e)})}};p.prototype.debug=function(b,k){b||(b=this._l);k=k||1;g.setColor(k&1,k&2,k&4).drawRect(b.x+k-1,b.y+k-1,b.x+b.w-k,b.y+b.h-k);b.pad&&g.drawRect(b.x+b.pad-1,b.y+b.pad-1,b.x+b.w-b.pad,b.y+
|
||||
b.h-b.pad);k++;b.c&&b.c.forEach(d=>this.debug(d,k))};p.prototype.update=function(){function b(a){"ram";k[a.type](a);if(a.r&1){var f=a._w;a._w=a._h;a._h=f}a._w=0|Math.max(a._w+(a.pad<<1),0|a.width);a._h=0|Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var k={txt:function(a){a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var f=g.setFont(a.font).stringMetrics(a.label);a._w=f.width;a._h=f.height}},btn:function(a){a.font&&
|
||||
a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));var f=a.src?g.imageMetrics("function"==typeof a.src?a.src():a.src):g.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+f.height;a._w=20+f.width},img:function(a){var f=g.imageMetrics("function"==typeof a.src?a.src():a.src),h=a.scale||1;a._w=f.width*h;a._h=f.height*h},"":function(a){a._w=0;a._h=0},custom:function(a){a._w=0;a._h=0},h:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>Math.max(f,h._h),0);a._w=
|
||||
a.c.reduce((f,h)=>f+h._w,0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)},v:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>f+h._h,0);a._w=a.c.reduce((f,h)=>Math.max(f,h._w),0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)}},d=this._l;b(d);d.fillx||d.filly?(d.w=Bangle.appRect.w,d.h=Bangle.appRect.h,d.x=Bangle.appRect.x,d.y=Bangle.appRect.y):(d.w=d._w,d.h=d._h,d.x=Bangle.appRect.w-d.w>>1,d.y=
|
||||
Bangle.appRect.y+(Bangle.appRect.h-d.h>>1));this.layout(d)};p.prototype.clear=function(b){b||(b=this._l);g.reset();void 0!==b.bgCol&&g.setBgColor(b.bgCol);g.clearRect(b.x,b.y,b.x+b.w-1,b.y+b.h-1)};exports=p
|
|
@ -18,18 +18,25 @@ SRCJS=$1
|
|||
SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp
|
||||
echo "TEST $SRCJS ($SRCBMP)"
|
||||
|
||||
cat ../../modules/Layout.js > $TESTJS
|
||||
run_test () {
|
||||
LAYOUTFILE=$1
|
||||
echo 'exports = {};' > $TESTJS
|
||||
cat $LAYOUTFILE >> $TESTJS
|
||||
echo ';' >> $TESTJS
|
||||
echo 'Layout = exports;' >> $TESTJS
|
||||
echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS
|
||||
echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS
|
||||
cat $SRCJS >> $TESTJS || exit 1
|
||||
echo 'layout.render()' >> $TESTJS
|
||||
#echo 'layout.debug()' >> $TESTJS
|
||||
echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS
|
||||
|
||||
echo =============================================
|
||||
echo TESTING $LAYOUTFILE $SRCJS
|
||||
bin/espruino $TESTJS || exit 1
|
||||
if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1
|
||||
then
|
||||
echo =============================================
|
||||
echo $LAYOUTFILE
|
||||
echo $TESTBMP $SRCBMP differ
|
||||
echo ==============================================
|
||||
convert "+append" $TESTBMP $SRCBMP testresult.bmp
|
||||
|
@ -39,3 +46,7 @@ else
|
|||
echo Files are the same
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
run_test ../../modules/Layout.js
|
||||
run_test ../../modules/Layout.min.js
|
||||
|
|