1
0
Fork 0

merge with upstream

master
hughbarney 2021-10-21 19:59:49 +01:00
commit 9f6602c596
42 changed files with 832 additions and 285 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

1
apps/bthrm/ChangeLog Normal file
View File

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

45
apps/bthrm/README.md Normal file
View File

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

1
apps/bthrm/app-icon.js Normal file
View File

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

BIN
apps/bthrm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

79
apps/bthrm/boot.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA="))
require("heatshrink").decompress(atob("mEw4UA///g3yrv/7f+Jf4AJgNVoAEGAANVAAIEGCIQABoAEEBYMFAwVQAggLBioGCqgEEFIgAGFwdXBYw1Dr4LKrwLHIIVaBYxNDvXVBanVteVBZGVt+VKooLBq+19u1JItQgNW0vlBYIxEL4Ne1u18taGIN9BYUD1XvBYN62+q1a0D1d7ytttYLEWYV6BYNt93VEYKzCita6t59vqX4sFIgN70tqa4pUBTgO1vbvFgB0BKQNZawYACdYNeytdFwgwCBYJ2DFwQwCqoxBFwwABBYoKEGAKyDFwgwDFw4kDERBVDEQ4kEEQ4kDBRAYBERBuCNAoA/AA4="))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///9nou30h/qJf8Ah/wBasK0ALhHcMBqALBgtABYsVqgLBAYILFqtUF4MVqoKEgoLFqALGEYQLFAwILEGAlV6tUlWoitXGAgLC1WqBYsBq+VqwLBAYPVMIUFBYN61Uq1oLBHgQLC1WohWqrwLDiteEIOggQDB2pICivqA4IFBlWq1YLCq/V9WkAoMa1YHBKQVf9XUAoMX1f1KgVVEYIpDEYILBLwIuBC4YFBMAMBLAILFLQILBrdV12UBYMW3VV8tAgt9q+2BYee6t9qEFuoLHroLBqte6wvDy+1ZoILBvdWSoeV9oLD+tfBYYFBBYTEBrq5CgN1aQNQgNVAALdEAAISBAYL1EfgISCAgIKDDAQSEAH4AQ"))

View File

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

3
apps/widcom/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.02: Works with light theme
Doesn't drain battery by updating every 2 secs
Fix alignment

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -1 +1 @@
Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479
Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a

View File

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

2
css/spectre.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"]

View File

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