Merge branch 'espruino:master' into master

master
Peer David 2022-02-28 12:03:28 +01:00 committed by GitHub
commit f385d384c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 631 additions and 100 deletions

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Write available data on reset or kill
0.03: Buzz short on every finished measurement and longer if all are done

View File

@ -75,7 +75,6 @@ function write(){
data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues;
data += "\n";
file.write(data);
Bangle.buzz(500);
}
function onBtHrm(e) {
@ -87,6 +86,11 @@ function onBtHrm(e) {
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
hrvValues[hrvSlots[currentSlot]] = hrv;
currentSlot++;
if (currentSlot == hrvSlots.length){
Bangle.buzz(500)
} else {
Bangle.buzz(50);
}
}
}

View File

@ -2,7 +2,7 @@
"id": "bthrv",
"name": "Bluetooth Heart Rate variance calculator",
"shortName": "BT HRV",
"version": "0.02",
"version": "0.03",
"description": "Calculates HRV from a a BT HRM with interval data",
"icon": "app.png",
"type": "app",

View File

@ -20,3 +20,6 @@
Color depending on value (green -> red, red -> green) option
Good HRM value will not be overwritten so fast anymore
0.10: Use roboto font for time, date and day of week and center align them
0.11: New color option: foreground color
Improve performance, reduce memory usage
Small optical adjustments

View File

