forked from FOSS/BangleApps
merge with upstream
commit
9f6602c596
83
apps.json
83
apps.json
|
@ -79,10 +79,10 @@
|
|||
},
|
||||
{
|
||||
"id": "launch",
|
||||
"name": "Launcher (Default)",
|
||||
"name": "Launcher (Bangle.js 1 default)",
|
||||
"shortName": "Launcher",
|
||||
"version": "0.07",
|
||||
"description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"description": "This is needed by Bangle.js 1.0 to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"icon": "app.png",
|
||||
"type": "launch",
|
||||
"tags": "tool,system,launcher",
|
||||
|
@ -94,10 +94,10 @@
|
|||
},
|
||||
{
|
||||
"id": "launchb2",
|
||||
"name": "Launcher (Bangle.js 2)",
|
||||
"name": "Launcher (Bangle.js 2 default)",
|
||||
"shortName": "Launcher",
|
||||
"version": "0.03",
|
||||
"description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.",
|
||||
"description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.",
|
||||
"icon": "app.png",
|
||||
"type": "launch",
|
||||
"tags": "tool,system,launcher",
|
||||
|
@ -320,7 +320,7 @@
|
|||
"icon": "slidingtext.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"custom": "custom.html",
|
||||
"allow_emulator": false,
|
||||
|
@ -537,11 +537,11 @@
|
|||
{
|
||||
"id": "compass",
|
||||
"name": "Compass",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Simple compass that points North",
|
||||
"icon": "compass.png",
|
||||
"tags": "tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"compass.app.js","url":"compass.js"},
|
||||
{"name":"compass.img","url":"compass-icon.js","evaluate":true}
|
||||
|
@ -594,7 +594,7 @@
|
|||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
|
@ -624,11 +624,12 @@
|
|||
{
|
||||
"id": "heart",
|
||||
"name": "Heart Rate Recorder",
|
||||
"version": "0.06",
|
||||
"shortName": "HRM Record",
|
||||
"version": "0.07",
|
||||
"description": "Application that allows you to record your heart rate. Can run in background",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,health,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"heart.app.js","url":"app.js"},
|
||||
|
@ -818,11 +819,11 @@
|
|||
{
|
||||
"id": "hrm",
|
||||
"name": "Heart Rate Monitor",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Measure your heart rate and see live sensor data",
|
||||
"icon": "heartrate.png",
|
||||
"tags": "health",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"hrm.app.js","url":"heartrate.js"},
|
||||
{"name":"hrm.img","url":"heartrate-icon.js","evaluate":true}
|
||||
|
@ -831,16 +832,31 @@
|
|||
{
|
||||
"id": "widhrm",
|
||||
"name": "Simple Heart Rate widget",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "health,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widhrm.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName":"BT HRM",
|
||||
"version":"0.01",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"tags": "health,bluetooth",
|
||||
"type": "boot",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthrm.boot.js","url":"boot.js"},
|
||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stetho",
|
||||
"name": "Stethoscope",
|
||||
|
@ -1026,7 +1042,7 @@
|
|||
{
|
||||
"id": "sclock",
|
||||
"name": "Simple Clock",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "A Simple Digital Clock",
|
||||
"icon": "clock-simple.png",
|
||||
"type": "clock",
|
||||
|
@ -1071,12 +1087,12 @@
|
|||
{
|
||||
"id": "svclock",
|
||||
"name": "Simple V-Clock",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Modification of Simple Clock 0.04 to use Vectorfont",
|
||||
"icon": "vclock-simple.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"svclock.app.js","url":"vclock-simple.js"},
|
||||
|
@ -1376,12 +1392,12 @@
|
|||
{
|
||||
"id": "berlinc",
|
||||
"name": "Berlin Clock",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
|
||||
"icon": "berlin-clock.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"berlinc.app.js","url":"berlin-clock.js"},
|
||||
|
@ -1502,7 +1518,7 @@
|
|||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,address,mac",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widid.wid.js","url":"widget.js"}
|
||||
]
|
||||
|
@ -2804,12 +2820,12 @@
|
|||
"id": "worldclock",
|
||||
"name": "World Clock - 4 time zones",
|
||||
"shortName": "World Clock",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Current time zone plus up to four others",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
|
@ -3334,12 +3350,12 @@
|
|||
{
|
||||
"id": "widhrt",
|
||||
"name": "HRM Widget",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,hrm",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widhrt.wid.js","url":"widget.js"}
|
||||
|
@ -3382,7 +3398,7 @@
|
|||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,compass",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widcom.wid.js","url":"widget.js"}
|
||||
|
@ -3391,7 +3407,7 @@
|
|||
{
|
||||
"id": "arrow",
|
||||
"name": "Arrow Compass",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
|
||||
"icon": "arrow.png",
|
||||
"type": "app",
|
||||
|
@ -3975,5 +3991,20 @@
|
|||
{"name":"stopwatch.app.js","url":"stopwatch.app.js"},
|
||||
{"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "vernierrespirate",
|
||||
"name": "Vernier Go Direct Respiration Belt",
|
||||
"shortName":"Respiration Belt",
|
||||
"version":"0.01",
|
||||
"description": "Connects to a Go Direct Respiration Belt and shows respiration rate",
|
||||
"icon": "app.png",
|
||||
"tags": "health,bluetooth",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"vernierrespirate.app.js","url":"app.js"},
|
||||
{"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"vernierrespirate.json"}]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
0.01: First version
|
||||
0.02: Moved arrow image load to global scope
|
||||
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
||||
0.04: removed LCD1.write() as it was keeping LCD on
|
||||
0.04: removed LED1.write() as it was keeping LCD on
|
||||
0.05: Turn compass off when screen off
|
||||
Calibrate at start if no info
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var pal1color = new Uint16Array([0x0000,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([0x0000,0xffff],0,1);
|
||||
var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1);
|
||||
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||
var intervalRef;
|
||||
|
@ -7,6 +7,7 @@ var bearing=0; // always point north
|
|||
var heading = 0;
|
||||
var oldHeading = 0;
|
||||
var candraw = false;
|
||||
var isCalibrating = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function flip1(x,y) {
|
||||
|
@ -29,7 +30,7 @@ function drawCompass(hd) {
|
|||
if (Math.abs(hd - oldHeading) < 2) return 0;
|
||||
hd=hd*Math.PI/180;
|
||||
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
||||
|
||||
|
||||
// using polar cordinates, 64,64 is the offset from the 0,0 origin
|
||||
var poly = [
|
||||
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
||||
|
@ -40,16 +41,16 @@ function drawCompass(hd) {
|
|||
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
||||
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
||||
];
|
||||
|
||||
|
||||
buf1.fillPoly(poly);
|
||||
flip1(56, 56);
|
||||
}
|
||||
|
||||
// stops violent compass swings and wobbles, takes 3ms
|
||||
function newHeading(m,h){
|
||||
function newHeading(m,h){
|
||||
var s = Math.abs(m - h);
|
||||
var delta = (m>h)?1:-1;
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s<2) return h;
|
||||
var hd = h + delta*(1 + Math.round(s/5));
|
||||
if (hd<0) hd+=360;
|
||||
|
@ -76,7 +77,7 @@ function tiltfixread(O,S){
|
|||
return psi;
|
||||
}
|
||||
|
||||
function reading() {
|
||||
function reading(m) {
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
var dir = bearing - heading;
|
||||
|
@ -97,18 +98,19 @@ function reading() {
|
|||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
function onMag(m) {
|
||||
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);
|
||||
}
|
||||
Bangle.on('mag', onMag);
|
||||
Bangle.setCompassPower(1, "app");
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
Bangle.removeListener('mag', onMag);
|
||||
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;
|
||||
|
@ -132,6 +134,7 @@ function docalibrate(e,first){
|
|||
flip1(56,56);
|
||||
|
||||
calibrate().then((r)=>{
|
||||
isCalibrating = false;
|
||||
require("Storage").write("magnav.json",r);
|
||||
Bangle.buzz();
|
||||
CALIBDATA = r;
|
||||
|
@ -142,27 +145,39 @@ function docalibrate(e,first){
|
|||
startdraw();
|
||||
setTimeout(setButtons,1000);
|
||||
}
|
||||
}
|
||||
if (first===undefined) first=false;
|
||||
}
|
||||
|
||||
if (first === undefined) first = false;
|
||||
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
if (first)
|
||||
isCalibrating = true;
|
||||
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
else
|
||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||
}
|
||||
|
||||
function startdraw(){
|
||||
Bangle.setCompassPower(1, "app");
|
||||
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
Bangle.drawWidgets();
|
||||
candraw = true;
|
||||
intervalRef = setInterval(reading,500);
|
||||
if (intervalRef) clearInterval(intervalRef);
|
||||
intervalRef = setInterval(reading,200);
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
candraw=false;
|
||||
if(intervalRef) {clearInterval(intervalRef);}
|
||||
|
||||
Bangle.setCompassPower(0, "app");
|
||||
if (intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
|
@ -170,8 +185,9 @@ function setButtons(){
|
|||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
||||
}
|
||||
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (isCalibrating) return;
|
||||
if (on) {
|
||||
startdraw();
|
||||
} else {
|
||||
|
@ -179,9 +195,8 @@ Bangle.on('lcdPower',function(on) {
|
|||
}
|
||||
});
|
||||
|
||||
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
startdraw();
|
||||
setButtons();
|
||||
|
||||
Bangle.setLCDPower(1);
|
||||
if (CALIBDATA) startdraw(); else docalibrate({},true);
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
0.02: Modified for use with new bootloader and firmware
|
||||
0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal.
|
||||
0.04: Update to use Bangle.setUI instead of setWatch
|
||||
0.05: Update *on* the minute rather than every 15 secs
|
||||
Now show widgets
|
||||
Make compatible with themes, and Bangle.js 2
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
|
||||
// https://github.com/eska-muc/BangleApps
|
||||
const fields = [4, 4, 11, 4];
|
||||
const offset = 20;
|
||||
const offset = 24;
|
||||
const width = g.getWidth() - 2 * offset;
|
||||
const height = g.getHeight() - 2 * offset;
|
||||
const rowHeight = height / 4;
|
||||
|
@ -10,11 +10,23 @@ var show_date = false;
|
|||
var show_time = false;
|
||||
var yy = 0;
|
||||
|
||||
rowlights = [];
|
||||
time_digit = [];
|
||||
var rowlights = [];
|
||||
var time_digit = [];
|
||||
|
||||
function drawBerlinClock() {
|
||||
g.clear();
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
var now = new Date();
|
||||
|
||||
// show date below the clock
|
||||
|
@ -24,8 +36,7 @@ function drawBerlinClock() {
|
|||
var day = now.getDate();
|
||||
var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
|
||||
var strWidth = g.stringWidth(dateString);
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1,-1);
|
||||
g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4);
|
||||
}
|
||||
|
||||
|
@ -50,8 +61,7 @@ function drawBerlinClock() {
|
|||
x2 = (col + 1) * boxWidth + offset;
|
||||
y2 = (row + 1) * rowHeight + offset;
|
||||
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawRect(x1, y1, x2, y2);
|
||||
g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2);
|
||||
if (col < rowlights[row]) {
|
||||
if (row === 2) {
|
||||
if (((col + 1) % 3) === 0) {
|
||||
|
@ -65,46 +75,42 @@ function drawBerlinClock() {
|
|||
g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2);
|
||||
}
|
||||
if (row == 3 && show_time) {
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.fg).setFontAlign(0,0);
|
||||
g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function toggleDate() {
|
||||
show_date = ! show_date;
|
||||
drawBerlinClock();
|
||||
draw();
|
||||
}
|
||||
|
||||
function toggleTime() {
|
||||
show_time = ! show_time;
|
||||
drawBerlinClock();
|
||||
draw();
|
||||
}
|
||||
|
||||
// special function to handle display switch on
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
g.clear();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
Bangle.drawWidgets();
|
||||
// call your app function here
|
||||
drawBerlinClock();
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawBerlinClock, 15E3);
|
||||
// Show launcher when button pressed, handle up/down
|
||||
Bangle.setUI("clockupdown", dir=> {
|
||||
if (dir<0) toggleTime();
|
||||
if (dir>0) toggleDate();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawBerlinClock();
|
||||
if (BTN3) {
|
||||
// Toggle date display, when BTN3 is pressed
|
||||
setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"});
|
||||
// Toggle date display, when BTN3 is pressed
|
||||
setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"});
|
||||
}
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
draw();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,45 @@
|
|||
# Bluetooth Heart Rate Monitor
|
||||
|
||||
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
|
||||
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
|
||||
|
||||
This means it's compatible with many Bangle.js apps including:
|
||||
|
||||
* [Heart Rate Widget](https://banglejs.com/apps/#widhrt)
|
||||
* [Heart Rate Recorder](https://banglejs.com/apps/#heart)
|
||||
|
||||
It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm)
|
||||
as that requires live sensor data (rather than just BPM readings).
|
||||
|
||||
## Usage
|
||||
|
||||
Just install the app, then install an app that uses the heart rate monitor.
|
||||
|
||||
Once installed it'll automatically try and connect to the first bluetooth
|
||||
heart rate monitor it finds.
|
||||
|
||||
**To disable this and return to normal HRM, uninstall the app**
|
||||
|
||||
## Compatible Heart Rate Monitors
|
||||
|
||||
This works with any heart rate monitor providing the standard Bluetooth
|
||||
Heart Rate Service (`180D`) and characteristic (`2A37`).
|
||||
|
||||
So far it has been tested on:
|
||||
|
||||
* CooSpo Bluetooth Heart Rate Monitor
|
||||
|
||||
## Internals
|
||||
|
||||
This replaces `Bangle.setHRMPower` with its own implementation.
|
||||
|
||||
## TODO
|
||||
|
||||
* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off
|
||||
* A widget to show connection state?
|
||||
* Specify a specific device by address?
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA=="))
|
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,79 @@
|
|||
(function() {
|
||||
var log = function() {};//print
|
||||
var gatt;
|
||||
var status;
|
||||
|
||||
Bangle.isHRMOn = function() {
|
||||
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
||||
}
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
log("setHRMPower ->", isOn, app);
|
||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
|
||||
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
|
||||
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
|
||||
isOn = Bangle._PWR.HRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
log("setHRMPower on", app);
|
||||
if (!Bangle.isHRMOn()) {
|
||||
log("HRM not already on");
|
||||
status = "searching";
|
||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||
log("Found device "+device.id);
|
||||
status = "connecting";
|
||||
device.on('gattserverdisconnected', function(reason) {
|
||||
gatt = undefined;
|
||||
});
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
log("Connected");
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService(0x180D);
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic(0x2A37);
|
||||
}).then(function(characteristic) {
|
||||
log("Got characteristic");
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var flags = dv.getUint8(0);
|
||||
// 0 = 8 or 16 bit
|
||||
// 1,2 = sensor contact
|
||||
// 3 = energy expended shown
|
||||
// 4 = RR interval
|
||||
var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit
|
||||
/* var idx = 2 + (flags&1); // index of next field
|
||||
if (flags&8) idx += 2; // energy expended
|
||||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
Bangle.emit('HRM',{
|
||||
bpm:bpm,
|
||||
confidence:100
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
log("Ready");
|
||||
status = "ok";
|
||||
}).catch(function(err) {
|
||||
log("Error",err);
|
||||
gatt = undefined;
|
||||
status = "error";
|
||||
});
|
||||
}
|
||||
} else { // not on
|
||||
log("setHRMPower off", app);
|
||||
if (gatt) {
|
||||
log("HRM connected - disconnecting");
|
||||
status = undefined;
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("HRM disconnect error", e);
|
||||
}
|
||||
gatt = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Show text if uncalibrated
|
||||
0.03: Eliminate flickering
|
||||
0.03: Eliminate flickering
|
||||
0.04: Fix for Bangle.js 2 and themes
|
||||
|
|
|
@ -1,60 +1,72 @@
|
|||
var tg = Graphics.createArrayBuffer(120,20,1,{msb:true});
|
||||
var timg = {
|
||||
width:tg.getWidth(),
|
||||
height:tg.getHeight(),
|
||||
bpp:1,
|
||||
buffer:tg.buffer
|
||||
};
|
||||
|
||||
var ag = Graphics.createArrayBuffer(160,160,2,{msb:true});
|
||||
var W = g.getWidth();
|
||||
var M = W/2; // middle of screen
|
||||
// Angle buffer
|
||||
var AGS = W > 200 ? 160 : 120; // buffer size
|
||||
var AGM = AGS/2; // midpoint/radius
|
||||
var AGH = AGM-10; // hand size
|
||||
var ag = Graphics.createArrayBuffer(AGS,AGS,2,{msb:true});
|
||||
var aimg = {
|
||||
width:ag.getWidth(),
|
||||
height:ag.getHeight(),
|
||||
bpp:2,
|
||||
buffer:ag.buffer,
|
||||
palette:new Uint16Array([0,0x03FF,0xF800,0x001F])
|
||||
palette:new Uint16Array([
|
||||
g.theme.bg,
|
||||
g.toColor("#07f"),
|
||||
g.toColor("#f00"),
|
||||
g.toColor("#00f")])
|
||||
};
|
||||
ag.setColor(1);
|
||||
ag.fillCircle(80,80,79,79);
|
||||
ag.setColor(0);
|
||||
ag.fillCircle(80,80,69,69);
|
||||
ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1);
|
||||
ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11);
|
||||
|
||||
function arrow(r,c) {
|
||||
r=r*Math.PI/180;
|
||||
var p = Math.PI/2;
|
||||
ag.setColor(c);
|
||||
ag.fillPoly([
|
||||
80+60*Math.sin(r), 80-60*Math.cos(r),
|
||||
80+10*Math.sin(r+p), 80-10*Math.cos(r+p),
|
||||
80+10*Math.sin(r-p), 80-10*Math.cos(r-p),
|
||||
ag.setColor(c).fillPoly([
|
||||
AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r),
|
||||
AGM+10*Math.sin(r+p), AGM-10*Math.cos(r+p),
|
||||
AGM+10*Math.sin(r-p), AGM-10*Math.cos(r-p),
|
||||
]);
|
||||
}
|
||||
|
||||
var wasUncalibrated = false;
|
||||
var oldHeading = 0;
|
||||
Bangle.on('mag', function(m) {
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
tg.clear();
|
||||
tg.setFont("6x8",1);
|
||||
tg.setColor(1);
|
||||
g.reset();
|
||||
if (isNaN(m.heading)) {
|
||||
tg.setFontAlign(0,-1);
|
||||
tg.setFont("6x8",1);
|
||||
tg.drawString("Uncalibrated",60,4);
|
||||
tg.drawString("turn 360° around",60,12);
|
||||
if (!wasUncalibrated) {
|
||||
g.clearRect(0,24,W,48);
|
||||
g.setFontAlign(0,-1).setFont("6x8");
|
||||
g.drawString("Uncalibrated\nturn 360° around",M,24+4);
|
||||
wasUncalibrated = true;
|
||||
}
|
||||
} else {
|
||||
if (wasUncalibrated) {
|
||||
g.clearRect(0,24,W,48);
|
||||
wasUncalibrated = false;
|
||||
}
|
||||
g.setFontAlign(0,0).setFont("6x8",3);
|
||||
var y = 36;
|
||||
g.clearRect(M-40,y,M+40,y+24);
|
||||
g.drawString(Math.round(m.heading),M,y,true);
|
||||
}
|
||||
else {
|
||||
tg.setFontAlign(0,0);
|
||||
tg.setFont("6x8",2);
|
||||
tg.drawString(Math.round(m.heading),60,12);
|
||||
}
|
||||
g.drawImage(timg,0,0,{scale:2});
|
||||
|
||||
|
||||
ag.setColor(0);
|
||||
arrow(oldHeading,0);
|
||||
arrow(oldHeading+180,0);
|
||||
arrow(m.heading,2);
|
||||
arrow(m.heading+180,3);
|
||||
g.drawImage(aimg,40,50);
|
||||
g.drawImage(aimg,
|
||||
(W-ag.getWidth())/2,
|
||||
g.getHeight()-(ag.getHeight()+4));
|
||||
oldHeading = m.heading;
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
Generate scale based on defined minimum and maximum measurement
|
||||
Added background line on 50% to ease estimation of drawn values
|
||||
0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||
0.07: theme support
|
||||
|
|
|
@ -221,9 +221,9 @@ function graphRecord(n) {
|
|||
if (tempCount == startLine) {
|
||||
// generating rgaph in loop when reaching startLine to keep loading
|
||||
// message on screen until graph can be drawn
|
||||
g.clear().
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()).
|
||||
// Home for Btn2
|
||||
setColor(1, 1, 1).
|
||||
setColor(g.theme.fg).
|
||||
drawLine(220, 118, 227, 110).
|
||||
drawLine(227, 110, 234, 118).
|
||||
drawPoly([222,117,222,125,232,125,232,117], false).
|
||||
|
@ -245,7 +245,7 @@ function graphRecord(n) {
|
|||
// scale indicator line for 50%
|
||||
drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2).
|
||||
// background line for 50%
|
||||
setColor(1, 1, 1).
|
||||
setColor(g.theme.fg).
|
||||
drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2).
|
||||
setFontAlign(1, -1, 0).
|
||||
setFont("Vector", 10);
|
||||
|
@ -303,7 +303,7 @@ function graphRecord(n) {
|
|||
log("Finished rendering data");
|
||||
Bangle.buzz(200, 0.3);
|
||||
g.flip();
|
||||
setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
|
||||
setWatch(stop, (global.BTN2!==undefined)?BTN2:BTN1, {edge:"falling", debounce:50, repeat:false});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Fix timing issues, and use 1/2 scale to keep graph on screen
|
||||
0.04: Update for new firmwares that have a 'HRM-raw' event
|
||||
0.05: Tweaks for 'HRM-raw' handling
|
||||
0.06: Add widgets
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA="))
|
||||
require("heatshrink").decompress(atob("mEw4UA///g3yrv/7f+Jf4AJgNVoAEGAANVAAIEGCIQABoAEEBYMFAwVQAggLBioGCqgEEFIgAGFwdXBYw1Dr4LKrwLHIIVaBYxNDvXVBanVteVBZGVt+VKooLBq+19u1JItQgNW0vlBYIxEL4Ne1u18taGIN9BYUD1XvBYN62+q1a0D1d7ytttYLEWYV6BYNt93VEYKzCita6t59vqX4sFIgN70tqa4pUBTgO1vbvFgB0BKQNZawYACdYNeytdFwgwCBYJ2DFwQwCqoxBFwwABBYoKEGAKyDFwgwDFw4kDERBVDEQ4kEEQ4kDBRAYBERBuCNAoA/AA4="))
|
||||
|
|
|
@ -4,13 +4,14 @@ Bangle.setHRMPower(1);
|
|||
var hrmInfo, hrmOffset = 0;
|
||||
var hrmInterval;
|
||||
var btm = g.getHeight()-1;
|
||||
var lastHrmPt = []; // last xy coords we draw a line to
|
||||
|
||||
function onHRM(h) {
|
||||
if (counter!==undefined) {
|
||||
// the first time we're called remove
|
||||
// the countdown
|
||||
counter = undefined;
|
||||
g.clear();
|
||||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
}
|
||||
hrmInfo = h;
|
||||
/* On 2v09 and earlier firmwares the only solution for realtime
|
||||
|
@ -28,7 +29,7 @@ function onHRM(h) {
|
|||
|
||||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,24,239,80);
|
||||
g.clearRect(0,24,g.getWidth(),80);
|
||||
g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75);
|
||||
var str = hrmInfo.bpm;
|
||||
g.setFontVector(40).drawString(str,px,45);
|
||||
|
@ -43,17 +44,18 @@ Bangle.on('HRM-raw', function(v) {
|
|||
hrmOffset++;
|
||||
if (hrmOffset>g.getWidth()) {
|
||||
hrmOffset=0;
|
||||
g.clearRect(0,80,239,239);
|
||||
g.moveTo(-100,0);
|
||||
g.clearRect(0,80,g.getWidth(),g.getHeight());
|
||||
lastHrmPt = [-100,0];
|
||||
}
|
||||
|
||||
y = E.clip(btm-v.filt/4,btm-10,btm);
|
||||
g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y);
|
||||
y = E.clip(170 - (v.raw/2),80,btm);
|
||||
g.setColor(g.theme.fg).lineTo(hrmOffset, y);
|
||||
g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
|
||||
lastHrmPt = [hrmOffset, y];
|
||||
if (counter !==undefined) {
|
||||
counter = undefined;
|
||||
g.clear();
|
||||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -65,7 +67,10 @@ function countDown() {
|
|||
setTimeout(countDown, 1000);
|
||||
}
|
||||
}
|
||||
g.clear().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
countDown();
|
||||
|
||||
|
@ -79,13 +84,14 @@ function readHRM() {
|
|||
if (!hrmInfo) return;
|
||||
|
||||
if (hrmOffset==0) {
|
||||
g.clearRect(0,100,239,239);
|
||||
g.moveTo(-100,0);
|
||||
g.clearRect(0,100,g.getWidth(),g.getHeight());
|
||||
lastHrmPt = [-100,0];
|
||||
}
|
||||
for (var i=0;i<2;i++) {
|
||||
var a = hrmInfo.raw[hrmOffset];
|
||||
hrmOffset++;
|
||||
y = E.clip(170 - (a*2),100,230);
|
||||
g.setColor(g.theme.fg).lineTo(hrmOffset, y);
|
||||
g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
|
||||
lastHrmPt = [hrmOffset, y];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.04: Make this clock do 12h and 24h
|
||||
0.05: setUI, screen size changes
|
||||
0.06: Use Bangle.setUI for button/launcher handling
|
||||
0.07: Update *on* the minute rather than every 15 secs
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* jshint esversion: 6 */
|
||||
const big = g.getWidth()>200;
|
||||
const timeFontSize = big?6:5;
|
||||
const dateFontSize = big?3:2;
|
||||
|
@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9;
|
|||
// Check settings for what type our clock should be
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
function drawSimpleClock() {
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
@ -60,11 +71,18 @@ function drawSimpleClock() {
|
|||
var gmt = da[5];
|
||||
g.setFont(font, gmtFontSize);
|
||||
g.drawString(gmt, xyCenter, yposGMT, true);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) drawSimpleClock();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// clean app screen
|
||||
|
@ -74,8 +92,5 @@ Bangle.setUI("clock");
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawSimpleClock, 15E3);
|
||||
|
||||
// draw now
|
||||
drawSimpleClock();
|
||||
draw();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: Modification of SimpleClock 0.04 to use Vectorfont
|
||||
0.02: Use Bangle.setUI for button/launcher handling
|
||||
0.03: Scale to BangleJS 2 and add locale
|
||||
0.03: Scale to BangleJS 2 and add locale
|
||||
0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* jshint esversion: 6 */
|
||||
const locale = require("locale");
|
||||
|
||||
var timeFontSize;
|
||||
|
@ -12,8 +11,7 @@ var yposDate;
|
|||
var yposYear;
|
||||
var yposGMT;
|
||||
|
||||
switch (process.env.BOARD) {
|
||||
case "EMSCRIPTEN":
|
||||
if (g.getWidth() > 200) {
|
||||
timeFontSize = 65;
|
||||
dateFontSize = 20;
|
||||
gmtFontSize = 10;
|
||||
|
@ -22,8 +20,7 @@ switch (process.env.BOARD) {
|
|||
yposDate = 130;
|
||||
yposYear = 175;
|
||||
yposGMT = 220;
|
||||
break;
|
||||
case "EMSCRIPTEN2":
|
||||
} else {
|
||||
timeFontSize = 48;
|
||||
dateFontSize = 15;
|
||||
gmtFontSize = 10;
|
||||
|
@ -32,12 +29,23 @@ switch (process.env.BOARD) {
|
|||
yposDate = 95;
|
||||
yposYear = 128;
|
||||
yposGMT = 161;
|
||||
break;
|
||||
}
|
||||
// Check settings for what type our clock should be
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
function drawSimpleClock() {
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
@ -76,23 +84,26 @@ function drawSimpleClock() {
|
|||
// draw gmt
|
||||
g.setFont(font, gmtFontSize);
|
||||
g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) drawSimpleClock();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
// clean app screen
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawSimpleClock, 15E3);
|
||||
|
||||
// draw now
|
||||
drawSimpleClock();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
draw();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///9nou30h/qJf8Ah/wBasK0ALhHcMBqALBgtABYsVqgLBAYILFqtUF4MVqoKEgoLFqALGEYQLFAwILEGAlV6tUlWoitXGAgLC1WqBYsBq+VqwLBAYPVMIUFBYN61Uq1oLBHgQLC1WohWqrwLDiteEIOggQDB2pICivqA4IFBlWq1YLCq/V9WkAoMa1YHBKQVf9XUAoMX1f1KgVVEYIpDEYILBLwIuBC4YFBMAMBLAILFLQILBrdV12UBYMW3VV8tAgt9q+2BYee6t9qEFuoLHroLBqte6wvDy+1ZoILBvdWSoeV9oLD+tfBYYFBBYTEBrq5CgN1aQNQgNVAALdEAAISBAYL1EfgISCAgIKDDAQSEAH4AQ"))
|
|
@ -0,0 +1,256 @@
|
|||
|
||||
// get settings
|
||||
var settings = require("Storage").readJSON("vernierrespirate.json",1)||{};
|
||||
settings.vibrateBPM = settings.vibrateBPM||27;
|
||||
// settings.vibrate; // undefined / "calculated" / "vernier"
|
||||
|
||||
function saveSettings() {
|
||||
require("Storage").writeJSON("vernierrespirate.json", settings);
|
||||
}
|
||||
|
||||
|
||||
g.clear();
|
||||
var graphHeight = g.getHeight()-100;
|
||||
var last = {
|
||||
time : Date.now(),
|
||||
x : 0,
|
||||
y : 24,
|
||||
};
|
||||
var avrValue;
|
||||
var aboveAvr = false;
|
||||
var lastBreath;
|
||||
var lastBreaths = [];
|
||||
var vibrateInterval;
|
||||
|
||||
function onMsg(txt) {
|
||||
print(txt);
|
||||
E.showMessage(txt);
|
||||
}
|
||||
|
||||
function setVibrate(isOn) {
|
||||
var wasOn = vibrateInterval!==undefined;
|
||||
if (isOn == wasOn) return;
|
||||
|
||||
if (isOn) {
|
||||
vibrateInterval = setInterval(function() {
|
||||
Bangle.buzz();
|
||||
}, 1000);
|
||||
} else {
|
||||
clearInterval(vibrateInterval);
|
||||
vibrateInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function onBreath() {
|
||||
var t = Date.now();
|
||||
if (lastBreath!==undefined) {
|
||||
// time between breaths
|
||||
var value = 60000 / (t-lastBreath);
|
||||
// average of last 3
|
||||
while (lastBreaths.length>=3) lastBreaths.shift(); // keep length small
|
||||
lastBreaths.push(value);
|
||||
value = E.sum(lastBreaths) / lastBreaths.length;
|
||||
// draw value
|
||||
g.reset();
|
||||
g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50);
|
||||
g.setFont("6x8").setFontAlign(0,0);
|
||||
g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95);
|
||||
g.setFont("Vector",40).setFontAlign(0,0);
|
||||
g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70);
|
||||
// set vibration IF we're doing it from our calculations
|
||||
if (settings.vibrate == "calculated")
|
||||
setVibrate(value > settings.vibrateBPM);
|
||||
}
|
||||
lastBreath = t;
|
||||
}
|
||||
|
||||
function onData(n, value) {
|
||||
g.reset();
|
||||
if (n==2) {
|
||||
function scale(v) {
|
||||
return Math.max(graphHeight - (1+v*4),24);
|
||||
}
|
||||
if (avrValue==undefined) avrValue=value;
|
||||
avrValue = avrValue*0.95 + value*0.05;
|
||||
if (avrValue < 1) avrValue = 1;
|
||||
if (value > avrValue) {
|
||||
if (!aboveAvr) onBreath();
|
||||
aboveAvr = true;
|
||||
} else aboveAvr = false;
|
||||
|
||||
var t = Date.now();
|
||||
var x = Math.round((t - last.time) / 100) // 10 per second
|
||||
if (last.x>=g.getWidth()) {
|
||||
x = 0;
|
||||
last.x = 0;
|
||||
last.time = t;
|
||||
g.clearRect(0,24,g.getWidth(),graphHeight);
|
||||
}
|
||||
var y = scale(value);
|
||||
g.setPixel(x, scale(avrValue), "#f00");
|
||||
g.drawLine(last.x, last.y, x, y);
|
||||
last.x = x;
|
||||
last.y = y;
|
||||
}
|
||||
if (n==4) {
|
||||
g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight());
|
||||
g.setFont("6x8").setFontAlign(0,0);
|
||||
g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45);
|
||||
g.setFont("Vector",40).setFontAlign(0,0);
|
||||
g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20);
|
||||
// set vibration IF we're doing it from our calculations
|
||||
if (settings.vibrate == "vernier")
|
||||
setVibrate(value > settings.vibrateBPM);
|
||||
}
|
||||
Bangle.setLCDPower(1); // ensure LCD is on
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var gatt, service, rx, tx;
|
||||
var rollingCounter = 0xFF;
|
||||
|
||||
// any button to exit
|
||||
Bangle.setUI("updown", function() {
|
||||
setVibrate(false);
|
||||
Bangle.buzz();
|
||||
try {
|
||||
if (gatt) gatt.disconnect();
|
||||
} catch (e) {
|
||||
}
|
||||
setTimeout(mainMenu, 1000);
|
||||
});
|
||||
|
||||
function sendCommand(subCommand) {
|
||||
const command = new Uint8Array(4 + subCommand.length);
|
||||
command.set(new Uint8Array(subCommand), 4);
|
||||
// Populate the packet header bytes
|
||||
command[0] = 0x58; // header
|
||||
command[1] = command.length;
|
||||
command[2] = --rollingCounter;
|
||||
command[3] = E.sum(command) & 0xFF; // checksum
|
||||
return tx.writeValue(command);
|
||||
}
|
||||
function firstSetBit(v) {
|
||||
return v & -v;
|
||||
}
|
||||
function handleResponse(dv) {
|
||||
//print(dv.buffer);
|
||||
var resType = dv.getUint8(0);
|
||||
if (resType==0x20) {
|
||||
// [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127])
|
||||
// 6 = data type = real
|
||||
// 6,0 = bit mask for sensors
|
||||
// 2 = value count
|
||||
if (dv.getUint8(4)!=6) return; //throw "Not float32 data";
|
||||
var sensorIds = dv.getUint16(5, true);
|
||||
// var count = dv.getUint8(7); doesn't seem right
|
||||
var offs = 9;
|
||||
while (sensorIds) {
|
||||
var value = dv.getFloat32(offs, true);
|
||||
var s = firstSetBit(sensorIds);
|
||||
if (isFinite(value)) onData(s,value);
|
||||
//else print(s,value);
|
||||
sensorIds &= ~s;
|
||||
offs += 4;
|
||||
}
|
||||
} else {
|
||||
var cmd = dv.getUint8(4); // cmd
|
||||
//print("CMD",dv.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
onMsg("Searching...");
|
||||
NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) {
|
||||
device.on("gattserverdisconnected", function() {
|
||||
onMsg("Device disconnected");
|
||||
});
|
||||
onMsg("Found. Connecting...");
|
||||
return device.gatt.connect({minInterval:20, maxInterval:20});
|
||||
}).then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112");
|
||||
}).then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb");
|
||||
}).then(function(c) {
|
||||
tx = c;
|
||||
return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe");
|
||||
}).then(function(c) {
|
||||
rx = c;
|
||||
rx.on('characteristicvaluechanged', function(event) {
|
||||
//print("EVT",event.target.value.buffer);
|
||||
handleResponse(event.target.value);
|
||||
});
|
||||
return rx.startNotifications();
|
||||
}).then(function() {
|
||||
onMsg("Init");
|
||||
sendCommand([ // init
|
||||
0x1a, 0xa5, 0x4a, 0x06,
|
||||
0x49, 0x07, 0x48, 0x08,
|
||||
0x47, 0x09, 0x46, 0x0a,
|
||||
0x45, 0x0b, 0x44, 0x0c,
|
||||
0x43, 0x0d, 0x42, 0x0e,
|
||||
0x41,
|
||||
]);
|
||||
/*setTimeout(function() {
|
||||
print("Set measurement period");
|
||||
var us = 100000; // period in us
|
||||
sendCommand([0x1b, 0xff, 0x00,
|
||||
us & 255,
|
||||
(us >> 8) & 255,
|
||||
(us >> 16) & 255,
|
||||
(us >> 24) & 255,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00]);
|
||||
}, 100);*/
|
||||
|
||||
/* setTimeout(function() {
|
||||
print("Get sensor info");
|
||||
sendCommand([0x51, 0]); // get sensor IDs
|
||||
// returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0]
|
||||
// 54 is the bit mask of available channels
|
||||
//sendCommand([106, 16]); // get sensor info
|
||||
}, 2000);*/
|
||||
|
||||
setTimeout(function() {
|
||||
onMsg("Start measurements");
|
||||
//https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588
|
||||
var channels = 6; // data channels 4 and 2
|
||||
sendCommand([ // start measurements
|
||||
0x18, 0xff, 0x01, channels,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00
|
||||
]);
|
||||
}, 500);
|
||||
}).catch(function() {
|
||||
onMsg("Connect Fail");
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
function mainMenu() {
|
||||
var vibText = ["No","Calculated","Vernier"];
|
||||
var vibValue = ["","calculated","vernier"];
|
||||
E.showMenu({"":{title:"Respiration Belt"},
|
||||
"< Back" : () => { saveSettings(); load(); },
|
||||
"Connect" : () => { saveSettings(); E.showMenu(); connect(); },
|
||||
"Vib" : {
|
||||
value : Math.max(vibValue.indexOf(settings.vibrate),0),
|
||||
format : v => vibText[v],
|
||||
min:0,max:2,
|
||||
onchange : v => { settings.vibrate=vibValue[v]; }
|
||||
},
|
||||
"BPM" : {
|
||||
value : settings.vibrateBPM,
|
||||
min:10,max:50,
|
||||
onchange : v => { settings.vibrateBPM=v; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mainMenu();
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,3 @@
|
|||
0.02: Works with light theme
|
||||
Doesn't drain battery by updating every 2 secs
|
||||
Fix alignment
|
|
@ -1,30 +1,17 @@
|
|||
(function(){
|
||||
//var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA"));
|
||||
//var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA=="));
|
||||
var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"));
|
||||
|
||||
function draw() {
|
||||
var cp = Bangle.setCompassPower;
|
||||
Bangle.setCompassPower = () => {
|
||||
cp.apply(Bangle, arguments);
|
||||
WIDGETS.compass.draw();
|
||||
};
|
||||
|
||||
WIDGETS.compass={area:"tr",width:24,draw:function() {
|
||||
g.reset();
|
||||
if (Bangle.isCompassOn()) {
|
||||
g.setColor(1,0.8,0); // on = amber
|
||||
g.setColor(g.theme.dark ? "#FC0" : "#F00");
|
||||
} else {
|
||||
g.setColor(0.3,0.3,0.3); // off = grey
|
||||
g.setColor(g.theme.dark ? "#333" : "#CCC");
|
||||
}
|
||||
g.drawImage(img, 10+this.x, 2+this.var);
|
||||
}
|
||||
|
||||
var timerInterval;
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
WIDGETS.compass.draw();
|
||||
if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000);
|
||||
} else {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
WIDGETS.compass={area:"tr",width:24,draw:draw};
|
||||
g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var);
|
||||
}};
|
||||
})();
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Tweaks for variable size widget system
|
||||
0.03: Ensure redrawing works with variable size widget system
|
||||
0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||
0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2)
|
||||
|
|
|
@ -1,13 +1,38 @@
|
|||
(() => {
|
||||
var currentBPM = undefined;
|
||||
var lastBPM = undefined;
|
||||
var firstBPM = true; // first reading since sensor turned on
|
||||
if (!Bangle.isLocked) return; // old firmware
|
||||
var currentBPM;
|
||||
var lastBPM;
|
||||
var isHRMOn = false;
|
||||
|
||||
function draw() {
|
||||
// turn on sensor when the LCD is unlocked
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
Bangle.setHRMPower(1,"widhrm");
|
||||
currentBPM = undefined;
|
||||
WIDGETS["hrm"].draw();
|
||||
} else {
|
||||
Bangle.setHRMPower(0,"widhrm");
|
||||
}
|
||||
});
|
||||
|
||||
var hp = Bangle.setHRMPower;
|
||||
Bangle.setHRMPower = () => {
|
||||
hp.apply(Bangle, arguments);
|
||||
isHRMOn = Bangle.isHRMOn();
|
||||
WIDGETS["hrm"].draw();
|
||||
};
|
||||
|
||||
Bangle.on('HRM',function(d) {
|
||||
currentBPM = d.bpm;
|
||||
lastBPM = currentBPM;
|
||||
WIDGETS["hrm"].draw();
|
||||
});
|
||||
|
||||
// add your widget
|
||||
WIDGETS["hrm"]={area:"tl",width:24,draw:function() {
|
||||
var width = 24;
|
||||
g.reset();
|
||||
g.setFont("6x8", 1);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0);
|
||||
g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background
|
||||
var bpm = currentBPM, isCurrent = true;
|
||||
if (bpm===undefined) {
|
||||
|
@ -16,36 +41,12 @@
|
|||
}
|
||||
if (bpm===undefined)
|
||||
bpm = "--";
|
||||
g.setColor(isCurrent ? "#ffffff" : "#808080");
|
||||
g.setColor(isCurrent ? g.theme.fg : "#808080");
|
||||
g.drawString(bpm, this.x+width/2, this.y+19);
|
||||
g.setColor(isCurrent ? "#ff0033" : "#808080");
|
||||
g.setColor(isHRMOn ? "#ff0033" : "#808080");
|
||||
g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1);
|
||||
g.setColor(-1);
|
||||
}
|
||||
}};
|
||||
|
||||
// redraw when the LCD turns on
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
Bangle.setHRMPower(1,"widhrm");
|
||||
firstBPM = true;
|
||||
currentBPM = undefined;
|
||||
WIDGETS["hrm"].draw();
|
||||
} else {
|
||||
Bangle.setHRMPower(0,"widhrm");
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('HRM',function(d) {
|
||||
if (firstBPM)
|
||||
firstBPM=false; // ignore the first one as it's usually rubbish
|
||||
else {
|
||||
currentBPM = d.bpm;
|
||||
lastBPM = currentBPM;
|
||||
}
|
||||
WIDGETS["hrm"].draw();
|
||||
});
|
||||
Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm");
|
||||
|
||||
// add your widget
|
||||
WIDGETS["hrm"]={area:"tl",width:24,draw:draw};
|
||||
Bangle.setHRMPower(!Bangle.isLocked(),"widhrm");
|
||||
})();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
0.01: First version
|
||||
0.02: Don't break if running on 2v08 firmware (just don't display anything)
|
||||
|
||||
0.03: Works with light theme
|
||||
Doesn't drain battery by updating every 2 secs
|
||||
fix alignment
|
||||
|
|
|
@ -1,28 +1,18 @@
|
|||
(function(){
|
||||
if (!Bangle.isHRMOn) return; // old firmware
|
||||
var hp = Bangle.setHRMPower;
|
||||
Bangle.setHRMPower = () => {
|
||||
hp.apply(Bangle, arguments);
|
||||
WIDGETS.widhrt.draw();
|
||||
};
|
||||
|
||||
function draw() {
|
||||
WIDGETS.widhrt={area:"tr",width:24,draw:function() {
|
||||
g.reset();
|
||||
if (Bangle.isHRMOn()) {
|
||||
g.setColor(1,0,0); // on = red
|
||||
g.setColor("#f00"); // on = red
|
||||
} else {
|
||||
g.setColor(0.3,0.3,0.3); // off = grey
|
||||
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
|
||||
}
|
||||
g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y);
|
||||
}
|
||||
|
||||
var timerInterval;
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
WIDGETS.widhrt.draw();
|
||||
if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000);
|
||||
} else {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
WIDGETS.widhrt={area:"tr",width:24,draw:draw};
|
||||
g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y);
|
||||
}};
|
||||
})();
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
0.02: Update custom.html for refactor; add README
|
||||
0.03: Update for larger secondary timezone display (#610)
|
||||
0.04: setUI, different screen sizes
|
||||
0.05: Now update *on* the minute rather than every 15 secs
|
||||
Fix rendering of single extra timezone on Bangle.js 2
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* jshint esversion: 6 */
|
||||
|
||||
const big = g.getWidth()>200;
|
||||
// Font for primary time and date
|
||||
const primaryTimeFontSize = big?6:5;
|
||||
|
@ -16,8 +14,13 @@ 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 yposTime = big ? 75 : 60;
|
||||
const yposTime2 = yposTime + (big ? 100 : 60);
|
||||
const yposDate = big ? 130 : 90;
|
||||
const yposWorld = big ? 170 : 120;
|
||||
|
||||
|
@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || [];
|
|||
// 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],
|
||||
// ],
|
||||
// fiveOffsets: [
|
||||
// ["Tokyo", 9],
|
||||
// ["UTC", 0],
|
||||
// ["Denver", -7],
|
||||
// ["Chicago", -6],
|
||||
// ["Miami", -5],
|
||||
// ],
|
||||
// };
|
||||
/*
|
||||
const mockOffsets = {
|
||||
zeroOffsets: [],
|
||||
oneOffset: [["UTC", 0]],
|
||||
twoOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
],
|
||||
fourOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
["Denver", -7],
|
||||
["Miami", -5],
|
||||
],
|
||||
fiveOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
["Denver", -7],
|
||||
["Chicago", -6],
|
||||
["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
|
||||
// offsets = mockOffsets.fiveOffsets; // should render first four in columns
|
||||
//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
|
||||
//offsets = mockOffsets.fiveOffsets; // should render first four in columns
|
||||
|
||||
// END TESTING CODE
|
||||
|
||||
// Check settings for what type our clock should be
|
||||
//var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
var secondInterval;
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function doublenum(x) {
|
||||
return x < 10 ? "0" + x : "" + x;
|
||||
|
@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) {
|
|||
return new Date(dt.getTime() + offset * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function drawSimpleClock() {
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
@ -111,9 +125,9 @@ function drawSimpleClock() {
|
|||
// 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, yposTime + 100, true);
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true);
|
||||
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
|
||||
|
||||
// draw Day, name of month, Date
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
|
@ -132,6 +146,8 @@ function drawSimpleClock() {
|
|||
g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
|
||||
}
|
||||
});
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
|
@ -141,18 +157,15 @@ Bangle.setUI("clock");
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 15 sec when screen is on
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
secondInterval = setInterval(drawSimpleClock, 15e3);
|
||||
drawSimpleClock(); // draw immediately
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// draw now and every 15 sec until display goes off
|
||||
drawSimpleClock();
|
||||
if (Bangle.isLCDOn()) {
|
||||
secondInterval = setInterval(drawSimpleClock, 15e3);
|
||||
}
|
||||
// draw now
|
||||
draw();
|
||||
|
|
2
core
2
core
|
@ -1 +1 @@
|
|||
Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479
|
||||
Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a
|
19
css/main.css
19
css/main.css
|
@ -35,6 +35,21 @@
|
|||
top: 36px;
|
||||
left: -24px;
|
||||
}
|
||||
.btn-favourite {
|
||||
color: red;
|
||||
.btn.btn-favourite { color: red; }
|
||||
.btn.btn-favourite:hover { color: red; }
|
||||
|
||||
.icon.icon-emulator { text-indent: 0px; } /*override spectre*/
|
||||
.icon.icon-emulator::before {
|
||||
content: "\01F5B5";
|
||||
}
|
||||
.icon.icon-favourite { text-indent: 0px; } /*override spectre*/
|
||||
.icon.icon-favourite::before {
|
||||
content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */
|
||||
}
|
||||
.icon.icon-favourite-active::before {
|
||||
content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */
|
||||
}
|
||||
.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/
|
||||
.icon.icon-interface::before {
|
||||
content: "\01F5AB";
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"]
|
18
loader.js
18
loader.js
|
@ -133,8 +133,8 @@ window.addEventListener('load', (event) => {
|
|||
});
|
||||
});
|
||||
|
||||
// Hook onto device chooser dropdown
|
||||
window.addEventListener('load', (event) => {
|
||||
// Hook onto device chooser dropdown
|
||||
htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => {
|
||||
button.addEventListener("click", event => {
|
||||
var a = event.target;
|
||||
|
@ -144,4 +144,20 @@ window.addEventListener('load', (event) => {
|
|||
document.querySelector(".devicetype-nav span").innerText = a.innerText;
|
||||
});
|
||||
});
|
||||
|
||||
// Button to install all default apps in one go
|
||||
document.getElementById("installdefault").addEventListener("click",event=>{
|
||||
getInstalledApps().then(() => {
|
||||
if (device.id == "BANGLEJS")
|
||||
return httpGet("defaultapps_banglejs.json");
|
||||
if (device.id == "BANGLEJS2")
|
||||
return httpGet("defaultapps_banglejs2.json");
|
||||
throw new Error("Unknown device "+device.id);
|
||||
}).then(json=>{
|
||||
return installMultipleApps(JSON.parse(json), "default");
|
||||
}).catch(err=>{
|
||||
Progress.hide({sticky:true});
|
||||
showToast("App Install failed, "+err,"error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue