1
0
Fork 0
dapgo 2023-01-24 11:03:51 +01:00
commit e7d9132741
42 changed files with 493 additions and 164 deletions

View File

@ -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)

View File

@ -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;
}
});
});

View File

@ -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"}],

1
apps/alarmqm/ChangeLog Normal file
View File

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

BIN
apps/alarmqm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

20
apps/alarmqm/boot.js Normal file
View File

@ -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);
})();

View File

@ -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"}
]
}

1
apps/backswipe/ChangeLog Normal file
View File

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

BIN
apps/backswipe/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

50
apps/backswipe/boot.js Normal file
View File

@ -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);
})();

View File

@ -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"}
]
}

104
apps/backswipe/settings.js Normal file
View File

@ -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();
})

View File

@ -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

View File

@ -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 => {

View File

@ -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"}],

1
apps/chargent/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First version

13
apps/chargent/README.md Normal file
View File

@ -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

30
apps/chargent/boot.js Normal file
View File

@ -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;
}
}
});
})();

1
apps/chargent/boot.min.js vendored Normal file
View File

@ -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)})})()

BIN
apps/chargent/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -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"}
]
}

View File

@ -1,2 +1,3 @@
0.1: New App!
0.2: Now with timer function
0.3: Fix HRM

View File

@ -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);
}
}

View File

@ -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"}],

View File

@ -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"},

View File

@ -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

41
apps/magnav/lib.js Normal file
View File

@ -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;
}

View File

@ -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()

View File

@ -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();

View File

@ -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"}]

View File

@ -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

View File

@ -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;
},

View File

@ -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",

View File

@ -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'

View File

@ -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

View File

@ -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);

View File

@ -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"},

View File

@ -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=>{

View File

@ -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,

View File

@ -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

View File

@ -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--;
};

View File

@ -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;
}