forked from FOSS/BangleApps
Merge branch 'master' of https://github.com/dapgo/BangleApps
commit
e7d9132741
|
@ -10,4 +10,5 @@
|
|||
0.09: Ensure Agenda supplies an image for clkinfo items
|
||||
0.10: Update clock_info to avoid a redraw
|
||||
0.11: Setting to use "Today" and "Yesterday" instead of dates
|
||||
Added dynamic, short and range fields to clkinfo
|
||||
Added dynamic, short and range fields to clkinfo
|
||||
0.12: Added color field and updating clkinfo periodically (running events)
|
||||
|
|
|
@ -5,6 +5,51 @@
|
|||
if(passed<0) return 0;
|
||||
return passed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the array [interval, switchTimeout]
|
||||
* `interval` is the refresh rate (hourly or per minute)
|
||||
* `switchTimeout` is the time before the refresh rate should change (or expiration)
|
||||
*/
|
||||
function getRefreshIntervals(ev) {
|
||||
const threshold = 2 * 60 * 1000; //2 mins
|
||||
const slices = 16;
|
||||
var now = new Date();
|
||||
var passed = now - (ev.timestamp*1000);
|
||||
var remaining = (ev.durationInSeconds*1000) - passed;
|
||||
if(remaining<0)
|
||||
return [];
|
||||
if(passed<0) //check once it's started
|
||||
return [ 2*-passed, -passed ];
|
||||
var slice = Math.round(remaining/slices);
|
||||
if(slice < threshold) { //no need to refresh frequently
|
||||
return [ threshold, remaining ];
|
||||
}
|
||||
return [ slice, remaining ];
|
||||
}
|
||||
|
||||
function _doInterval(interval) {
|
||||
return setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
this.interval = setInterval(()=>{
|
||||
this.emit("redraw");
|
||||
}, interval);
|
||||
}, interval);
|
||||
}
|
||||
function _doSwitchTimeout(ev, switchTimeout) {
|
||||
return setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
var tmp = getRefreshIntervals(ev);
|
||||
var interval = tmp[0];
|
||||
var switchTimeout = tmp[1];
|
||||
if(!interval) return;
|
||||
this.interval = _doInterval.call(this, interval);
|
||||
this.switchTimeout = _doSwitchTimeout.call(this, ev, switchTimeout);
|
||||
}, switchTimeout);
|
||||
}
|
||||
|
||||
var agendaItems = {
|
||||
name: "Agenda",
|
||||
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
||||
|
@ -23,16 +68,33 @@
|
|||
var date = new Date(entry.timestamp*1000);
|
||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
|
||||
var color = "#"+(0x1000000+Number(entry.color)).toString(16).padStart(6,"0");
|
||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||
shortStr = shortStr.trim().replace(" ", "\n");
|
||||
|
||||
agendaItems.items.push({
|
||||
name: "Agenda "+i,
|
||||
hasRange: true,
|
||||
get: () => ({ text: title + "\n" + dateStr,
|
||||
img: agendaItems.img, short: shortStr.trim(),
|
||||
img: agendaItems.img, short: shortStr,
|
||||
color: color,
|
||||
v: getPassedSec(date), min: 0, max: entry.durationInSeconds}),
|
||||
show: function() {},
|
||||
hide: function () {}
|
||||
show: function() {
|
||||
var tmp = getRefreshIntervals(entry);
|
||||
var interval = tmp[0];
|
||||
var switchTimeout = tmp[1];
|
||||
if(!interval) return;
|
||||
this.interval = _doInterval.call(this, interval);
|
||||
this.switchTimeout = _doSwitchTimeout.call(this, entry, switchTimeout);
|
||||
},
|
||||
hide: function() {
|
||||
if(this.interval)
|
||||
clearInterval(this.interval);
|
||||
if(this.switchTimeout)
|
||||
clearTimeout(this.switchTimeout);
|
||||
this.interval = undefined;
|
||||
this.switchTimeout = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,20 @@
|
|||
(function () {
|
||||
function dismissAlarm(alarm) {
|
||||
// Run only for alarms, not timers
|
||||
if (!alarm.timer) {
|
||||
if ("qmsched" in WIDGETS) {
|
||||
require("qmsched").setMode(0);
|
||||
} else {
|
||||
// Code from qmsched.js, so we can work without it
|
||||
require("Storage").writeJSON(
|
||||
"setting.json",
|
||||
Object.assign(require("Storage").readJSON("setting.json", 1) || {}, {
|
||||
quiet: 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.on("alarmDismiss", dismissAlarm);
|
||||
})();
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "alarmqm",
|
||||
"name": "Alarm Quiet Mode",
|
||||
"shortName":"AlarmQM",
|
||||
"version":"0.01",
|
||||
"description": "Service that turns off quiet mode after alarm dismiss",
|
||||
"icon": "app.png",
|
||||
"tags": "quiet,alarm",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "bootloader",
|
||||
"storage": [
|
||||
{"name":"alarmqm.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
Binary file not shown.
After Width: | Height: | Size: 764 B |
|
@ -0,0 +1,50 @@
|
|||
(function () {
|
||||
var DEFAULTS = {
|
||||
mode: 0,
|
||||
apps: [],
|
||||
};
|
||||
var settings = require("Storage").readJSON("backswipe.json", 1) || DEFAULTS;
|
||||
|
||||
// Overrride the default setUI method, so we can save the back button callback
|
||||
var setUI = Bangle.setUI;
|
||||
Bangle.setUI = function (mode, cb) {
|
||||
var options = {};
|
||||
if ("object"==typeof mode) {
|
||||
options = mode;
|
||||
}
|
||||
|
||||
var currentFile = global.__FILE__ || "";
|
||||
|
||||
if(global.BACK) delete global.BACK;
|
||||
if (options && options.back && enabledForApp(currentFile)) {
|
||||
global.BACK = options.back;
|
||||
}
|
||||
setUI(mode, cb);
|
||||
};
|
||||
|
||||
function goBack(lr, ud) {
|
||||
// if it is a left to right swipe
|
||||
if (lr === 1) {
|
||||
// if we're in an app that has a back button, run the callback for it
|
||||
if (global.BACK) {
|
||||
global.BACK();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the back button should be enabled for the current app
|
||||
// app is the src file of the app
|
||||
function enabledForApp(app) {
|
||||
if (!settings) return true;
|
||||
if (settings.mode === 0) {
|
||||
return !(settings.apps.filter((a) => a.src === app).length > 0);
|
||||
} else if (settings.mode === 1) {
|
||||
return settings.apps.filter((a) => a.src === app).length > 0;
|
||||
} else {
|
||||
return settings.mode === 2 ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to left to right swipe
|
||||
Bangle.on("swipe", goBack);
|
||||
})();
|
|
@ -0,0 +1,17 @@
|
|||
{ "id": "backswipe",
|
||||
"name": "Back Swipe",
|
||||
"shortName":"BackSwipe",
|
||||
"version":"0.01",
|
||||
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
||||
"icon": "app.png",
|
||||
"tags": "back,gesture,swipe",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "bootloader",
|
||||
"storage": [
|
||||
{"name":"backswipe.boot.js","url":"boot.js"},
|
||||
{"name":"backswipe.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"backswipe.json"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
(function(back) {
|
||||
var FILE = 'backswipe.json';
|
||||
// Mode can be 'blacklist', 'whitelist', 'on' or 'disabled'
|
||||
// Apps is an array of app info objects, where all the apps that are there are either blocked or allowed, depending on the mode
|
||||
var DEFAULTS = {
|
||||
'mode': 0,
|
||||
'apps': []
|
||||
};
|
||||
|
||||
var settings = {};
|
||||
|
||||
var loadSettings = function() {
|
||||
settings = require('Storage').readJSON(FILE, 1) || DEFAULTS;
|
||||
}
|
||||
|
||||
var saveSettings = function(settings) {
|
||||
require('Storage').write(FILE, settings);
|
||||
}
|
||||
|
||||
// Get all app info files
|
||||
var getApps = function() {
|
||||
var apps = require('Storage').list(/\.info$/).map(appInfoFileName => {
|
||||
var appInfo = require('Storage').readJSON(appInfoFileName, 1);
|
||||
return appInfo && {
|
||||
'name': appInfo.name,
|
||||
'sortorder': appInfo.sortorder,
|
||||
'src': appInfo.src
|
||||
};
|
||||
}).filter(app => app && !!app.src);
|
||||
apps.sort((a, b) => {
|
||||
var n = (0 | a.sortorder) - (0 | b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
return apps;
|
||||
}
|
||||
|
||||
var showMenu = function() {
|
||||
var menu = {
|
||||
'': { 'title': 'Backswipe' },
|
||||
'< Back': () => {
|
||||
back();
|
||||
},
|
||||
'Mode': {
|
||||
value: settings.mode,
|
||||
min: 0,
|
||||
max: 3,
|
||||
format: v => ["Blacklist", "Whitelist", "Always On", "Disabled"][v],
|
||||
onchange: v => {
|
||||
settings.mode = v;
|
||||
saveSettings(settings);
|
||||
},
|
||||
},
|
||||
'App List': () => {
|
||||
showAppSubMenu();
|
||||
}
|
||||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
var showAppSubMenu = function() {
|
||||
var menu = {
|
||||
'': { 'title': 'Backswipe' },
|
||||
'< Back': () => {
|
||||
showMenu();
|
||||
},
|
||||
'Add App': () => {
|
||||
showAppList();
|
||||
}
|
||||
};
|
||||
settings.apps.forEach(app => {
|
||||
menu[app.name] = () => {
|
||||
settings.apps.splice(settings.apps.indexOf(app), 1);
|
||||
saveSettings(settings);
|
||||
showAppSubMenu();
|
||||
}
|
||||
});
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
var showAppList = function() {
|
||||
var apps = getApps();
|
||||
var menu = {
|
||||
'': { 'title': 'Backswipe' },
|
||||
'< Back': () => {
|
||||
showMenu();
|
||||
}
|
||||
};
|
||||
apps.forEach(app => {
|
||||
menu[app.name] = () => {
|
||||
settings.apps.push(app);
|
||||
saveSettings(settings);
|
||||
showAppSubMenu();
|
||||
}
|
||||
});
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
showMenu();
|
||||
})
|
|
@ -10,3 +10,4 @@
|
|||
0.09: Fix scope of let variables
|
||||
0.10: Use default Bangle formatter for booleans
|
||||
0.11: Fix off-by-one-error on next year
|
||||
0.12: Mark dated events on a day
|
||||
|
|
|
@ -22,15 +22,17 @@ let bgColorDow = color2;
|
|||
let bgColorWeekend = color3;
|
||||
let fgOtherMonth = gray1;
|
||||
let fgSameMonth = white;
|
||||
let bgEvent = blue;
|
||||
const eventsPerDay=6; // how much different events per day we can display
|
||||
|
||||
const timeutils = require("time_utils");
|
||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||
let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
|
||||
if (settings.ndColors === undefined)
|
||||
if (process.env.HWVERSION == 2) {
|
||||
settings.ndColors = true;
|
||||
} else {
|
||||
settings.ndColors = false;
|
||||
}
|
||||
const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date); // all alarms that run on a specific date
|
||||
|
||||
if (settings.ndColors === undefined) {
|
||||
settings.ndColors = !g.theme.dark;
|
||||
}
|
||||
|
||||
if (settings.ndColors === true) {
|
||||
bgColor = white;
|
||||
|
@ -39,6 +41,7 @@ if (settings.ndColors === true) {
|
|||
bgColorWeekend = yellow;
|
||||
fgOtherMonth = blue;
|
||||
fgSameMonth = black;
|
||||
bgEvent = color2;
|
||||
}
|
||||
|
||||
function getDowLbls(locale) {
|
||||
|
@ -103,6 +106,12 @@ function getDowLbls(locale) {
|
|||
return dowLbls;
|
||||
}
|
||||
|
||||
function sameDay(d1, d2) {
|
||||
return d1.getFullYear() === d2.getFullYear() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getDate() === d2.getDate();
|
||||
}
|
||||
|
||||
function drawCalendar(date) {
|
||||
g.setBgColor(bgColor);
|
||||
g.clearRect(0, 0, maxX, maxY);
|
||||
|
@ -188,14 +197,15 @@ function drawCalendar(date) {
|
|||
for (x = 0; x < colN; x++) {
|
||||
i++;
|
||||
const day = days[i];
|
||||
const isToday =
|
||||
today.year === year && today.month === month && today.day === day - 50;
|
||||
const curMonth = day < 15 ? month+1 : day < 50 ? month-1 : month;
|
||||
const curDay = new Date(year, curMonth, day > 50 ? day-50 : day);
|
||||
const isToday = sameDay(curDay, new Date());
|
||||
const x1 = x * colW;
|
||||
const y1 = y * rowH + headerH + rowH;
|
||||
const x2 = x * colW + colW;
|
||||
const y2 = y * rowH + headerH + rowH + rowH;
|
||||
if (isToday) {
|
||||
g.setColor(red);
|
||||
let x1 = x * colW;
|
||||
let y1 = y * rowH + headerH + rowH;
|
||||
let x2 = x * colW + colW;
|
||||
let y2 = y * rowH + headerH + rowH + rowH;
|
||||
g.drawRect(x1, y1, x2, y2);
|
||||
g.drawRect(
|
||||
x1 + 1,
|
||||
|
@ -204,6 +214,22 @@ function drawCalendar(date) {
|
|||
y2 - 1
|
||||
);
|
||||
}
|
||||
|
||||
// Display events for this day
|
||||
const eventsCurDay = events.filter(ev => ev.date === curDay.toLocalISOString().substr(0, 10));
|
||||
if (eventsCurDay.length > 0) {
|
||||
g.setColor(bgEvent);
|
||||
eventsCurDay.forEach(ev => {
|
||||
const time = timeutils.decodeTime(ev.t);
|
||||
const hour = time.h + time.m/60.0;
|
||||
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
|
||||
const height = (y2-2) - (y1+2); // height of a cell
|
||||
const sliceHeight = height/eventsPerDay;
|
||||
const ystart = (y1+2) + slice*sliceHeight;
|
||||
g.fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
|
||||
});
|
||||
}
|
||||
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12", fontSize);
|
||||
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
|
||||
|
@ -217,11 +243,6 @@ function drawCalendar(date) {
|
|||
}
|
||||
|
||||
const date = new Date();
|
||||
const today = {
|
||||
day: date.getDate(),
|
||||
month: date.getMonth(),
|
||||
year: date.getFullYear()
|
||||
};
|
||||
drawCalendar(date);
|
||||
clearWatch();
|
||||
Bangle.on("touch", area => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"description": "Simple calendar",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: First version
|
|
@ -0,0 +1,13 @@
|
|||
# Charge Gently
|
||||
|
||||
Charging Li-ion batteries to their full capacity has a significant impact on their lifespan. If possible, it is good practice to charge more often, but only to a certain lower capacity.
|
||||
|
||||
The first stage of charging Li-ion ends at ~80% capacity when the charge voltage reaches its peak*. When that happens, the watch will buzz twice every 30s to remind you to disconnect the watch.
|
||||
|
||||
This app has no UI and no configuration. To disable the app, you have to uninstall it.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
\* according to https://batteryuniversity.com/article/bu-409-charging-lithium-ion assuming similar characteristics and readouts from pin `D30` approximate charge voltage
|
|
@ -0,0 +1,30 @@
|
|||
(() => {
|
||||
var id;
|
||||
Bangle.on('charging', (charging) => {
|
||||
if (charging) {
|
||||
if (!id) {
|
||||
var max = 0;
|
||||
var count = 0;
|
||||
id = setInterval(() => {
|
||||
var d30 = analogRead(D30);
|
||||
if (max < d30) {
|
||||
max = d30;
|
||||
count = 0;
|
||||
} else {
|
||||
count++;
|
||||
if (10 <= count) { // 10 * 30s == 5 min // TODO ? customizable
|
||||
// TODO ? customizable
|
||||
Bangle.buzz(500);
|
||||
setTimeout(() => Bangle.buzz(500), 1000);
|
||||
}
|
||||
}
|
||||
}, 30*1000);
|
||||
}
|
||||
} else {
|
||||
if (id) {
|
||||
clearInterval(id);
|
||||
id = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
(function(){var a;Bangle.on("charging",function(e){if(e){if(!a){var c=0,b=0;a=setInterval(function(){var d=analogRead(D30);c<d?(c=d,b=0):(b++,10<=b&&(Bangle.buzz(500),setTimeout(function(){return Bangle.buzz(500)},1E3)))},3E4)}}else a&&(clearInterval(a),a=void 0)})})()
|
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
|
@ -0,0 +1,13 @@
|
|||
{ "id": "chargent",
|
||||
"name": "Charge Gently",
|
||||
"version": "0.01",
|
||||
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
"tags": "battery",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name": "chargent.boot.js", "url": "boot.min.js"}
|
||||
]
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
0.1: New App!
|
||||
0.2: Now with timer function
|
||||
0.3: Fix HRM
|
||||
|
|
|
@ -101,7 +101,7 @@ Bangle.on('touch',t => {
|
|||
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
|
||||
g.setFontAlign(0, 0).setFont("6x8", 2);
|
||||
g.drawString(getSteps(), 50, y+70);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
||||
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
|
@ -124,4 +124,4 @@ Bangle.on('touch',t => {
|
|||
Bangle.loadWidgets();
|
||||
draw();
|
||||
setTimeout(Bangle.drawWidgets,0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "entonclk",
|
||||
"name": "Enton Clock",
|
||||
"version": "0.2",
|
||||
"version": "0.3",
|
||||
"description": "A simple clock using the Audiowide font with timer. ",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "app-icon.png",
|
||||
"tags": "tools,health",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"inspire.app.js","url":"app.js"},
|
||||
|
|
|
@ -2,6 +2,5 @@
|
|||
0.02: Course marker
|
||||
0.03: Tilt compensation and calibration
|
||||
0.04: Fix Font size
|
||||
0.05: Inital portable version
|
||||
|
||||
|
||||
0.05: Initial portable version
|
||||
0.06: Outsource tilt compensation to library
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
exports.calibrate = () => {
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},20000);
|
||||
});
|
||||
}
|
||||
|
||||
exports.tiltfixread = (O,S) => {
|
||||
"ram"
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
}
|
|
@ -61,24 +61,7 @@ function newHeading(m,h){
|
|||
|
||||
var candraw = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function tiltfixread(O,S){
|
||||
"ram"
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
}
|
||||
const tiltfixread = require("magnav").tiltfixread;
|
||||
|
||||
// Note actual mag is 360-m, error in firmware
|
||||
function reading() {
|
||||
|
@ -97,30 +80,6 @@ function reading() {
|
|||
flip(buf,Yoff+80);
|
||||
}
|
||||
|
||||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},20000);
|
||||
});
|
||||
}
|
||||
|
||||
var calibrating=false;
|
||||
function docalibrate(first){
|
||||
calibrating=true;
|
||||
|
@ -139,7 +98,7 @@ function docalibrate(first){
|
|||
buf.drawString("Fig 8s to",120,0);
|
||||
buf.drawString("Calibrate",120,26);
|
||||
flip(buf,Yoff);
|
||||
calibrate().then((r)=>{
|
||||
require("magnav").calibrate().then((r)=>{
|
||||
CALIBDATA=r;
|
||||
require("Storage").write("magnav.json",r);
|
||||
restart()
|
||||
|
|
|
@ -53,24 +53,7 @@ function newHeading(m,h){
|
|||
|
||||
var candraw = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function tiltfixread(O,S){
|
||||
"ram"
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
}
|
||||
const tiltfixread = require("magnav").tiltfixread;
|
||||
|
||||
// Note actual mag is 360-m, error in firmware
|
||||
function reading() {
|
||||
|
@ -94,30 +77,6 @@ function reading() {
|
|||
g.flip();
|
||||
}
|
||||
|
||||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},20000);
|
||||
});
|
||||
}
|
||||
|
||||
var calibrating=false;
|
||||
function docalibrate(first){
|
||||
calibrating=true;
|
||||
|
@ -137,7 +96,7 @@ function docalibrate(first){
|
|||
g.drawString("Fig 8s to",88,Ypos);
|
||||
g.drawString("Calibrate",88,Ypos+18);
|
||||
g.flip();
|
||||
calibrate().then((r)=>{
|
||||
require("magnav").calibrate().then((r)=>{
|
||||
CALIBDATA=r;
|
||||
require("Storage").write("magnav.json",r);
|
||||
restart();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "magnav",
|
||||
"name": "Navigation Compass",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.",
|
||||
"screenshots": [{"url":"screenshot-b2.png"},{"url":"screenshot-light-b2.png"}],
|
||||
"icon": "magnav.png",
|
||||
|
@ -11,6 +11,7 @@
|
|||
"storage": [
|
||||
{"name":"magnav.app.js","url":"magnav_b1.js","supports":["BANGLEJS"]},
|
||||
{"name":"magnav.app.js","url":"magnav_b2.js","supports":["BANGLEJS2"]},
|
||||
{"name":"magnav","url":"lib.js"},
|
||||
{"name":"magnav.img","url":"magnav-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"magnav.json"}]
|
||||
|
|
|
@ -20,3 +20,5 @@
|
|||
0.17: Fix midnight in local timezone (alarms wouldn't always fire as expected in timezone != 0)
|
||||
0.18: Update clock_info to avoid a redraw
|
||||
0.19: Update clock_info to refresh periodically on active alarms/timers
|
||||
0.20: Alarm dismiss and snooze events
|
||||
0.21: Fix crash in clock_info
|
||||
|
|
|
@ -118,8 +118,8 @@
|
|||
this.switchTimeout = _doSwitchTimeout.call(this, a, switchTimeout);
|
||||
},
|
||||
hide: function() {
|
||||
clearInterval(this.interval);
|
||||
clearTimeout(this.switchTimeout);
|
||||
if (this.interval) clearInterval(this.interval);
|
||||
if (this.switchTimeout) clearTimeout(this.switchTimeout);
|
||||
this.interval = undefined;
|
||||
this.switchTimeout = undefined;
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "sched",
|
||||
"name": "Scheduler",
|
||||
"version": "0.19",
|
||||
"version": "0.21",
|
||||
"description": "Scheduling library for alarms and timers",
|
||||
"icon": "app.png",
|
||||
"type": "scheduler",
|
||||
|
|
|
@ -36,6 +36,7 @@ function showAlarm(alarm) {
|
|||
alarm.ot = alarm.t;
|
||||
}
|
||||
alarm.t += settings.defaultSnoozeMillis;
|
||||
Bangle.emit("alarmSnooze", alarm);
|
||||
} else {
|
||||
let del = alarm.del === undefined ? settings.defaultDeleteExpiredTimers : alarm.del;
|
||||
if (del) {
|
||||
|
@ -52,6 +53,7 @@ function showAlarm(alarm) {
|
|||
alarm.on = false;
|
||||
}
|
||||
}
|
||||
Bangle.emit("alarmDismiss", alarm);
|
||||
}
|
||||
|
||||
// The updated alarm is still a member of 'alarms'
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Fix not displaying of wpindex = 0
|
||||
0.06: Added adjustment for Bangle.js magnetometer heading fix
|
||||
0.07: Add settings file with the option to disable the slow direction updates
|
||||
0.08: Use tilt compensation from new magnav library
|
||||
|
|
|
@ -85,31 +85,22 @@ function newHeading(m,h){
|
|||
}
|
||||
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1) || {};
|
||||
|
||||
function tiltfixread(O,S){
|
||||
var m = Bangle.getCompass();
|
||||
if (O === undefined || S === undefined) {
|
||||
// no valid calibration from magnav, use built in
|
||||
return m.heading;
|
||||
}
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
let tiltfixread;
|
||||
try {
|
||||
tiltfixread = require("magnav").tiltfixread;
|
||||
} catch(e) {
|
||||
// magnav not installed
|
||||
}
|
||||
|
||||
// Note actual mag is 360-m, error in firmware
|
||||
function read_compass() {
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
let d;
|
||||
if (tiltfixread === undefined || CALIBDATA.offset === undefined || CALIBDATA.scale === undefined) {
|
||||
// magnav not installed or no valid calibration, use built in
|
||||
d = Bangle.getCompass().heading;
|
||||
} else {
|
||||
d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
}
|
||||
if (isNaN(d)) return; // built in compass heading can return NaN when uncalibrated
|
||||
heading = newHeading(d,heading);
|
||||
direction = wp_bearing - heading;
|
||||
|
@ -278,6 +269,9 @@ function doselect(){
|
|||
wp = waypoints[wpindex];
|
||||
require("waypoints").save(waypoints);
|
||||
}
|
||||
if (selected) {
|
||||
Bangle.resetCompass(); // reset built in compass when a waypoint is selected
|
||||
}
|
||||
selected=!selected;
|
||||
drawN();
|
||||
}
|
||||
|
@ -294,6 +288,7 @@ Bangle.drawWidgets();
|
|||
// load widgets can turn off GPS
|
||||
Bangle.setGPSPower(1);
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.resetCompass() // reset built in compass on start in case we are not using tilt compensation
|
||||
drawAll();
|
||||
startTimers();
|
||||
Bangle.on('GPS', onGPS);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"id": "waypointer",
|
||||
"name": "Way Pointer",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation",
|
||||
"icon": "waypointer.png",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"dependencies" : { "waypoints":"type" },
|
||||
"dependencies" : { "waypoints":"type", "magnav" : "app" },
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"waypointer.app.js","url":"app.js"},
|
||||
|
|
30
loader.js
30
loader.js
|
@ -249,23 +249,27 @@ window.addEventListener('load', (event) => {
|
|||
|
||||
// BLE Compatibility
|
||||
var selectBLECompat = document.getElementById("settings-ble-compat");
|
||||
Puck.increaseMTU = !SETTINGS.bleCompat;
|
||||
selectBLECompat.checked = !!SETTINGS.bleCompat;
|
||||
selectBLECompat.addEventListener("change",event=>{
|
||||
console.log("BLE compatibility mode "+(event.target.checked?"on":"off"));
|
||||
SETTINGS.bleCompat = event.target.checked;
|
||||
if (selectBLECompat) {
|
||||
Puck.increaseMTU = !SETTINGS.bleCompat;
|
||||
saveSettings();
|
||||
});
|
||||
selectBLECompat.checked = !!SETTINGS.bleCompat;
|
||||
selectBLECompat.addEventListener("change",event=>{
|
||||
console.log("BLE compatibility mode "+(event.target.checked?"on":"off"));
|
||||
SETTINGS.bleCompat = event.target.checked;
|
||||
Puck.increaseMTU = !SETTINGS.bleCompat;
|
||||
saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Sending usage stats
|
||||
var selectUsageStats = document.getElementById("settings-usage-stats");
|
||||
selectUsageStats.checked = !!SETTINGS.sendUsageStats;
|
||||
selectUsageStats.addEventListener("change",event=>{
|
||||
console.log("Send Usage Stats "+(event.target.checked?"on":"off"));
|
||||
SETTINGS.sendUsageStats = event.target.checked;
|
||||
saveSettings();
|
||||
});
|
||||
if (selectUsageStats) {
|
||||
selectUsageStats.checked = !!SETTINGS.sendUsageStats;
|
||||
selectUsageStats.addEventListener("change",event=>{
|
||||
console.log("Send Usage Stats "+(event.target.checked?"on":"off"));
|
||||
SETTINGS.sendUsageStats = event.target.checked;
|
||||
saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Load language list
|
||||
httpGet("lang/index.json").then(languagesJSON=>{
|
||||
|
|
|
@ -186,8 +186,12 @@ Layout.prototype.render = function (l) {
|
|||
x+4,y+h-1,
|
||||
x,y+h-5,
|
||||
x,y+4
|
||||
], bg = l.selected?gfx.theme.bgH:gfx.theme.bg2;
|
||||
gfx.setColor(bg).fillPoly(poly).setColor(l.selected ? gfx.theme.fgH : gfx.theme.fg2).drawPoly(poly);
|
||||
], bg = l.bgCol!==undefined?l.bgCol:gfx.theme.bg2,
|
||||
btnborder = l.btnBorder!==undefined?l.btnBorder:gfx.theme.fg2;
|
||||
if(l.selected){
|
||||
bg = gfx.theme.bgH, btnborder = gfx.theme.fgH;
|
||||
}
|
||||
gfx.setColor(bg).fillPoly(poly).setColor(btnborder).drawPoly(poly);
|
||||
if (l.col!==undefined) gfx.setColor(l.col);
|
||||
if (l.src) gfx.setBgColor(bg).drawImage(
|
||||
"function"==typeof l.src?l.src():l.src,
|
||||
|
|
|
@ -46,7 +46,8 @@ layout.render();
|
|||
- A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°).
|
||||
- A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` and `fillx`/`filly` to be set. Not compatible with text rotation.
|
||||
- A `col` field, eg `#f00` for red
|
||||
- A `bgCol` field for background color (will automatically fill on render)
|
||||
- A `bgCol` field for background color (will automatically fill on render). When `type:"btn"`, this sets the background color of the button, and will not change color on press
|
||||
- A `btnBorder` field for button border color (will default to theme if not set)
|
||||
- A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
|
||||
- A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
|
||||
- A `pad` integer field to set pixels padding
|
||||
|
|
|
@ -54,7 +54,7 @@ let storage = require("Storage");
|
|||
let stepGoal = undefined;
|
||||
// Load step goal from health app and pedometer widget
|
||||
let d = storage.readJSON("health.json", true) || {};
|
||||
stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
|
||||
stepGoal = d.stepGoal;
|
||||
if (stepGoal == undefined) {
|
||||
d = storage.readJSON("wpedom.json", true) || {};
|
||||
stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
|
@ -120,8 +120,10 @@ exports.load = function() {
|
|||
|
||||
if (Bangle.getPressure){ // Altimeter may not exist
|
||||
bangleItems.push({ name : "Altitude",
|
||||
hasRange : true,
|
||||
get : () => ({
|
||||
text : alt, v : alt,
|
||||
text : alt, v : parseInt(alt),
|
||||
min : 0, max : 3000,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==")
|
||||
}),
|
||||
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
|
||||
|
@ -266,7 +268,14 @@ exports.addInteractive = function(menu, options) {
|
|||
}
|
||||
Bangle.on("swipe",swipeHandler);
|
||||
var touchHandler;
|
||||
var lockHandler;
|
||||
if (options.x!==undefined && options.y!==undefined && options.w && options.h) {
|
||||
lockHandler = function() {
|
||||
if(options.focus) {
|
||||
options.focus=false;
|
||||
options.redraw();
|
||||
}
|
||||
};
|
||||
touchHandler = function(_,e) {
|
||||
if (e.x<options.x || e.y<options.y ||
|
||||
e.x>(options.x+options.w) || e.y>(options.y+options.h)) {
|
||||
|
@ -278,7 +287,7 @@ exports.addInteractive = function(menu, options) {
|
|||
}
|
||||
if (!options.focus) {
|
||||
options.focus=true; // if not focussed, set focus
|
||||
options.redraw();
|
||||
options.redraw();
|
||||
} else if (menu[options.menuA].items[options.menuB].run) {
|
||||
Bangle.buzz(100, 0.7);
|
||||
menu[options.menuA].items[options.menuB].run(); // allow tap on an item to run it (eg home assistant)
|
||||
|
@ -287,6 +296,7 @@ exports.addInteractive = function(menu, options) {
|
|||
}
|
||||
};
|
||||
Bangle.on("touch",touchHandler);
|
||||
Bangle.on("lock", lockHandler);
|
||||
}
|
||||
// draw the first item
|
||||
menuShowItem(menu[options.menuA].items[options.menuB]);
|
||||
|
@ -294,6 +304,7 @@ exports.addInteractive = function(menu, options) {
|
|||
options.remove = function() {
|
||||
Bangle.removeListener("swipe",swipeHandler);
|
||||
if (touchHandler) Bangle.removeListener("touch",touchHandler);
|
||||
if (lockHandler) Bangle.removeListener("lock", lockHandler);
|
||||
menuHideItem(menu[options.menuA].items[options.menuB]);
|
||||
exports.loadCount--;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle centered at X,Y. All angles are radians.
|
||||
exports.fillArc = function(graphics, X, Y, minR, maxR, minAngle, maxAngle, stepAngle) {
|
||||
var step = stepAngle || 0.2;
|
||||
var step = stepAngle || 0.21;
|
||||
var angle = minAngle;
|
||||
var inside = [];
|
||||
var outside = [];
|
||||
|
@ -31,5 +31,5 @@ exports.degreesToRadians = function(degrees){
|
|||
}
|
||||
|
||||
exports.radiansToDegrees = function(radians){
|
||||
return 180/Math.PI * degrees;
|
||||
return 180/Math.PI * radians;
|
||||
}
|
Loading…
Reference in New Issue