@ -3,23 +3,8 @@ const storage = require("Storage");
const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
const shoesIcon = atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA");
const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
const temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA");
const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
// Actual height 39 (40 - 2)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16));
@ -67,7 +52,7 @@ const colorGreen = '#008000';
const colorBlue = '#0000ff';
const colorYellow = '#ffff00';
const widgetOffset = showWidgets ? 24 : 0;
const dowOffset = circleCount == 3 ? 22 : 24; // dow offset relative to date
const dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date
const h = g.getHeight() - widgetOffset;
const w = g.getWidth();
const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset;
@ -103,21 +88,24 @@ const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
const iconOffset = circleCount == 3 ? 6 : 8;
const defaultCircleTypes = ["steps", "hr", "battery", "weather"];
function hideWidgets() {
/*
* we are not drawing the widgets as we are taking over the whole screen
* so we will blank out the draw() functions of each widget and change the
* area to the top bar doesn't get cleared.
*/
if (WIDGETS && typeof WIDGETS === "object") {
for (let wd of WIDGETS) {
wd.draw = () => {};
wd.area = "";
}
}
}
function draw() {
g.clear(true);
if (!showWidgets) {
/*
* we are not drawing the widgets as we are taking over the whole screen
* so we will blank out the draw() functions of each widget and change the
* area to the top bar doesn't get cleared.
*/
if (WIDGETS && typeof WIDGETS === "object") {
for (let wd of WIDGETS) {
wd.draw = () => {};
wd.area = "";
}
}
hideWidgets();
} else {
Bangle.drawWidgets();
}
@ -129,7 +117,7 @@ function draw() {
g.setFontRobotoRegular50NumericOnly();
g.setFontAlign(0, -1);
g.setColor(colorFg);
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8);
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6);
now = Math.round(new Date().getTime() / 1000);
// date & dow
@ -138,10 +126,19 @@ function draw() {
g.drawString(locale.date(new Date()), w / 2, h2);
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
drawCircle(1);
drawCircle(2);
drawCircle(3);
if (circleCount >= 4) drawCircle(4);
// draw the circles a little bit delayed so we decrease the blocking time
setTimeout(function() {
drawCircle(1);
}, 1);
setTimeout(function() {
drawCircle(2);
}, 1);
setTimeout(function() {
drawCircle(3);
}, 1);
setTimeout(function() {
if (circleCount >= 4) drawCircle(4);
}, 1);
}
function drawCircle(index) {
@ -248,6 +245,9 @@ function getGradientColor(color, percent) {
const colorList = [
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
];
if (color == "fg") {
color = colorFg;
}
if (color == "green-red") {
const colorIndex = Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00";
@ -325,6 +325,8 @@ function drawStepsDistance(w) {
function drawHeartRate(w) {
if (!w) w = getCircleXPosition("hr");
const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
drawCircleBackground(w);
const color = getCircleColor("hr");
@ -349,6 +351,8 @@ function drawBattery(w) {
if (!w) w = getCircleXPosition("battery");
const battery = E.getBattery();
const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
drawCircleBackground(w);
let color = getCircleColor("battery");
@ -426,6 +430,10 @@ function drawSunProgress(w) {
if (!w) w = getCircleXPosition("sunprogress");
const percent = getSunProgress();
// sunset icons:
const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
drawCircleBackground(w);
const color = getCircleColor("sunprogress");
@ -559,6 +567,18 @@ function windAsBeaufort(windInKmh) {
*/
function getWeatherIconByCode(code) {
const codeGroup = Math.round(code / 100);
// weather icons:
const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
switch (codeGroup) {
case 2:
return weatherStormy;
@ -823,7 +843,6 @@ if (isCircleEnabled("hr")) {
enableHRMSensor();
}
Bangle.setUI("clock");
Bangle.loadWidgets();

View File

@ -1,7 +1,7 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
"version":"0.10",
"version":"0.11",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],

View File

@ -14,8 +14,10 @@
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"];
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"];
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"];
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"];
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
"cyan", "white", "black", "green->red", "red->green", "foreground"];
const weatherData = ["empty", "humidity", "wind"];

View File

@ -5,3 +5,4 @@
0.23: Customizer! Unused fonts no longer take up precious memory.
0.24: Added previews to the customizer.
0.25: Fixed a bug that would let widgets change the color of the clock.
0.26: Time formatted to locale

View File

@ -1,3 +1,11 @@
var is12;
function getHours(d) {
var h = d.getHours();
if (is12===undefined) is12 = (require('Storage').readJSON('setting.json',1)||{})["12hour"];
if (!is12) return h;
return (h%12==0) ? 12 : h%12;
}
exports.drawClock = function(fontIndex) {
var digits = [];
fontFile=require("Storage").read("contourclock-"+Math.abs(parseInt(fontIndex+0.5))+".json");
@ -15,8 +23,8 @@ exports.drawClock = function(fontIndex) {
var y = g.getHeight()/2-digits[0].height/2;
var date = new Date();
g.clearRect(0,38,g.getWidth()-1,138);
d1=parseInt(date.getHours()/10);
d2=parseInt(date.getHours()%10);
d1=parseInt(getHours(date)/10);
d2=parseInt(getHours(date)%10);
d3=10;
d4=parseInt(date.getMinutes()/10);
d5=parseInt(date.getMinutes()%10);

View File

@ -1,7 +1,7 @@
{ "id": "contourclock",
"name": "Contour Clock",
"shortName" : "Contour Clock",
"version":"0.25",
"version":"0.26",
"icon": "app.png",
"description": "A Minimalist clockface with large Digits. Now with more fonts!",
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],

View File

@ -1,2 +1,3 @@
0.01: first release
0.02: added settings menu to change color
0.03: fix metadata.json to allow setting as clock

View File

@ -1,9 +1,10 @@
{ "id": "daisy",
"name": "Daisy",
"version":"0.02",
"version":"0.03",
"dependencies": {"mylocation":"app"},
"description": "A clock based on the Pastel clock with large ring guage for steps",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"screenshot_daisy2.jpg"}],

View File

@ -0,0 +1,2 @@
0.01: Initial version
0.02: Added BangleJS Two

View File

@ -1,4 +1,4 @@
The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3. This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required.
The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3 (Mapped to top touch and bottom touch on Bangle 2). This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required.
When you press the middle button on the side, the HR monitor starts, the alarm will trigger when your heart rate average drops to the limit youve set and has a certain level of steadiness that is determined by a assessing the variance over several readings - the sensitivity of this variance can be adjusted in a variable in the app's code under 'ADVANCED SETTINGS' if needed. The code also has a basic logging function which shows, in a CSV file, when you started the HR tracker and when the alarm was triggered.

View File

@ -3,6 +3,8 @@ var lower_limit_BPM = 49;
var upper_limit_BPM = 140;
var deviation_threshold = 3;
var ISBANGLEJS1 = process.env.HWVERSION==1;
var target_heartrate = 70;
var heartrate_set;
@ -33,25 +35,39 @@ function btn2Pressed() {
}
function update_target_HR(){
g.clear();
g.setColor("#00ff7f");
g.setFont("6x8", 4);
g.setFontAlign(0,0); // center font
if (process.env.HWVERSION==1) {
g.setColor("#00ff7f");
g.setFont("6x8", 4);
g.setFontAlign(0,0); // center font
g.drawString(target_heartrate, 120,120);
g.setFont("6x8", 2);
g.setFontAlign(-1,-1);
g.drawString("-", 220, 200);
g.drawString("+", 220, 40);
g.drawString("GO", 210, 120);
g.setColor("#ffffff");
g.setFontAlign(0,0); // center font
g.drawString("target HR", 120,90);
g.setFont("6x8", 1);
g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170);
g.drawString(target_heartrate, 120,120);
g.setFont("6x8", 2);
g.setFontAlign(-1,-1);
g.drawString("-", 220, 200);
g.drawString("+", 220, 40);
g.drawString("GO", 210, 120);
g.setColor("#ffffff");
g.setFontAlign(0,0); // center font
g.drawString("target HR", 120,90);
g.setFont("6x8", 1);
g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170);
} else {
g.setFont("6x8", 4);
g.setFontAlign(0,0); // center font
g.drawString(target_heartrate, 88,88);
g.setFont("6x8", 2);
g.setFontAlign(-1,-1);
g.drawString("-", 160, 160);
g.drawString("+", 160, 10);
g.drawString("GO", 150, 88);
g.setFontAlign(0,0); // center font
g.drawString("target HR", 88,65);
g.setFont("6x8", 1);
g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 88,150);
}
g.setFont("6x8",3);
g.flip();
@ -105,8 +121,13 @@ function checkHR() {
average_HR = average(HR_samples).toFixed(0);
stdev_HR = getStandardDeviation (HR_samples).toFixed(1);
g.drawString("HR: " + average_HR, 120,100);
g.drawString("STDEV: " + stdev_HR, 120,160);
if (ISBANGLEJS1) {
g.drawString("HR: " + average_HR, 120,100);
g.drawString("STDEV: " + stdev_HR, 120,160);
} else {
g.drawString("HR: " + average_HR, 88,60);
g.drawString("STDEV: " + stdev_HR, 88,90);
}
HR_samples = [];
if(average_HR < target_heartrate && stdev_HR < deviation_threshold){
@ -131,12 +152,26 @@ function checkHR() {
update_target_HR();
setWatch(btn1Pressed, BTN1, {repeat:true});
setWatch(btn2Pressed, BTN2, {repeat:true});
setWatch(btn3Pressed, BTN3, {repeat:true});
if (ISBANGLEJS1) {
// Bangle 1
setWatch(btn1Pressed, BTN1, {repeat:true});
setWatch(btn2Pressed, BTN2, {repeat:true});
setWatch(btn3Pressed, BTN3, {repeat:true});
} else {
// Bangle 2
setWatch(btn2Pressed, BTN1, { repeat: true });
Bangle.on('touch', function(zone, e) {
if (e.y < g.getHeight() / 2) {
btn1Pressed();
}
if (e.y > g.getHeight() / 2) {
btn3Pressed();
}
});
}
Bangle.on('HRM',function(hrm) {
if(trigger_count < 2){
if (firstBPM)
firstBPM=false; // ignore the first one as it's usually rubbish

View File

@ -2,11 +2,11 @@
"id": "edisonsball",
"name": "Edison's Ball",
"shortName": "Edison's Ball",
"version": "0.01",
"version": "0.02",
"description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness",
"icon": "app-icon.png",
"tags": "",
"supports": ["BANGLEJS"],
"tags": "sleep,hyponagogia,quick,nap",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"edisonsball.app.js","url":"app.js"},

View File

@ -1,3 +1,4 @@
0.01: Initial Release
0.02: Minor tweaks for light theme
0.03: Made images 2 bit and fixed theme honoring
0.03: Made images 2 bit and fixed theme honoring
0.04: Fixed date font alignment and changed date font to match a real Rolex

View File

@ -1,11 +1,13 @@
# Rolex
![](screenshot.png)
![](screenshot.png) ![](screenshot1.png)
Created with the aid of the Espruino documentation and looking through many of the wonderful exising watchfaces that have been made.
This has not been tested on a watch yet as I haven't aquired one but has been tested in the emulator.
The hands don't rotate dead on center but they're as close as I could get them to.
Colour switches based on watch theme so if you want a white on black change your watch theme to dark, and for black on white change it to light.
Special thanks to:
* rozek (for his updated widget draw code for utilization with background images)
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)

View File

@ -28,6 +28,14 @@ var imgSec = {
buffer : E.toArrayBuffer(atob("v/q//r/+v/qv+q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qv+v/6/D/wD8PDw8PwD/w///6v+qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqr+r//v///X/1X/Vf9V/1X/1///+//q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq6qrqg=="))
};
/* use font closer to Rolex */
Graphics.prototype.setFontRolexFont = function(scale) {
// Actual height 12 (12 - 1)
this.setFontCustom(atob("AAAABAACAAAAAYAHgA4AOABgAAAAA/gD/gMBgQBAwGA/4A/gAAAAAAIBAQCB/8D/4AAQAAAAAAAAAwEDAYEBQIEgYxA/CA4MAAAAAAAAgIBAhCBCEDOYHvgCOAAAAAAABgANAAyAHEAf/A/+AAgABAAAAAAADBAcCBsECYIEYgIeAAAAAAAHwAfwB5wGggZBAjGBH4CDgAAACAAYAAgABAMCDwE+APgAYAAAAAAABxwH3wJwgRhA3iB54AhgAAAAAAPhA/iBDMCCQGHgH+AHwAAAAAAAQIAgQAAAAAA="), 46, atob("BAUJCQkJCQkJCQkJBQ=="), 17+(scale<<8)+(1<<16));
return this;
};
/* Set variables to get screen width, height and center points */
let W = g.getWidth();
@ -36,10 +44,6 @@ let cx = W/2;
let cy = H/2;
let Timeout;
/* set font */
require("Font4x5Numeric").add(Graphics);
Bangle.loadWidgets();
/* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */
@ -106,9 +110,10 @@ function drawHands() {
g.drawImage(imgHour,cx-22*hourSin,cy+22*hourCos,{rotate:hourAngle});
g.drawImage(imgMin,cx-34*minSin,cy+34*minCos,{rotate:minAngle});
g.drawImage(imgSec,cx-25*secSin,cy+25*secCos,{rotate:secAngle});
g.setFont("4x5Numeric:3");
g.setFontRolexFont();
g.setColor(g.theme.bg);
g.drawString(d.getDate(),157,81);
g.setFontAlign(0,0,0);
g.drawString(d.getDate(),165,89);
}
function drawBackground() {

View File

@ -2,8 +2,8 @@
"name": "rolex",
"shortName":"rolex",
"icon": "rolex.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.03",
"screenshots": [{"url":"screenshot1.png"}],
"version":"0.04",
"description": "A rolex like watch face",
"tags": "clock",
"type": "clock",
@ -14,4 +14,4 @@
{"name":"rolex.app.js","url":"app.js"},
{"name":"rolex.img","url":"app-icon.js","evaluate":true}
]
}
}

BIN
apps/rolex/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Rename "Activity" in "Motion" and display the true values for it

View File

@ -5,5 +5,5 @@ It can display :
- time
- date
- hrm
- activity
- motion
- steps

View File

@ -1,6 +1,5 @@
var locale = require("locale");
var fontColor = g.theme.dark ? "#0f0" : "#000";
var startY = 24;
var paddingY = 2;
var font6x8At4Size = 32;
var font6x8At2Size = 18;
@ -15,25 +14,25 @@ function setFontSize(pos){
}
function clearField(pos){
var yStartPos = startY +
var yStartPos = Bangle.appRect.y +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos-1) +
font6x8At2Size * Math.max(0, pos-2);
var yEndPos = startY +
var yEndPos = Bangle.appRect.y +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos) +
font6x8At2Size * Math.max(0, pos-1);
g.clearRect(0, yStartPos, 240, yEndPos);
g.clearRect(Bangle.appRect.x, yStartPos, Bangle.appRect.x2, yEndPos);
}
function clearWatchIfNeeded(now){
if(now.getMinutes() % 10 == 0)
g.clearRect(0, startY, 240, 240);
g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2);
}
function drawLine(line, pos){
setFontSize(pos);
var yPos = startY +
var yPos = Bangle.appRect.y +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos-1) +
font6x8At2Size * Math.max(0, pos-2);
@ -76,11 +75,10 @@ function drawHRM(pos){
function drawActivity(pos){
clearField(pos);
var health = Bangle.getHealthStatus('last');
var steps_formated = ">Activity: " + parseInt(health.movement/10);
var steps_formated = ">Motion: " + parseInt(health.movement);
drawLine(steps_formated, pos);
}
function draw(){
var curPos = 1;
g.reset();
@ -109,7 +107,6 @@ function draw(){
drawInput(now, curPos);
}
Bangle.on('HRM',function(hrmInfo) {
if(hrmInfo.confidence >= settings.HRMinConfidence)
heartRate = hrmInfo.bpm;
@ -127,12 +124,12 @@ var settings = Object.assign({
showActivity: true,
showStepCount: true,
}, require('Storage').readJSON("terminalclock.json", true) || {});
// draw immediately at first
draw();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// draw immediately at first
draw();
var secondInterval = setInterval(draw, 10000);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,18 +1,26 @@
{
"id": "timeandlife",
"name": "Time and Life",
"shortName":"Time and Lfie",
"shortName": "Time and Life",
"icon": "app.png",
"version":"0.01",
"version": "0.01",
"description": "A simple watchface which displays the time when the screen is tapped and decay according to the rules of Conway's game of life.",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator":true,
"supports": [
"BANGLEJS2"
],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"timeandlife.app.js","url":"app.js"},
{"name":"timeandlife.img","url":"app-icon.js","evaluate":true}
{
"name": "timeandlife.app.js",
"url": "app.js"
},
{
"name": "timeandlife.img",
"url": "app-icon.js",
"evaluate": true
}
]
}
}

57
apps/widadjust/README.md Normal file
View File

@ -0,0 +1,57 @@
# Adjust Clock
Adjusts clock continually in the background to counter clock drift.
## Usage
First you need to determine the clock drift of your watch in PPM (parts per million).
For example if you measure that your watch clock is too fast by 5 seconds in 24 hours,
then PPM is `5 / (24*60*60) * 1000000 = 57.9`.
Then set PPM in settings and this widget will continually adjust the clock by that amount.
## Settings
See **Basic logic** below for more details.
- **PPM x 10** - change PPM in steps of 10
- **PPM x 1** - change PPM in steps of 1
- **PPM x 0.1** - change PPM in steps of 0.1
- **Update Interval** - How often to update widget and clock error.
- **Threshold** - Threshold for adjusting clock.
When clock error exceeds this threshold, clock is adjusted with `setTime`.
- **Save State** - If `On` clock error state is saved to file when widget exits, if needed.
That is recommended and default setting.
If `Off` clock error state is forgotten and reset to 0 whenever widget is restarted,
for example when going to Launcher. This can cause significant inaccuracy especially
with large **Update Interval** or **Threshold**.
- **Debug Log** - If `On` some debug information is logged to file `widadjust.log`.
## Display
Widget shows clock error in milliseconds and PPM.
## Basic logic
- When widget starts, clock error state is loaded from file `widadjust.state`.
- While widget is running, widget display and clock error is updated
periodically (**Update Interval**) according to **PPM**.
- When clock error exceeds **Threshold** clock is adjusted with `setTime`.
- When widget exists, clock error state is saved to file `widadjust.state` if needed.
## Services
Other apps/widgets can use `WIDGETS.adjust.now()` to request current adjusted time.
To support also case where this widget isn't present, the following code can be used:
```
function adjustedNow() {
return WIDGETS.adjust ? WIDGETS.adjust.now() : Date.now();
}
```
## Acknowledgment
Uses [Clock Settings](https://icons8.com/icon/tQvI71EfIWy3/clock-settings)
icon by [Icons8](https://icons8.com).

BIN
apps/widadjust/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,19 @@
{
"id": "widadjust",
"name": "Adjust Clock",
"icon": "icon.png",
"version": "0.01",
"description": "Adjusts clock continually in the background to counter clock drift",
"type": "widget",
"tags": "widget",
"supports": [ "BANGLEJS", "BANGLEJS2" ],
"readme": "README.md",
"storage": [
{ "name": "widadjust.wid.js", "url": "widget.js" },
{ "name": "widadjust.settings.js", "url": "settings.js" }
],
"data": [
{ "name": "widadjust.json" },
{ "name": "widadjust.state" }
]
}

120
apps/widadjust/settings.js Normal file
View File

@ -0,0 +1,120 @@
(function(back) {
const SETTINGS_FILE = 'widadjust.json';
const STATE_FILE = 'widadjust.state';
const DEFAULT_ADJUST_THRESHOLD = 100;
let thresholdV = [ 10, 25, 50, 100, 250, 500, 1000 ];
const DEFAULT_UPDATE_INTERVAL = 60000;
let intervalV = [ 10000, 30000, 60000, 180000, 600000, 1800000, 3600000 ];
let intervalN = [ "10 s", "30 s", "1 m", "3 m", "10 m", "30 m", "1 h" ];
let stateFileErased = false;
let settings = Object.assign({
advanced: false,
saveState: true,
debugLog: false,
ppm: 0,
adjustThreshold: DEFAULT_ADJUST_THRESHOLD,
updateInterval: DEFAULT_UPDATE_INTERVAL,
}, require('Storage').readJSON(SETTINGS_FILE, true) || {});
if (thresholdV.indexOf(settings.adjustThreshold) == -1) {
settings.adjustThreshold = DEFAULT_ADJUST_THRESHOLD;
}
if (intervalV.indexOf(settings.updateInterval) == -1) {
settings.updateInterval = DEFAULT_UPDATE_INTERVAL;
}
function onPpmChange(v) {
settings.ppm = v;
mainMenu['PPM x 10' ].value = v;
mainMenu['PPM x 1' ].value = v;
mainMenu['PPM x 0.1'].value = v;
}
let mainMenu = {
'': { 'title' : 'Adjust Clock' },
'< Back': () => {
require('Storage').writeJSON(SETTINGS_FILE, settings);
back();
},
/*
// NOT FULLY WORKING YET
'Mode': {
value: settings.advanced,
format: v => v ? 'Advanced' : 'Basic',
onchange: () => {
settings.advanced = !settings.advanced;
}
},
*/
'PPM x 10' : {
value: settings.ppm,
format: v => v.toFixed(1),
step: 10,
onchange : onPpmChange,
},
'PPM x 1' : {
value: settings.ppm,
format: v => v.toFixed(1),
step: 1,
onchange : onPpmChange,
},
'PPM x 0.1' : {
value: settings.ppm,
format: v => v.toFixed(1),
step: 0.1,
onchange : onPpmChange,
},
'Update Interval': {
value: intervalV.indexOf(settings.updateInterval),
min: 0,
max: intervalV.length - 1,
format: v => intervalN[v],
onchange: v => {
settings.updateInterval = intervalV[v];
},
},
'Threshold': {
value: thresholdV.indexOf(settings.adjustThreshold),
min: 0,
max: thresholdV.length - 1,
format: v => thresholdV[v] + " ms",
onchange: v => {
settings.adjustThreshold = thresholdV[v];
},
},
'Save State': {
value: settings.saveState,
format: v => v ? 'On' : 'Off',
onchange: () => {
settings.saveState = !settings.saveState;
if (!settings.saveState && !stateFileErased) {
stateFileErased = true;
require("Storage").erase(STATE_FILE);
}
},
},
'Debug Log': {
value: settings.debugLog,
format: v => v ? 'On' : 'Off',
onchange: () => {
settings.debugLog = !settings.debugLog;
},
},
};
E.showMenu(mainMenu);
})

244
apps/widadjust/widget.js Normal file
View File

@ -0,0 +1,244 @@
(() => {
// ======================================================================
// CONST
const DEBUG_LOG_FILE = 'widadjust.log';
const SETTINGS_FILE = 'widadjust.json';
const STATE_FILE = 'widadjust.state';
const DEFAULT_ADJUST_THRESHOLD = 100;
const DEFAULT_UPDATE_INTERVAL = 60 * 1000;
const MIN_INTERVAL = 10 * 1000;
const MAX_CLOCK_ERROR_FROM_SAVED_STATE = 2000;
const SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD = 1;
const SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD = 1;
const SAVE_STATE_PPM_DELTA_THRESHOLD = 1;
// Widget width.
const WIDTH = 22;
// ======================================================================
// VARIABLES
let settings;
let saved;
let lastClockCheckTime = Date.now();
let lastClockErrorUpdateTime;
let clockError;
let currentUpdateInterval;
let lastPpm = null;
let debugLogFile = null;
// ======================================================================
// FUNCTIONS
function clockCheck() {
let now = Date.now();
let elapsed = now - lastClockCheckTime;
lastClockCheckTime = now;
let prevUpdateInterval = currentUpdateInterval;
currentUpdateInterval = settings.updateInterval;
setTimeout(clockCheck, lastClockCheckTime + currentUpdateInterval - Date.now());
// If elapsed time differs a lot from expected,
// some other app probably used setTime to change clock significantly.
// -> reset clock error since elapsed time can't be trusted
if (Math.abs(elapsed - prevUpdateInterval) > 10 * 1000) {
// RESET CLOCK ERROR
clockError = 0;
lastClockErrorUpdateTime = now;
debug(
'Looks like some other app used setTime, so reset clockError. (elapsed = ' +
elapsed.toFixed(0) + ')'
);
WIDGETS.adjust.draw();
} else if (!settings.advanced) {
// UPDATE CLOCK ERROR WITHOUT TEMPERATURE COMPENSATION
updateClockError(settings.ppm);
} else {
// UPDATE CLOCK ERROR WITH TEMPERATURE COMPENSATION
Bangle.getPressure().then(d => {
let temp = d.temperature;
updateClockError(settings.ppm0 + settings.ppm1 * temp + settings.ppm2 * temp * temp);
}).catch(e => {
WIDGETS.adjust.draw();
});
}
}
function debug(line) {
console.log(line);
if (debugLogFile !== null) {
debugLogFile.write(line + '\n');
}
}
function draw() {
g.reset().setFont('6x8').setFontAlign(0, 0);
g.clearRect(this.x, this.y, this.x + WIDTH - 1, this.y + 23);
g.drawString(Math.round(clockError), this.x + WIDTH/2, this.y + 9);
if (lastPpm !== null) {
g.setFont('4x6').setFontAlign(0, 1);
g.drawString(lastPpm.toFixed(1), this.x + WIDTH/2, this.y + 23);
}
}
function loadSettings() {
settings = Object.assign({
advanced: false,
saveState: true,
debugLog: false,
ppm: 0,
ppm0: 0,
ppm1: 0,
ppm2: 0,
adjustThreshold: DEFAULT_ADJUST_THRESHOLD,
updateInterval: DEFAULT_UPDATE_INTERVAL,
}, require('Storage').readJSON(SETTINGS_FILE, true) || {});
if (settings.debugLog) {
if (debugLogFile === null) {
debugLogFile = require('Storage').open(DEBUG_LOG_FILE, 'a');
}
} else {
debugLogFile = null;
}
settings.updateInterval = Math.max(settings.updateInterval, MIN_INTERVAL);
}
function onQuit() {
let now = Date.now();
// WIP
let ppm = (lastPpm !== null) ? lastPpm : settings.ppm;
let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000;
let save = false;
if (! settings.saveState) {
debug(new Date(now).toISOString() + ' QUIT');
} else if (saved === undefined) {
save = true;
debug(new Date(now).toISOString() + ' QUIT & SAVE STATE');
} else {
let elapsedSaved = now - saved.time;
let estimatedClockError = saved.clockError + elapsedSaved * saved.ppm / 1000000;
let clockErrorDelta = updatedClockError - estimatedClockError;
let clockErrorDeltaInPpm = clockErrorDelta / elapsedSaved * 1000000;
let ppmDelta = ppm - saved.ppm;
let debugA = new Date(now).toISOString() + ' QUIT';
let debugB =
'\n> ' + updatedClockError.toFixed(2) + ' - ' + estimatedClockError.toFixed(2) + ' = ' +
clockErrorDelta.toFixed(2) + ' (' +
clockErrorDeltaInPpm.toFixed(1) + ' PPM) ; ' +
ppm.toFixed(1) + ' - ' + saved.ppm.toFixed(1) + ' = ' + ppmDelta.toFixed(1);
if ((Math.abs(clockErrorDelta) >= SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD
&& Math.abs(clockErrorDeltaInPpm) >= SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD
) || Math.abs(ppmDelta) >= SAVE_STATE_PPM_DELTA_THRESHOLD
)
{
save = true;
debug(debugA + ' & SAVE STATE' + debugB);
} else {
debug(debugA + debugB);
}
}
if (save) {
require('Storage').writeJSON(STATE_FILE, {
counter: (saved === undefined) ? 1 : saved.counter + 1,
time: Math.round(now),
clockError: Math.round(updatedClockError * 1000) / 1000,
ppm: Math.round(ppm * 1000) / 1000,
});
}
}
function updateClockError(ppm) {
let now = Date.now();
let elapsed = now - lastClockErrorUpdateTime;
let drift = elapsed * ppm / 1000000;
clockError += drift;
lastClockErrorUpdateTime = now;
lastPpm = ppm;
if (Math.abs(clockError) >= settings.adjustThreshold) {
let now = Date.now();
// Shorter variables are faster to look up and this part is time sensitive.
let e = clockError / 1000;
setTime(getTime() - e);
debug(
new Date(now).toISOString() + ' -> ' + ((now / 1000 - e) % 60).toFixed(3) +
' SET TIME (' + clockError.toFixed(2) + ')'
);
clockError = 0;
}
WIDGETS.adjust.draw();
}
// ======================================================================
// MAIN
loadSettings();
WIDGETS.adjust = {
area: 'tr',
draw: draw,
now: () => {
let now = Date.now();
// WIP
let ppm = (lastPpm !== null) ? lastPpm : settings.ppm;
let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000;
return now - updatedClockError;
},
width: WIDTH,
};
if (settings.saveState) {
saved = require('Storage').readJSON(STATE_FILE, true);
}
let now = Date.now();
lastClockErrorUpdateTime = now;
if (saved === undefined) {
clockError = 0;
debug(new Date().toISOString() + ' START');
} else {
clockError = saved.clockError + (now - saved.time) * saved.ppm / 1000000;
if (Math.abs(clockError) <= MAX_CLOCK_ERROR_FROM_SAVED_STATE) {
debug(
new Date().toISOString() + ' START & LOAD STATE (' +
clockError.toFixed(2) + ')'
);
} else {
debug(
new Date().toISOString() + ' START & IGNORE STATE (' +
clockError.toFixed(2) + ')'
);
clockError = 0;
}
}
clockCheck();
E.on('kill', onQuit);
})()