Andreas Rozek 2022-01-14 17:22:52 +01:00
commit c4bac89bfc
67 changed files with 1194 additions and 669 deletions

117
apps.json
View File

@ -120,7 +120,7 @@
"version": "0.06",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications",
"tags": "tool,system,messages,notifications,gadgetbridge",
"dependencies": {"messages":"app"},
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
@ -327,7 +327,7 @@
"description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android' Bangle.js app instead.",
"icon": "app.png",
"type": "widget",
"tags": "tool,system,android,widget",
"tags": "tool,system,android,widget,gadgetbridge",
"supports": ["BANGLEJS","BANGLEJS2"],
"dependencies": {"notify":"type"},
"readme": "README.md",
@ -344,7 +344,7 @@
"version":"0.01",
"description": "Debug info for Gadgetbridge. Run this app and when Gadgetbridge messages arrive they are displayed on-screen.",
"icon": "app.png",
"tags": "",
"tags": "tool,debug,gadgetbridge",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
@ -788,7 +788,7 @@
"id": "recorder",
"name": "Recorder (BETA)",
"shortName": "Recorder",
"version": "0.06",
"version": "0.07",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget",
@ -942,12 +942,13 @@
{
"id": "widlock",
"name": "Lock Widget",
"version": "0.03",
"version": "0.04",
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
"icon": "widget.png",
"type": "widget",
"tags": "widget,lock",
"supports": ["BANGLEJS","BANGLEJS2"],
"sortorder": -1,
"storage": [
{"name":"widlock.wid.js","url":"widget.js"}
]
@ -1060,7 +1061,7 @@
"id": "bthrm",
"name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM",
"version": "0.02",
"version": "0.03",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png",
"type": "app",
@ -1554,7 +1555,7 @@
{
"id": "assistedgps",
"name": "Assisted GPS Update (AGPS)",
"version": "0.02",
"version": "0.03",
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
"icon": "app.png",
"type": "RAM",
@ -3026,6 +3027,20 @@
],
"data": [{"wildcard":"accellog.?.csv"}]
},
{ "id": "accelgraph",
"name": "Accelerometer Graph",
"shortName":"Accel Graph",
"version":"0.01",
"description": "A simple app to draw a graph of data from the accelerometer on the screen",
"icon": "app.png",
"tags": "tool,debug",
"supports" : ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"storage": [
{"name":"accelgraph.app.js","url":"app.js"},
{"name":"accelgraph.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "cprassist",
"name": "CPR Assist",
@ -3955,8 +3970,8 @@
"id": "qmsched",
"name": "Quiet Mode Schedule and Widget",
"shortName": "Quiet Mode",
"version": "0.06",
"description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.",
"version": "0.07",
"description": "Automatically turn Quiet Mode on or off at set times, change theme and LCD options while Quiet Mode is active.",
"icon": "app.png",
"screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"},
{"url":"screenshot_b2_main.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_lcd.png"}],
@ -4297,7 +4312,7 @@
{
"id": "antonclk",
"name": "Anton Clock",
"version": "0.05",
"version": "0.06",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",
@ -4390,8 +4405,10 @@
"allow_emulator": true,
"storage": [
{"name":"ffcniftya.app.js","url":"app.js"},
{"name":"ffcniftya.img","url":"app-icon.js","evaluate":true}
]
{"name":"ffcniftya.img","url":"app-icon.js","evaluate":true},
{"name":"ffcniftya.settings.js","url":"settings.js"}
],
"data": [{"name":"ffcniftya.json"}]
},
{
"id": "ffcniftyb",
@ -4550,7 +4567,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.12",
"version":"0.13",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",
@ -5064,7 +5081,7 @@
{
"id": "coretemp",
"name": "CoreTemp",
"version": "0.02",
"version": "0.03",
"description": "Display CoreTemp device sensor data",
"icon": "coretemp.png",
"type": "app",
@ -5074,6 +5091,7 @@
"storage": [
{"name":"coretemp.wid.js","url":"widget.js"},
{"name":"coretemp.app.js","url":"coretemp.js"},
{"name":"coretemp.recorder.js","url":"recorder.js"},
{"name":"coretemp.settings.js","url":"settings.js"},
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
{"name":"coretemp.boot.js","url":"boot.js"}
@ -5187,14 +5205,13 @@
{
"id": "ftclock",
"name": "Four Twenty Clock",
"version": "0.01",
"version": "0.02",
"description": "A clock that tells when and where it's going to be 4:20 next",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot1.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"ftclock.app.js","url":"app.js"},
@ -5578,7 +5595,7 @@
{ "id": "banglexercise",
"name": "BanglExercise",
"shortName":"BanglExercise",
"version":"0.01",
"version":"0.02",
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
@ -5595,5 +5612,69 @@
"data": [
{"name":"banglexercise.json"}
]
},
{
"id": "widpa",
"name": "Simple Pedometer",
"shortName":"Simple Pedometer",
"icon": "screenshot_widpa.png",
"screenshots": [{"url":"screenshot_widpa.png"}],
"version":"0.02",
"type": "widget",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in 12x16 font, requires firmware v2.11.21 or later",
"tags": "widget,battery",
"storage": [
{"name":"widpa.wid.js","url":"widpa.wid.js"}
]
},
{
"id": "widpb",
"name": "Lato Pedometer",
"shortName":"Lato Pedometer",
"icon": "screenshot_widpb.png",
"screenshots": [{"url":"screenshot_widpb.png"}],
"version":"0.02",
"type": "widget",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in the Lato font, requires firmware v2.11.21 or later",
"tags": "widget,battery",
"storage": [
{"name":"widpb.wid.js","url":"widpb.wid.js"}
]
},
{
"id": "timeandlife",
"name": "Time and Life",
"shortName":"Time and Lfie",
"icon": "app.png",
"version":"0.1",
"description": "A simple watchface which displays the time when the screen is tapped and decays according to the rules of Conway's game of life.",
"type": "clock",
"tags": "clock",
"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}
]
},
{ "id": "acmaze",
"name": "AccelaMaze",
"shortName":"AccelaMaze",
"version":"0.01",
"description": "Tilt the watch to roll a ball through a maze",
"icon": "app.png",
"tags": "game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"screenshots": [{"url":"screenshot.png"}],
"storage": [
{"name":"acmaze.app.js","url":"app.js"},
{"name":"acmaze.img","url":"app-icon.js","evaluate":true}
]
}
]

View File

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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA/4AB304ief85L/ABNVAAwKCgILHoALBgoLHqALOrVVr4BEBZIFBBYiaCAAPq2oLQEYlqF5VrBZWnBZWvBZNWz4LGBoQLHJ4O///6v/1BZHa/4LFLYOlr9pR49r1ILJ09qr4ZBBY2vrWdBY5PBq2uyoLIquqBY5bBKoZTFLYILJJ4STDBY77IJ4QLUJ4QLU1QAE0oLPqoAGBZ0BBY9ABYMABY4KCAH4AGA="))

24
apps/accelgraph/app.js Normal file
View File

@ -0,0 +1,24 @@
Bangle.loadWidgets();
g.clear(1);
Bangle.drawWidgets();
var R = Bangle.appRect;
var x = 0;
var last;
function getY(v) {
return (R.y+R.y2 + v*R.h/2)/2;
}
Bangle.on('accel', a => {
g.reset();
if (last) {
g.setColor("#f00").drawLine(x-1,getY(last.x),x,getY(a.x));
g.setColor("#0f0").drawLine(x-1,getY(last.y),x,getY(a.y));
g.setColor("#00f").drawLine(x-1,getY(last.z),x,getY(a.z));
}
last = a;x++;
if (x>=g.getWidth()) {
x = 1;
g.clearRect(R);
}
});

BIN
apps/accelgraph/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

1
apps/acmaze/ChangeLog Normal file
View File

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

17
apps/acmaze/README.md Normal file
View File

@ -0,0 +1,17 @@
# AccelaMaze
Tilt the watch to roll a ball through a maze.
![Screenshot](screenshot.png)
## Usage
* Use the menu to select difficulty level (or exit).
* Wait until the maze gets generated and a red ball appears.
* Tilt the watch to get the ball into the green cell.
At any time you can click the button to return to the menu.
## Creator
[Nimrod Kerrett](https://zzzen.com)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwggaXh3M53/AA3yl4IHn//+EM5nMAoIX/C4RfCC4szmcxC4QFBAAUxC4UPAwIOB+YCCiMRkAFCkIGBAAQfBC4IUEAQhHIAAQX/C5EDmcyCgUTAoYXDR4kzC4UBPoKVB+YFFAQSPBiAKBiCnDGoZECABDUCa4YX/C5qPBQwoXGkczmC/FQYSSCVQSSCEwQOCC4hKFX4QXCd5YX/C4qMEmQXITAinDPoIADTwSPFkKMBX47RGI47XIC/4XCgZ9DQYYABmKYBmIXFkczmEBRIK/CQYQIBkECSoiSCA4MQa5pEFd6IX/RgMyC6H/QASVCRIS/EAQrXFJQoX/C6kDRQIXCiYFD+QFBmIUCkYFD+CJBiSPCRwIFFSoQFCiF3u9wI4gAO+wXW+IXygAAW"))

276
apps/acmaze/app.js Normal file
View File

@ -0,0 +1,276 @@
const MARGIN = 25;
const WALL_RIGHT = 1, WALL_DOWN = 2;
const STATUS_GENERATING = 0, STATUS_PLAYING = 1,
STATUS_SOLVED = 2, STATUS_ABORTED = -1;
function Maze(n) {
this.n = n;
this.status = STATUS_GENERATING;
this.wall_length = Math.floor((g.getHeight()-2*MARGIN)/n);
this.total_length = this.wall_length*n;
this.margin = Math.floor((g.getHeight()-this.total_length)/2);
this.ball_x = 0;
this.ball_y = 0;
this.clearScreen = function() {
g.clearRect(
0, this.margin,
g.getWidth(), this.margin+this.total_length
);
};
this.clearScreen();
g.setColor(g.theme.fg);
for (let i=0; i<=n; i++) {
g.drawRect(
this.margin, this.margin+i*this.wall_length,
g.getWidth()-this.margin, this.margin+i*this.wall_length
);
g.drawRect(
this.margin+i*this.wall_length, this.margin,
this.margin+i*this.wall_length, g.getHeight() - this.margin
);
}
this.walls = new Uint8Array(n*n);
this.groups = new Uint8Array(n*n);
for (let cell = 0; cell<n*n; cell++) {
this.walls[cell] = WALL_RIGHT|WALL_DOWN;
this.groups[cell] = cell;
}
let from_group, to_group;
let ngroups = n*n;
while (--ngroups) {
// Abort if BTN1 pressed [grace period for menu]
// (for some reason setWatch() fails inside constructor)
if (ngroups<n*n-4 && digitalRead(BTN1)) {
aborting = true;
return;
}
from_group = to_group = -1;
while (from_group<0) {
if (Math.random()<0.5) { // try to break a wall right
let r = Math.floor(Math.random()*n);
let c = Math.floor(Math.random()*(n-1));
let cell = r*n+c;
if (this.groups[cell]!=this.groups[cell+1]) {
this.walls[cell] &= ~WALL_RIGHT;
g.clearRect(
this.margin+(c+1)*this.wall_length,
this.margin+r*this.wall_length+1,
this.margin+(c+1)*this.wall_length,
this.margin+(r+1)*this.wall_length-1
);
g.flip(); // show progress.
from_group = this.groups[cell];
to_group = this.groups[cell+1];
}
} else { // try to break a wall down
let r = Math.floor(Math.random()*(n-1));
let c = Math.floor(Math.random()*n);
let cell = r*n+c;
if (this.groups[cell]!=this.groups[cell+n]) {
this.walls[cell] &= ~WALL_DOWN;
g.clearRect(
this.margin+c*this.wall_length+1,
this.margin+(r+1)*this.wall_length,
this.margin+(c+1)*this.wall_length-1,
this.margin+(r+1)*this.wall_length
);
from_group = this.groups[cell];
to_group = this.groups[cell+n];
}
}
}
for (let cell = 0; cell<n*n; cell++) {
if (this.groups[cell]==from_group) {
this.groups[cell] = to_group;
}
}
}
this.clearScreen = function() {
g.clearRect(
0, MARGIN, g.getWidth(), g.getHeight()-MARGIN-1
);
};
this.clearCell = function(r, c) {
if (!r && !c) {
g.setColor("#ffff00");
} else if (r==this.n-1 && c==this.n-1) {
g.setColor("#00ff00");
} else {
g.setColor(g.theme.bg);
}
g.fillRect(
this.margin+this.wall_length*c+1,
this.margin+this.wall_length*r+1,
this.margin+this.wall_length*(c+1),
this.margin+this.wall_length*(r+1)
);
g.setColor(g.theme.fg);
if (this.walls[r*n+c]&WALL_RIGHT) {
g.fillRect(
this.margin+this.wall_length*(c+1),
this.margin+this.wall_length*r,
this.margin+this.wall_length*(c+1),
this.margin+this.wall_length*(r+1)
);
}
if (this.walls[r*n+c]&WALL_DOWN) {
g.fillRect(
this.margin+this.wall_length*c,
this.margin+this.wall_length*(r+1),
this.margin+this.wall_length*(c+1),
this.margin+this.wall_length*(r+1)
);
}
};
this.drawBall = function(x, y) {
g.setColor("#ff0000");
g.fillEllipse(
this.margin+x+1,
this.margin+y+1,
this.margin+x+this.wall_length-1,
this.margin+y+this.wall_length-1
);
g.setColor(g.theme.fg);
};
this.move = function(dx, dy) {
let next_x = this.ball_x,
next_y = this.ball_y,
ball_r = Math.floor(this.ball_y/this.wall_length),
ball_c = Math.floor(this.ball_x/this.wall_length);
if (this.ball_x%this.wall_length) {
if (dx) {
next_x += dx;
} else {
return false;
}
} else if (this.ball_y%this.wall_length) {
if (dy) {
next_y += dy;
} else {
return false;
}
} else { // exactly in a cell. Check walls
if (dy<0 && ball_r>0 && !(this.walls[n*(ball_r-1)+ball_c]&WALL_DOWN)) {
next_y--;
} else if (dy>0 && ball_r<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_DOWN)) {
next_y++;
} else if (dx<0 && ball_c>0 && !(this.walls[n*ball_r+ball_c-1]&WALL_RIGHT)) {
next_x--;
} else if (dx>0 && ball_c<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_RIGHT)) {
next_x++;
} else {
return false;
}
}
this.clearCell(ball_r, ball_c);
if (this.ball_x%this.wall_length) {
this.clearCell(ball_r, ball_c+1);
}
if (this.ball_y%this.wall_length) {
this.clearCell(ball_r+1, ball_c);
}
this.ball_x = next_x;
this.ball_y = next_y;
this.drawBall(this.ball_x, this.ball_y);
if (this.ball_x==(n-1)*this.wall_length && this.ball_y==(n-1)*this.wall_length) {
this.status = STATUS_SOLVED;
}
return true;
};
this.try_move_horizontally = function(accel_x) {
if (accel_x>0.15) {
return this.move(-1, 0);
} else if (accel_x<-0.15) {
return this.move(1, 0);
}
return false;
};
this.try_move_vertically = function(accel_y) {
if (accel_y<-0.15) {
return this.move(0,1);
} else if (accel_y>0.15) {
return this.move(0,-1);
}
return false;
};
this.tick = function() {
accel = Bangle.getAccel();
if (this.ball_x%this.wall_length) {
this.try_move_horizontally(accel.x);
} else if (this.ball_y%this.wall_length) {
this.try_move_vertically(accel.y);
} else {
if (Math.abs(accel.x)>Math.abs(accel.y)) { // prefer horizontally
if (!this.try_move_horizontally(accel.x)) {
this.try_move_vertically(accel.y);
}
} else { // prefer vertically
if (!this.try_move_vertically(accel.y)) {
this.try_move_horizontally(accel.x);
}
}
}
};
this.clearCell(0,0);
this.clearCell(n-1,n-1);
this.drawBall(0,0);
this.status = STATUS_PLAYING;
}
function timeToText(t) { // Courtesy of stopwatch app
let hrs = Math.floor(t/3600000);
let mins = Math.floor(t/60000)%60;
let secs = Math.floor(t/1000)%60;
let tnth = Math.floor(t/100)%10;
let text;
if (hrs === 0)
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
else
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
return text;
}
let aborting = false;
let start_time = 0;
let duration = 0;
let maze=null;
let mazeMenu = {
"": { "title": "Maze size", "selected": 1 },
"Easy (8x8)": function() { E.showMenu(); maze = new Maze(8); },
"Medium (10x10)": function() { E.showMenu(); maze = new Maze(10); },
"Hard (14x14)": function() { E.showMenu(); maze = new Maze(14); },
"< Exit": function() { setTimeout(load, 100); } // timeout voodoo prevents deadlock
};
g.clear(true);
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setLocked(false);
Bangle.setLCDTimeout(0);
E.showMenu(mazeMenu);
let maze_interval = setInterval(
function() {
if (maze) {
if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) {
console.log(`aborting ${start_time}`);
maze = null;
start_time = duration = 0;
aborting = false;
setTimeout(function() {E.showMenu(mazeMenu); }, 100);
return;
}
if (!start_time) {
start_time = Date.now();
}
if (maze.status==STATUS_PLAYING) {
maze.tick();
}
if (maze.status==STATUS_SOLVED && !duration) {
duration = Date.now()-start_time;
g.setFontAlign(0,0).setColor(g.theme.fg);
g.setFont("Vector",18);
g.drawString(`Solved in\n ${timeToText(duration)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true);
}
}
}, 25);

BIN
apps/acmaze/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
apps/acmaze/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -4,4 +4,7 @@
0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too.
0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off)
when weekday name "Off": week #:<num>
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
0.06: fixes #1271 - wrong settings name
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
week is buffered until date or timezone changes

View File

@ -40,9 +40,9 @@ The main menu contains several settings covering Anton clock in general.
* **Show Weekday** - Weekday is shown in the time presentation without seconds.
Weekday name depends on the current locale.
If seconds are shown, the weekday is never shown as there is not enough space on the watch face.
* **Show Weeknumber** - Week-number (ISO-8601) is shown. (default: Off)
If "Show Weekday" is "Off" the week-number is displayed as "week #:<num>".
If "Show Weekday" is "On" the weekday name is cut at 6th position and suffixed with ".#<week num>".
* **Show CalWeek** - Week-number (ISO-8601) is shown. (default: Off)
If "Show Weekday" is "Off" displays the week-number as "week #<num>".
If "Show Weekday" is "On" displays "weekday name short" with " #<num>" .
If seconds are shown, the week number is never shown as there is not enough space on the watch face.
* **Vector font** - Use the built-in vector font for dates and weekday.
This can improve readability.

View File

@ -1,6 +1,6 @@
// Clock with large digits using the "Anton" bold font
var SETTINGSFILE = "antonclk.json";
const SETTINGSFILE = "antonclk.json";
Graphics.prototype.setFontAnton = function(scale) {
// Actual height 69 (68 - 0)
@ -28,7 +28,7 @@ var drawTimeout;
var queueMillis = 1000;
var secondsScreen = true;
var isBangle1 = (g.getWidth() == 240);
var isBangle1 = (process.env.HWVERSION == 1);
//For development purposes
/*
@ -50,13 +50,11 @@ require('Storage').writeJSON(SETTINGSFILE, {
require('Storage').erase(SETTINGSFILE);
*/
// Helper method for loading the settings
function def(value, def) {
return (value !== undefined ? value : def);
}
// Load settings
function loadSettings() {
// Helper function default setting
function def (value, def) {return value !== undefined ? value : def;}
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
secondsMode = def(settings.secondsMode, "Never");
secondsColoured = def(settings.secondsColoured, true);
@ -104,7 +102,12 @@ function isoStr(date) {
return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).substr(-2) + "-" + ("0" + date.getDate()).substr(-2);
}
var calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested)
function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
dateNoTime = date; dateNoTime.setHours(0,0,0,0);
if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2];
calWeekBuffer[0] = date.getTimezoneOffset();
calWeekBuffer[1] = dateNoTime;
var tdt = new Date(date.valueOf());
var dayn = (date.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
@ -113,7 +116,8 @@ function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu
if (tdt.getDay() !== 4) {
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000);
return calWeekBuffer[2];
}
function doColor() {
@ -186,13 +190,17 @@ function draw() {
else
g.setFont("6x8", 2);
g.drawString(dateStr, x, y);
if (weekDay || calWeek) {
var dowwumStr = require("locale").dow(date);
if (calWeek || weekDay) {
var dowcwStr = "";
if (calWeek)
dowwumStr = (weekDay ? dowwumStr.substr(0,Math.min(dowwumStr.length,6)) + (dowwumStr.length>=6 ? "." : "") : "week ") + "#" + ISO8601calWeek(date); //TODO: locale for "week"
dowcwStr = " #" + ("0" + ISO8601calWeek(date)).substring(-2);
if (weekDay)
dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort #<calWeek> e.g. Mon #01
else //week #01
dowcwStr = /*LANG*/"week" + dowcwStr;
if (upperCase)
dowwumStr = dowwumStr.toUpperCase();
g.drawString(dowwumStr, x, y + (vectorFont ? 26 : 16));
dowcwStr = dowcwStr.toUpperCase();
g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16));
}
}

View File

@ -47,11 +47,11 @@
writeSettings();
}
},
"Show Weeknumber": {
value: (settings.weekNum !== undefined ? settings.weekNum : true),
"Show CalWeek": {
value: (settings.calWeek !== undefined ? settings.calWeek : false),
format: v => v ? "On" : "Off",
onchange: v => {
settings.weekNum = v;
settings.calWeek = v;
writeSettings();
}
},

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Update to work with Bangle.js 2
0.03: Select GNSS systems to use for Bangle.js 2

View File

@ -27,6 +27,31 @@
</div>
</div>
<div id="banglejs2-info" style="display:none">
<p>Using fewer GNSS systems may decrease the time to fix. (If unsure, select only GPS)</p>
<div class="form-group">
<label class="form-label">Select which GNSS system you want.</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="3"><i class="form-icon"></i> GPS+BDS
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="4"><i class="form-icon"></i> GLONASS
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="5"><i class="form-icon"></i> GPS+GLONASS
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> BDS+GLONASS
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> GPS+BDS+GLONASS
</label>
</div>
</div>
<p id="upload-wrap" style="display:none">Click <button id="upload" class="btn btn-primary">Upload</button></p>
@ -116,8 +141,13 @@
}
if (isB2) { // CASIC
// Disable BDS, use just GPS (supposedly improve lock time)
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,1")}")\n`; // set GPS-only mode
// Select what GNSS System to use for decreased fix time.
var radios = document.getElementsByName('gnss_select');
var gnss_select="1";
for (var i=0; i<radios.length; i++)
if (radios[i].checked)
gnss_select=radios[i].value;
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
// What about:
// NAV-TIMEUTC (0x01 0x10)
// NAV-PV (0x01 0x03)

View File

@ -1 +1,4 @@
0.01: New App!
0.02: Add sit ups
Add more feedback to the user about the exercises
Clean up code

View File

@ -2,7 +2,7 @@
Can automatically track exercises while wearing the Bangle.js watch.
Currently only push ups and curls are supported.
Currently only push ups, curls and sit ups are supported.
## Disclaimer
@ -23,7 +23,7 @@ Press stop to end your exercise.
## TODO
* Add other exercise types:
* Rope jumps
* Sit ups
* Star jumps
* ...
* Save exercise summaries to file system
* Configure daily goal for exercises

View File

@ -25,22 +25,32 @@ let exerciseType = {
const exerciseTypes = [{
"id": "pushup",
"name": "push ups",
"useYaxe": true,
"useZaxe": false,
"thresholdY": 2500,
"thresholdMinTime": 1400, // mininmal time between two push ups in ms
"useYaxis": true,
"useZaxis": false,
"threshold": 2500,
"thresholdMinTime": 800, // mininmal time between two push ups in ms
"thresholdMaxTime": 5000, // maximal time between two push ups in ms
"thresholdMinDurationTime": 700, // mininmal duration of half a push ups in ms
"thresholdMinDurationTime": 600, // mininmal duration of half a push up in ms
},
{
"id": "curl",
"name": "curls",
"useYaxe": true,
"useZaxe": false,
"thresholdY": 2500,
"thresholdMinTime": 1000, // mininmal time between two curls in ms
"useYaxis": true,
"useZaxis": false,
"threshold": 2500,
"thresholdMinTime": 800, // mininmal time between two curls in ms
"thresholdMaxTime": 5000, // maximal time between two curls in ms
"thresholdMinDurationTime": 500, // mininmal duration of half a push ups in ms
"thresholdMinDurationTime": 500, // mininmal duration of half a curl in ms
},
{
"id": "situp",
"name": "sit ups",
"useYaxis": false,
"useZaxis": true,
"threshold": 3500,
"thresholdMinTime": 800, // mininmal time between two sit ups in ms
"thresholdMaxTime": 5000, // maximal time between two sit ups in ms
"thresholdMinDurationTime": 500, // mininmal duration of half a sit up in ms
}
];
let exerciseCounter = 0;
@ -66,7 +76,7 @@ function showMainMenu() {
};
exerciseTypes.forEach(function(et) {
menu["Do " + et.name] = function() {
menu[et.name] = function() {
exerciseType = et;
E.showMenu();
startTraining();
@ -81,8 +91,8 @@ function showMainMenu() {
value: exerciseCounter + " " + exerciseType.name
};
}
menu.Exit = function() {
load();
menu.exit = function() {
load();
};
E.showMenu(menu);
@ -91,11 +101,11 @@ function showMainMenu() {
function accelHandler(accel) {
if (!exerciseType) return;
const t = Math.round(new Date().getTime()); // time in ms
const y = exerciseType.useYaxe ? accel.y * 8192 : 0;
const z = exerciseType.useZaxe ? accel.z * 8192 : 0;
const y = exerciseType.useYaxis ? accel.y * 8192 : 0;
const z = exerciseType.useZaxis ? accel.z * 8192 : 0;
//console.log(t, y, z);
if (exerciseType.useYaxe) {
if (exerciseType.useYaxis) {
while (historyY.length > avgSize)
historyY.shift();
@ -109,7 +119,7 @@ function accelHandler(accel) {
}
}
if (exerciseType.useYaxe) {
if (exerciseType.useZaxis) {
while (historyZ.length > avgSize)
historyZ.shift();
@ -124,72 +134,64 @@ function accelHandler(accel) {
}
// slope for Y
if (exerciseType.useYaxe) {
if (exerciseType.useYaxis) {
let l = historyAvgY.length;
if (l > 1) {
const p1 = historyAvgY[l - 2];
const p2 = historyAvgY[l - 1];
const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
// we use this data for exercises which can be detected by using Y axis data
switch (exerciseType.id) {
case "pushup":
isValidYAxisExercise(slopeY, t);
break;
case "curl":
isValidYAxisExercise(slopeY, t);
break;
}
isValidExercise(slopeY, t);
}
}
// slope for Z
if (exerciseType.useZaxe) {
if (exerciseType.useZaxis) {
l = historyAvgZ.length;
if (l > 1) {
const p1 = historyAvgZ[l - 2];
const p2 = historyAvgZ[l - 1];
const slopeZ = (p2[1] - p1[1]) / (p2[0] - p1[0]);
historyAvgZ.shift();
historySlopeZ.push([p2[0] - p1[0], slopeZ]);
// TODO: we can use this data for some exercises which can be detected by using Z axis data
const slopeZ = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
// we use this data for some exercises which can be detected by using Z axis data
isValidExercise(slopeZ, t);
}
}
}
/*
* Check if slope value of Y-axis data looks like an exercise
* Check if slope value of Y-axis or Z-axis data (depending on exercise type) looks like an exercise
*
* In detail we look for slop values which are bigger than the configured Y threshold for the current exercise
* In detail we look for slop values which are bigger than the configured threshold for the current exercise type
* Then we look for two consecutive slope values of which one is above 0 and the other is below zero.
* If we find one pair of these values this could be part of one exercise.
* Then we look for a pair of values which cross the zero from the otherwise direction
*/
function isValidYAxisExercise(slopeY, t) {
function isValidExercise(slope, t) {
if (!exerciseType) return;
const thresholdY = exerciseType.thresholdY;
const threshold = exerciseType.threshold;
const historySlopeValues = exerciseType.useYaxis ? historySlopeY : historySlopeZ;
const thresholdMinTime = exerciseType.thresholdMinTime;
const thresholdMaxTime = exerciseType.thresholdMaxTime;
const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime;
const exerciseName = exerciseType.name;
if (Math.abs(slopeY) >= thresholdY) {
historyAvgY.shift();
historySlopeY.push([t, slopeY]);
//console.log(t, Math.abs(slopeY));
const lSlopeY = historySlopeY.length;
if (lSlopeY > 1) {
const p1 = historySlopeY[lSlopeY - 1][1];
const p2 = historySlopeY[lSlopeY - 2][1];
if (Math.abs(slope) >= threshold) {
historySlopeValues.push([t, slope]);
//console.log(t, Math.abs(slope));
const lSlopeHistory = historySlopeValues.length;
if (lSlopeHistory > 1) {
const p1 = historySlopeValues[lSlopeHistory - 1][1];
const p2 = historySlopeValues[lSlopeHistory - 2][1];
if (p1 > 0 && p2 < 0) {
if (lastZeroPassCameFromPositive == false) {
lastExerciseHalfCompletionTime = t;
//console.log(t, exerciseName + " half complete...");
console.log(t, exerciseName + " half complete...");
layout.progress.label = "½";
layout.recording.label = "TRAINING";
g.clear();
layout.render();
}
@ -201,7 +203,7 @@ function isValidYAxisExercise(slopeY, t) {
if (lastZeroPassCameFromPositive == true) {
const tDiffLastExercise = t - lastExerciseCompletionTime;
const tDiffStart = t - tStart;
//console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
// check minimal time between exercises:
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
@ -219,22 +221,36 @@ function isValidYAxisExercise(slopeY, t) {
layout.count.label = exerciseCounter;
layout.progress.label = "";
layout.recording.label = "Good!";
g.clear();
layout.render();
if (settings.buzz)
Bangle.buzz(100, 0.4);
Bangle.buzz(200, 0.5);
} else {
//console.log(t, exerciseName + " to quick for duration time threshold!");
console.log(t, exerciseName + " too quick for duration time threshold!"); // thresholdMinDurationTime
lastExerciseCompletionTime = t;
layout.recording.label = "Go slower!";
g.clear();
layout.render();
}
} else {
//console.log(t, exerciseName + " to slow for time threshold!");
console.log(t, exerciseName + " top slow for time threshold!"); // thresholdMaxTime
lastExerciseCompletionTime = t;
layout.recording.label = "Go faster!";
g.clear();
layout.render();
}
} else {
//console.log(t, exerciseName + " to quick for time threshold!");
console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime
lastExerciseCompletionTime = t;
layout.recording.label = "Go slower!";
g.clear();
layout.render();
}
}
@ -267,6 +283,7 @@ function startTraining() {
if (recordActive) return;
g.clear(1);
reset();
Bangle.setLCDTimeout(0); // force LCD on
Bangle.setHRMPower(1, "banglexercise");
if (!hrtValue) hrtValue = "...";
@ -285,7 +302,7 @@ function startTraining() {
type: "txt",
id: "count",
font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
label: 10,
label: exerciseCounter,
pad: 5
},
{
@ -337,11 +354,16 @@ function startTraining() {
layout.render();
Bangle.setPollInterval(80); // 12.5 Hz
Bangle.on('accel', accelHandler);
tStart = new Date().getTime();
recordActive = true;
if (settings.buzz)
Bangle.buzz(200, 1);
// delay start a little bit
setTimeout(() => {
Bangle.on('accel', accelHandler);
}, 1000);
}
function stopTraining() {

View File

@ -2,3 +2,6 @@
0.02: Make overriding the HRM event optional
Emit BTHRM event for external sensor
Add recorder app plugin
0.03: Prevent readings from internal sensor mixing into BT values
Mark events with src property
Show actual source of event in app

View File

@ -12,7 +12,6 @@
Bangle.isHRMOn = function() {
var settings = require('Storage').readJSON("bthrm.json", true) || {};
print(settings);
if (settings.enabled && !settings.replace){
return origIsHRMOn();
} else if (settings.enabled && settings.replace){
@ -69,13 +68,11 @@
var interval = dv.getUint16(idx,1); // in milliseconds
}*/
var eventName = settings.replace ? "HRM" : "BTHRM";
Bangle.emit(eventName, {
Bangle.emit(settings.replace?"HRM":"BTHRM", {
bpm:bpm,
confidence:100
});
confidence:100,
src:settings.replace?"bthrm":undefined
});
});
return characteristic.startNotifications();
}).then(function() {
@ -107,8 +104,20 @@
if (settings.enabled || !isOn){
Bangle.setBTHRMPower(isOn, app);
}
if (settings.enabled && !settings.replace || !isOn){
if ((settings.enabled && !settings.replace) || !settings.enabled || !isOn){
origSetHRMPower(isOn, app);
}
}
var settings = require('Storage').readJSON("bthrm.json", true) || {};
if (settings.enabled && settings.replace){
if (!(Bangle._PWR===undefined) && !(Bangle._PWR.HRM===undefined)){
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
var app = Bangle._PWR.HRM[i];
origSetHRMPower(0, app);
Bangle.setBTHRMPower(1, app);
if (Bangle._PWR.HRM===undefined) break;
}
}
}
})();

View File

@ -9,13 +9,14 @@ function draw(y, event, type, counter) {
var px = g.getWidth()/2;
g.reset();
g.setFontAlign(0,0);
g.clearRect(0,y,g.getWidth(),y+80);
g.clearRect(0,y,g.getWidth(),y+75);
if (type == null || event == null || counter == 0) return;
var str = event.bpm + "";
g.setFontVector(40).drawString(str,px,y+20);
str = "Confidence: " + event.confidence;
g.setFontVector(12).drawString(str,px,y+50);
str = "Event: " + type;
if (type == "HRM") str += " Source: " + (event.src ? event.src : "internal");
g.setFontVector(12).drawString(str,px,y+60);
}
@ -35,7 +36,6 @@ Bangle.on('BTHRM', onBtHrm);
Bangle.on('HRM', onHrm);
Bangle.setHRMPower(1,'bthrm')
Bangle.setBTHRMPower(1,'bthrm')
g.clear();
Bangle.loadWidgets();

View File

@ -1,15 +1,15 @@
(function(recorders) {
recorders.bthrm = function() {
var bpm = 0;
var bpm = "";
function onHRM(h) {
bpm = h.bpm;
bpm = h.bpm;
}
return {
name : "BTHR",
fields : ["BT Heartrate"],
getValues : () => {
result = [bpm];
bpm = 0;
bpm = "";
return result;
},
start : () => {

View File

@ -136,8 +136,10 @@ function getCirclePosition(type) {
if (setting == type) return circlePosX[i - 1];
}
for (let i = 0; i < defaultCircleTypes.length; i++) {
if (type == defaultCircleTypes[i]) return circlePosX[i];
}
if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) {
return circlePosX[i];
}
}
return undefined;
}

View File

@ -1,2 +1,3 @@
0.01: New app
0.02: Cleanup interface and add settings, widget, add skin temp reporting.
0.03: Move code for recording to this app

31
apps/coretemp/recorder.js Normal file
View File

@ -0,0 +1,31 @@
(function(recorders) {
recorders.coretemp = function() {
var core = "", skin = "";
var hasCore = false;
function onCore(c) {
core=c.core;
skin=c.skin;
hasCore = true;
}
return {
name : "Core",
fields : ["Core","Skin"],
getValues : () => {
var r = [core,skin];
core = "";
skin = "";
return r;
},
start : () => {
hasCore = false;
Bangle.on('CoreTemp', onCore);
},
stop : () => {
hasCore = false;
Bangle.removeListener('CoreTemp', onCore);
},
draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
};
}
})

View File

@ -1 +1,2 @@
0.01: New Clock Nifty A
0.02: Shows the current week number (ISO8601), can be disabled via settings ""

View File

@ -1,4 +1,14 @@
# Nifty-A Clock
Colors are black/white - photos have non correct camera color "blue"
## This is the clock
![](screenshot_nifty.png)
## The week number (ISO8601) can be turned of in settings
(default is **"On"**)
![](screenshot_settings_nifty.png)

View File

@ -1,5 +1,6 @@
const locale = require("locale");
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true};
/* Clock *********************************************/
const scale = g.getWidth() / 176;
@ -16,6 +17,18 @@ const center = {
y: Math.round(((viewport.height - widget) / 2) + widget),
}
function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
var tdt = new Date(date.valueOf());
var dayn = (date.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
var firstThursday = tdt.valueOf();
tdt.setMonth(0, 1);
if (tdt.getDay() !== 4) {
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
}
function d02(value) {
return ('0' + value).substr(-2);
}
@ -29,23 +42,26 @@ function draw() {
const minutes = d02(now.getMinutes());
const day = d02(now.getDate());
const month = d02(now.getMonth() + 1);
const year = now.getFullYear();
const month2 = locale.month(now, 3);
const day2 = locale.dow(now, 3);
const year = now.getFullYear(now);
const weekNum = d02(ISO8601_week_no(now));
const monthName = locale.month(now, 3);
const dayName = locale.dow(now, 3);
const centerTimeScaleX = center.x + 32 * scale;
g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale);
g.drawString(hour, centerTimeScaleX, center.y - 31 * scale);
g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale);
g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale);
const centerDatesScaleX = center.x + 40 * scale;
g.setFontAlign(-1, 0).setFont("Vector", 16 * scale);
g.drawString(year, center.x + 40 * scale, center.y - 62 * scale);
g.drawString(month, center.x + 40 * scale, center.y - 44 * scale);
g.drawString(day, center.x + 40 * scale, center.y - 26 * scale);
g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale);
g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale);
g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale);
g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,23 @@
(function(back) {
var FILE = "ffcniftya.json";
// Load settings
var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
function writeSettings() {
require('Storage').writeJSON(FILE, cfg);
}
// Show the menu
E.showMenu({
"" : { "title" : "Nifty-A Clock" },
"< Back" : () => back(),
'week number?': {
value: cfg.showWeekNum,
format: v => v?"On":"Off",
onchange: v => {
cfg.showWeekNum = v;
writeSettings();
}
}
});
})

View File

@ -1 +1,2 @@
0.01: first release
0.02: RAM efficient version of `fourTwentyTz.js` (as suggested by @gfwilliams).

View File

@ -17,14 +17,13 @@ function queueDraw() {
function draw() {
g.reset();
g.setBgColor("#ffffff");
let date = new Date();
let timeStr = require("locale").time(date,1);
let next420 = getNextFourTwenty();
g.clearRect(0,26,g.getWidth(),g.getHeight());
g.setColor("#00ff00").setFontAlign(0,-1).setFont("Teletext10x18Ascii",2);
g.drawString(next420.minutes? timeStr: `\0${leaf_img}${timeStr}\0${leaf_img}`, g.getWidth()/2, 28);
g.setColor("#000000");
g.setColor(g.theme.fg);
g.setFontAlign(-1,-1).setFont("Teletext10x18Ascii");
g.drawString(g.wrapString(next420.text, g.getWidth()-8).join("\n"),4,60);

View File

@ -1,4 +1,6 @@
let timezones = require("fourTwentyTz").timezones;
let ftz = require("fourTwentyTz"),
offsets = ftz.offsets,
timezones = ftz.timezones;
function get420offset() {
let current_time = Math.floor((Date.now()%(24*3600*1000))/60000);
@ -24,10 +26,10 @@ function makeFourTwentyText(minutes, places) {
function getNextFourTwenty() {
let offs = get420offset();
for (let i=0; i<timezones.length; i++) {
if (timezones[i][0]<=offs) {
let minutes = offs-timezones[i][0];
let places = timezones[i][1];
for (let i=0; i<offsets.length; i++) {
if (offsets[i]<=offs) {
let minutes = offs-offsets[i];
let places = timezones(offsets[i]);
return {
minutes: minutes,
places: places,

View File

@ -1,463 +1,33 @@
// Generated by mkFourTwentyTz.js
// Wed Jan 12 2022 19:35:36 GMT+0200 (Israel Standard Time)
// Data source: https://timezonedb.com/files/timezonedb.csv.zip
// Sun Jan 09 2022 13:21:47 GMT+0200 (Israel Standard Time)
exports.timezones = {
"0": [
"Troll, Antarctica",
"Ouagadougou, Burkina Faso",
"Abidjan, Côte d'Ivoire",
"Canary, Spain",
"Faroe, Faroe Islands",
"London, United Kingdom of Great Britain and Northern Ireland",
"Guernsey, Guernsey",
"Accra, Ghana",
"Danmarkshavn, Greenland",
"Banjul, Gambia",
"Conakry, Guinea",
"Bissau, Guinea-Bissau",
"Dublin, Ireland",
"Isle of_Man, Isle of Man",
"Reykjavik, Iceland",
"Jersey, Jersey",
"Monrovia, Liberia",
"Bamako, Mali",
"Nouakchott, Mauritania",
"Lisbon, Portugal",
"Madeira, Portugal",
"St Helena, Saint Helena, Ascension and Tristan da Cunha",
"Freetown, Sierra Leone",
"Dakar, Senegal",
"Sao Tome, Sao Tome and Principe",
"Lome, Togo"
],
"60": [
"Andorra, Andorra",
"Tirane, Albania",
"Luanda, Angola",
"Vienna, Austria",
"Sarajevo, Bosnia and Herzegovina",
"Brussels, Belgium",
"Porto-Novo, Benin",
"Kinshasa, Congo, Democratic Republic of the",
"Bangui, Central African Republic",
"Brazzaville, Congo",
"Zurich, Switzerland",
"Douala, Cameroon",
"Prague, Czechia",
"Berlin, Germany",
"Busingen, Germany",
"Copenhagen, Denmark",
"Algiers, Algeria",
"El Aaiun, Western Sahara",
"Madrid, Spain",
"Ceuta, Spain",
"Paris, France",
"Libreville, Gabon",
"Gibraltar, Gibraltar",
"Malabo, Equatorial Guinea",
"Zagreb, Croatia",
"Budapest, Hungary",
"Rome, Italy",
"Vaduz, Liechtenstein",
"Luxembourg, Luxembourg",
"Casablanca, Morocco",
"Monaco, Monaco",
"Podgorica, Montenegro",
"Skopje, North Macedonia",
"Malta, Malta",
"Niamey, Niger",
"Lagos, Nigeria",
"Amsterdam, Netherlands",
"Oslo, Norway",
"Warsaw, Poland",
"Belgrade, Serbia",
"Stockholm, Sweden",
"Ljubljana, Slovenia",
"Longyearbyen, Svalbard and Jan Mayen",
"Bratislava, Slovakia",
"San Marino, San Marino",
"Ndjamena, Chad",
"Tunis, Tunisia",
"Vatican, Holy See"
],
"120": [
"Mariehamn, Åland Islands",
"Sofia, Bulgaria",
"Bujumbura, Burundi",
"Gaborone, Botswana",
"Lubumbashi, Congo, Democratic Republic of the",
"Nicosia, Cyprus",
"Famagusta, Cyprus",
"Tallinn, Estonia",
"Cairo, Egypt",
"Helsinki, Finland",
"Athens, Greece",
"Jerusalem, Israel",
"Amman, Jordan",
"Beirut, Lebanon",
"Maseru, Lesotho",
"Vilnius, Lithuania",
"Riga, Latvia",
"Tripoli, Libya",
"Chisinau, Moldova, Republic of",
"Blantyre, Malawi",
"Maputo, Mozambique",
"Windhoek, Namibia",
"Gaza, Palestine, State of",
"Hebron, Palestine, State of",
"Bucharest, Romania",
"Kaliningrad, Russian Federation",
"Kigali, Rwanda",
"Khartoum, Sudan",
"Juba, South Sudan",
"Damascus, Syrian Arab Republic",
"Mbabane, Eswatini",
"Kiev, Ukraine",
"Uzhgorod, Ukraine",
"Zaporozhye, Ukraine",
"Johannesburg, South Africa",
"Lusaka, Zambia",
"Harare, Zimbabwe"
],
"180": [
"Syowa, Antarctica",
"Bahrain, Bahrain",
"Minsk, Belarus",
"Djibouti, Djibouti",
"Asmara, Eritrea",
"Addis Ababa, Ethiopia",
"Baghdad, Iraq",
"Nairobi, Kenya",
"Comoro, Comoros",
"Kuwait, Kuwait",
"Antananarivo, Madagascar",
"Qatar, Qatar",
"Moscow, Russian Federation",
"Simferopol, Ukraine",
"Kirov, Russian Federation",
"Volgograd, Russian Federation",
"Riyadh, Saudi Arabia",
"Mogadishu, Somalia",
"Istanbul, Turkey",
"Dar es_Salaam, Tanzania, United Republic of",
"Kampala, Uganda",
"Aden, Yemen",
"Mayotte, Mayotte"
],
"240": [
"Dubai, United Arab Emirates",
"Yerevan, Armenia",
"Baku, Azerbaijan",
"Tbilisi, Georgia",
"Mauritius, Mauritius",
"Muscat, Oman",
"Reunion, Réunion",
"Astrakhan, Russian Federation",
"Saratov, Russian Federation",
"Ulyanovsk, Russian Federation",
"Samara, Russian Federation",
"Mahe, Seychelles"
],
"300": [
"Mawson, Antarctica",
"Qyzylorda, Kazakhstan",
"Aqtobe, Kazakhstan",
"Aqtau, Kazakhstan",
"Atyrau, Kazakhstan",
"Oral, Kazakhstan",
"Maldives, Maldives",
"Karachi, Pakistan",
"Yekaterinburg, Russian Federation",
"Kerguelen, French Southern Territories",
"Dushanbe, Tajikistan",
"Ashgabat, Turkmenistan",
"Samarkand, Uzbekistan",
"Tashkent, Uzbekistan"
],
"360": [
"Vostok, Antarctica",
"Dhaka, Bangladesh",
"Thimphu, Bhutan",
"Urumqi, China",
"Chagos, British Indian Ocean Territory",
"Bishkek, Kyrgyzstan",
"Almaty, Kazakhstan",
"Qostanay, Kazakhstan",
"Omsk, Russian Federation"
],
"420": [
"Davis, Antarctica",
"Christmas, Christmas Island",
"Jakarta, Indonesia",
"Pontianak, Indonesia",
"Phnom Penh, Cambodia",
"Vientiane, Lao People's Democratic Republic",
"Hovd, Mongolia",
"Novosibirsk, Russian Federation",
"Barnaul, Russian Federation",
"Tomsk, Russian Federation",
"Novokuznetsk, Russian Federation",
"Krasnoyarsk, Russian Federation",
"Bangkok, Thailand",
"Ho Chi_Minh, Viet Nam"
],
"480": [
"Perth, Australia",
"Brunei, Brunei Darussalam",
"Shanghai, China",
"Hong Kong, Hong Kong",
"Makassar, Indonesia",
"Ulaanbaatar, Mongolia",
"Choibalsan, Mongolia",
"Macau, Macao",
"Kuala Lumpur, Malaysia",
"Kuching, Malaysia",
"Manila, Philippines",
"Irkutsk, Russian Federation",
"Singapore, Singapore",
"Taipei, Taiwan, Province of China"
],
"540": [
"Jayapura, Indonesia",
"Tokyo, Japan",
"Pyongyang, Korea (Democratic People's Republic of)",
"Seoul, Korea, Republic of",
"Palau, Palau",
"Chita, Russian Federation",
"Yakutsk, Russian Federation",
"Khandyga, Russian Federation",
"Dili, Timor-Leste"
],
"600": [
"DumontDUrville, Antarctica",
"Brisbane, Australia",
"Lindeman, Australia",
"Chuuk, Micronesia (Federated States of)",
"Guam, Guam",
"Saipan, Northern Mariana Islands",
"Port Moresby, Papua New Guinea",
"Vladivostok, Russian Federation",
"Ust-Nera, Russian Federation"
],
"660": [
"Casey, Antarctica",
"Lord Howe, Australia",
"Macquarie, Australia",
"Hobart, Australia",
"Melbourne, Australia",
"Sydney, Australia",
"Pohnpei, Micronesia (Federated States of)",
"Kosrae, Micronesia (Federated States of)",
"Noumea, New Caledonia",
"Bougainville, Papua New Guinea",
"Magadan, Russian Federation",
"Sakhalin, Russian Federation",
"Srednekolymsk, Russian Federation",
"Guadalcanal, Solomon Islands",
"Efate, Vanuatu"
],
"720": [
"Tarawa, Kiribati",
"Majuro, Marshall Islands",
"Kwajalein, Marshall Islands",
"Norfolk, Norfolk Island",
"Nauru, Nauru",
"Kamchatka, Russian Federation",
"Anadyr, Russian Federation",
"Funafuti, Tuvalu",
"Wake, United States Minor Outlying Islands",
"Wallis, Wallis and Futuna"
],
"780": [
"McMurdo, Antarctica",
"Pago Pago, American Samoa",
"Fiji, Fiji",
"Kanton, Kiribati",
"Niue, Niue",
"Auckland, New Zealand",
"Fakaofo, Tokelau",
"Tongatapu, Tonga",
"Midway, United States Minor Outlying Islands",
"Apia, Samoa"
],
"840": [
"Rarotonga, Cook Islands",
"Kiritimati, Kiribati",
"Tahiti, French Polynesia",
"Adak, United States of America",
"Honolulu, United States of America"
],
"900": [
"Gambier, French Polynesia",
"Anchorage, United States of America",
"Juneau, United States of America",
"Sitka, United States of America",
"Metlakatla, United States of America",
"Yakutat, United States of America",
"Nome, United States of America"
],
"960": [
"Vancouver, Canada",
"Tijuana, Mexico",
"Pitcairn, Pitcairn",
"Los Angeles, United States of America"
],
"1020": [
"Edmonton, Canada",
"Cambridge Bay, Canada",
"Yellowknife, Canada",
"Inuvik, Canada",
"Creston, Canada",
"Dawson Creek, Canada",
"Fort Nelson, Canada",
"Whitehorse, Canada",
"Dawson, Canada",
"Mazatlan, Mexico",
"Chihuahua, Mexico",
"Ojinaga, Mexico",
"Hermosillo, Mexico",
"Denver, United States of America",
"Boise, United States of America",
"Phoenix, United States of America"
],
"1080": [
"Belize, Belize",
"Winnipeg, Canada",
"Rainy River, Canada",
"Resolute, Canada",
"Rankin Inlet, Canada",
"Regina, Canada",
"Swift Current, Canada",
"Costa Rica, Costa Rica",
"Galapagos, Ecuador",
"Guatemala, Guatemala",
"Tegucigalpa, Honduras",
"Mexico City, Mexico",
"Merida, Mexico",
"Monterrey, Mexico",
"Matamoros, Mexico",
"Bahia Banderas, Mexico",
"Managua, Nicaragua",
"El Salvador, El Salvador",
"Chicago, United States of America",
"Tell City, Indiana",
"Knox, Indiana",
"Menominee, United States of America",
"Center, North Dakota",
"New_Salem, North Dakota",
"Beulah, North Dakota"
],
"1140": [
"Eirunepe, Brazil",
"Rio Branco, Brazil",
"Nassau, Bahamas",
"Toronto, Canada",
"Nipigon, Canada",
"Thunder Bay, Canada",
"Iqaluit, Canada",
"Pangnirtung, Canada",
"Atikokan, Canada",
"Easter, Chile",
"Bogota, Colombia",
"Havana, Cuba",
"Guayaquil, Ecuador",
"Port-au-Prince, Haiti",
"Jamaica, Jamaica",
"Cayman, Cayman Islands",
"Cancun, Mexico",
"Panama, Panama",
"Lima, Peru",
"Grand Turk, Turks and Caicos Islands",
"New York, United States of America",
"Detroit, United States of America",
"Louisville, Kentucky",
"Monticello, Kentucky",
"Indianapolis, Indiana",
"Vincennes, Indiana",
"Winamac, Indiana",
"Marengo, Indiana",
"Petersburg, Indiana",
"Vevay, Indiana"
],
"1200": [
"Antigua, Antigua and Barbuda",
"Anguilla, Anguilla",
"Aruba, Aruba",
"Barbados, Barbados",
"St Barthelemy, Saint Barthélemy",
"Bermuda, Bermuda",
"La Paz, Bolivia (Plurinational State of)",
"Kralendijk, Bonaire, Sint Eustatius and Saba",
"Campo Grande, Brazil",
"Cuiaba, Brazil",
"Porto Velho, Brazil",
"Boa Vista, Brazil",
"Manaus, Brazil",
"Halifax, Canada",
"Glace Bay, Canada",
"Moncton, Canada",
"Goose Bay, Canada",
"Blanc-Sablon, Canada",
"Curacao, Curaçao",
"Dominica, Dominica",
"Santo Domingo, Dominican Republic",
"Grenada, Grenada",
"Thule, Greenland",
"Guadeloupe, Guadeloupe",
"Guyana, Guyana",
"St Kitts, Saint Kitts and Nevis",
"St Lucia, Saint Lucia",
"Marigot, Saint Martin (French part)",
"Martinique, Martinique",
"Montserrat, Montserrat",
"Puerto Rico, Puerto Rico",
"Lower Princes, Sint Maarten (Dutch part)",
"Port of_Spain, Trinidad and Tobago",
"St Vincent, Saint Vincent and the Grenadines",
"Caracas, Venezuela (Bolivarian Republic of)",
"Tortola, Virgin Islands (British)",
"St Thomas, Virgin Islands (U.S.)"
],
"1260": [
"Palmer, Antarctica",
"Rothera, Antarctica",
"Buenos Aires, Argentina",
"Cordoba, Argentina",
"Salta, Argentina",
"Jujuy, Argentina",
"Tucuman, Argentina",
"Catamarca, Argentina",
"La Rioja, Argentina",
"San Juan, Argentina",
"Mendoza, Argentina",
"San Luis, Argentina",
"Rio Gallegos, Argentina",
"Ushuaia, Argentina",
"Belem, Brazil",
"Fortaleza, Brazil",
"Recife, Brazil",
"Araguaina, Brazil",
"Maceio, Brazil",
"Bahia, Brazil",
"Sao Paulo, Brazil",
"Santarem, Brazil",
"Santiago, Chile",
"Punta Arenas, Chile",
"Stanley, Falkland Islands (Malvinas)",
"Cayenne, French Guiana",
"Nuuk, Greenland",
"Miquelon, Saint Pierre and Miquelon",
"Asuncion, Paraguay",
"Paramaribo, Suriname",
"Montevideo, Uruguay"
],
"1320": [
"Noronha, Brazil",
"South Georgia, South Georgia and the South Sandwich Islands"
],
"1380": [
"Cape Verde, Cabo Verde",
"Scoresbysund, Greenland",
"Azores, Portugal"
]
}
exports.offsets = [1380,1320,1260,1200,1140,1080,1020,960,900,840,780,720,660,600,540,480,420,360,300,240,180,120,60,0];
exports.timezones = function(offs) {
switch (offs) {
case 1380: return ["Cape Verde, Cabo Verde","Scoresbysund, Greenland","Azores, Portugal"];
case 1320: return ["Noronha, Brazil","South Georgia, South Georgia and the South Sandwich Islands"];
case 1260: return ["Palmer, Antarctica","Rothera, Antarctica","Buenos Aires, Argentina","Cordoba, Argentina","Salta, Argentina","Jujuy, Argentina","Tucuman, Argentina","Catamarca, Argentina","La Rioja, Argentina","San Juan, Argentina","Mendoza, Argentina","San Luis, Argentina","Rio Gallegos, Argentina","Ushuaia, Argentina","Belem, Brazil","Fortaleza, Brazil","Recife, Brazil","Araguaina, Brazil","Maceio, Brazil","Bahia, Brazil","Sao Paulo, Brazil","Santarem, Brazil","Santiago, Chile","Punta Arenas, Chile","Stanley, Falkland Islands (Malvinas)","Cayenne, French Guiana","Nuuk, Greenland","Miquelon, Saint Pierre and Miquelon","Asuncion, Paraguay","Paramaribo, Suriname","Montevideo, Uruguay"];
case 1200: return ["Antigua, Antigua and Barbuda","Anguilla, Anguilla","Aruba, Aruba","Barbados, Barbados","St Barthelemy, Saint Barthélemy","Bermuda, Bermuda","La Paz, Bolivia (Plurinational State of)","Kralendijk, Bonaire, Sint Eustatius and Saba","Campo Grande, Brazil","Cuiaba, Brazil","Porto Velho, Brazil","Boa Vista, Brazil","Manaus, Brazil","Halifax, Canada","Glace Bay, Canada","Moncton, Canada","Goose Bay, Canada","Blanc-Sablon, Canada","Curacao, Curaçao","Dominica, Dominica","Santo Domingo, Dominican Republic","Grenada, Grenada","Thule, Greenland","Guadeloupe, Guadeloupe","Guyana, Guyana","St Kitts, Saint Kitts and Nevis","St Lucia, Saint Lucia","Marigot, Saint Martin (French part)","Martinique, Martinique","Montserrat, Montserrat","Puerto Rico, Puerto Rico","Lower Princes, Sint Maarten (Dutch part)","Port of_Spain, Trinidad and Tobago","St Vincent, Saint Vincent and the Grenadines","Caracas, Venezuela (Bolivarian Republic of)","Tortola, Virgin Islands (British)","St Thomas, Virgin Islands (U.S.)"];
case 1140: return ["Eirunepe, Brazil","Rio Branco, Brazil","Nassau, Bahamas","Toronto, Canada","Nipigon, Canada","Thunder Bay, Canada","Iqaluit, Canada","Pangnirtung, Canada","Atikokan, Canada","Easter, Chile","Bogota, Colombia","Havana, Cuba","Guayaquil, Ecuador","Port-au-Prince, Haiti","Jamaica, Jamaica","Cayman, Cayman Islands","Cancun, Mexico","Panama, Panama","Lima, Peru","Grand Turk, Turks and Caicos Islands","New York, United States of America","Detroit, United States of America","Louisville, Kentucky","Monticello, Kentucky","Indianapolis, Indiana","Vincennes, Indiana","Winamac, Indiana","Marengo, Indiana","Petersburg, Indiana","Vevay, Indiana"];
case 1080: return ["Belize, Belize","Winnipeg, Canada","Rainy River, Canada","Resolute, Canada","Rankin Inlet, Canada","Regina, Canada","Swift Current, Canada","Costa Rica, Costa Rica","Galapagos, Ecuador","Guatemala, Guatemala","Tegucigalpa, Honduras","Mexico City, Mexico","Merida, Mexico","Monterrey, Mexico","Matamoros, Mexico","Bahia Banderas, Mexico","Managua, Nicaragua","El Salvador, El Salvador","Chicago, United States of America","Tell City, Indiana","Knox, Indiana","Menominee, United States of America","Center, North Dakota","New_Salem, North Dakota","Beulah, North Dakota"];
case 1020: return ["Edmonton, Canada","Cambridge Bay, Canada","Yellowknife, Canada","Inuvik, Canada","Creston, Canada","Dawson Creek, Canada","Fort Nelson, Canada","Whitehorse, Canada","Dawson, Canada","Mazatlan, Mexico","Chihuahua, Mexico","Ojinaga, Mexico","Hermosillo, Mexico","Denver, United States of America","Boise, United States of America","Phoenix, United States of America"];
case 960: return ["Vancouver, Canada","Tijuana, Mexico","Pitcairn, Pitcairn","Los Angeles, United States of America"];
case 900: return ["Gambier, French Polynesia","Anchorage, United States of America","Juneau, United States of America","Sitka, United States of America","Metlakatla, United States of America","Yakutat, United States of America","Nome, United States of America"];
case 840: return ["Rarotonga, Cook Islands","Kiritimati, Kiribati","Tahiti, French Polynesia","Adak, United States of America","Honolulu, United States of America"];
case 780: return ["McMurdo, Antarctica","Pago Pago, American Samoa","Fiji, Fiji","Kanton, Kiribati","Niue, Niue","Auckland, New Zealand","Fakaofo, Tokelau","Tongatapu, Tonga","Midway, United States Minor Outlying Islands","Apia, Samoa"];
case 720: return ["Tarawa, Kiribati","Majuro, Marshall Islands","Kwajalein, Marshall Islands","Norfolk, Norfolk Island","Nauru, Nauru","Kamchatka, Russian Federation","Anadyr, Russian Federation","Funafuti, Tuvalu","Wake, United States Minor Outlying Islands","Wallis, Wallis and Futuna"];
case 660: return ["Casey, Antarctica","Lord Howe, Australia","Macquarie, Australia","Hobart, Australia","Melbourne, Australia","Sydney, Australia","Pohnpei, Micronesia (Federated States of)","Kosrae, Micronesia (Federated States of)","Noumea, New Caledonia","Bougainville, Papua New Guinea","Magadan, Russian Federation","Sakhalin, Russian Federation","Srednekolymsk, Russian Federation","Guadalcanal, Solomon Islands","Efate, Vanuatu"];
case 600: return ["DumontDUrville, Antarctica","Brisbane, Australia","Lindeman, Australia","Chuuk, Micronesia (Federated States of)","Guam, Guam","Saipan, Northern Mariana Islands","Port Moresby, Papua New Guinea","Vladivostok, Russian Federation","Ust-Nera, Russian Federation"];
case 540: return ["Jayapura, Indonesia","Tokyo, Japan","Pyongyang, Korea (Democratic People's Republic of)","Seoul, Korea, Republic of","Palau, Palau","Chita, Russian Federation","Yakutsk, Russian Federation","Khandyga, Russian Federation","Dili, Timor-Leste"];
case 480: return ["Perth, Australia","Brunei, Brunei Darussalam","Shanghai, China","Hong Kong, Hong Kong","Makassar, Indonesia","Ulaanbaatar, Mongolia","Choibalsan, Mongolia","Macau, Macao","Kuala Lumpur, Malaysia","Kuching, Malaysia","Manila, Philippines","Irkutsk, Russian Federation","Singapore, Singapore","Taipei, Taiwan, Province of China"];
case 420: return ["Davis, Antarctica","Christmas, Christmas Island","Jakarta, Indonesia","Pontianak, Indonesia","Phnom Penh, Cambodia","Vientiane, Lao People's Democratic Republic","Hovd, Mongolia","Novosibirsk, Russian Federation","Barnaul, Russian Federation","Tomsk, Russian Federation","Novokuznetsk, Russian Federation","Krasnoyarsk, Russian Federation","Bangkok, Thailand","Ho Chi_Minh, Viet Nam"];
case 360: return ["Vostok, Antarctica","Dhaka, Bangladesh","Thimphu, Bhutan","Urumqi, China","Chagos, British Indian Ocean Territory","Bishkek, Kyrgyzstan","Almaty, Kazakhstan","Qostanay, Kazakhstan","Omsk, Russian Federation"];
case 300: return ["Mawson, Antarctica","Qyzylorda, Kazakhstan","Aqtobe, Kazakhstan","Aqtau, Kazakhstan","Atyrau, Kazakhstan","Oral, Kazakhstan","Maldives, Maldives","Karachi, Pakistan","Yekaterinburg, Russian Federation","Kerguelen, French Southern Territories","Dushanbe, Tajikistan","Ashgabat, Turkmenistan","Samarkand, Uzbekistan","Tashkent, Uzbekistan"];
case 240: return ["Dubai, United Arab Emirates","Yerevan, Armenia","Baku, Azerbaijan","Tbilisi, Georgia","Mauritius, Mauritius","Muscat, Oman","Reunion, Réunion","Astrakhan, Russian Federation","Saratov, Russian Federation","Ulyanovsk, Russian Federation","Samara, Russian Federation","Mahe, Seychelles"];
case 180: return ["Syowa, Antarctica","Bahrain, Bahrain","Minsk, Belarus","Djibouti, Djibouti","Asmara, Eritrea","Addis Ababa, Ethiopia","Baghdad, Iraq","Nairobi, Kenya","Comoro, Comoros","Kuwait, Kuwait","Antananarivo, Madagascar","Qatar, Qatar","Moscow, Russian Federation","Simferopol, Ukraine","Kirov, Russian Federation","Volgograd, Russian Federation","Riyadh, Saudi Arabia","Mogadishu, Somalia","Istanbul, Turkey","Dar es_Salaam, Tanzania, United Republic of","Kampala, Uganda","Aden, Yemen","Mayotte, Mayotte"];
case 120: return ["Mariehamn, Åland Islands","Sofia, Bulgaria","Bujumbura, Burundi","Gaborone, Botswana","Lubumbashi, Congo, Democratic Republic of the","Nicosia, Cyprus","Famagusta, Cyprus","Tallinn, Estonia","Cairo, Egypt","Helsinki, Finland","Athens, Greece","Jerusalem, Israel","Amman, Jordan","Beirut, Lebanon","Maseru, Lesotho","Vilnius, Lithuania","Riga, Latvia","Tripoli, Libya","Chisinau, Moldova, Republic of","Blantyre, Malawi","Maputo, Mozambique","Windhoek, Namibia","Gaza, Palestine, State of","Hebron, Palestine, State of","Bucharest, Romania","Kaliningrad, Russian Federation","Kigali, Rwanda","Khartoum, Sudan","Juba, South Sudan","Damascus, Syrian Arab Republic","Mbabane, Eswatini","Kiev, Ukraine","Uzhgorod, Ukraine","Zaporozhye, Ukraine","Johannesburg, South Africa","Lusaka, Zambia","Harare, Zimbabwe"];
case 60: return ["Andorra, Andorra","Tirane, Albania","Luanda, Angola","Vienna, Austria","Sarajevo, Bosnia and Herzegovina","Brussels, Belgium","Porto-Novo, Benin","Kinshasa, Congo, Democratic Republic of the","Bangui, Central African Republic","Brazzaville, Congo","Zurich, Switzerland","Douala, Cameroon","Prague, Czechia","Berlin, Germany","Busingen, Germany","Copenhagen, Denmark","Algiers, Algeria","El Aaiun, Western Sahara","Madrid, Spain","Ceuta, Spain","Paris, France","Libreville, Gabon","Gibraltar, Gibraltar","Malabo, Equatorial Guinea","Zagreb, Croatia","Budapest, Hungary","Rome, Italy","Vaduz, Liechtenstein","Luxembourg, Luxembourg","Casablanca, Morocco","Monaco, Monaco","Podgorica, Montenegro","Skopje, North Macedonia","Malta, Malta","Niamey, Niger","Lagos, Nigeria","Amsterdam, Netherlands","Oslo, Norway","Warsaw, Poland","Belgrade, Serbia","Stockholm, Sweden","Ljubljana, Slovenia","Longyearbyen, Svalbard and Jan Mayen","Bratislava, Slovakia","San Marino, San Marino","Ndjamena, Chad","Tunis, Tunisia","Vatican, Holy See"];
case 0: return ["Troll, Antarctica","Ouagadougou, Burkina Faso","Abidjan, Côte d'Ivoire","Canary, Spain","Faroe, Faroe Islands","London, United Kingdom of Great Britain and Northern Ireland","Guernsey, Guernsey","Accra, Ghana","Danmarkshavn, Greenland","Banjul, Gambia","Conakry, Guinea","Bissau, Guinea-Bissau","Dublin, Ireland","Isle of_Man, Isle of Man","Reykjavik, Iceland","Jersey, Jersey","Monrovia, Liberia","Bamako, Mali","Nouakchott, Mauritania","Lisbon, Portugal","Madeira, Portugal","St Helena, Saint Helena, Ascension and Tristan da Cunha","Freetown, Sierra Leone","Dakar, Senegal","Sao Tome, Sao Tome and Principe","Lome, Togo"];
default: return ["Houston, we have a bug."];
}
};

View File

@ -57,6 +57,10 @@ fs.createReadStream(__dirname+'/country.csv')
zonelist.push(zone.name);
offsdict[zone.offs] = zonelist;
}
offsets = [];
for (o in offsdict) {
offsets.unshift(parseInt(o));
}
fs.open("fourTwentyTz.js","w", (err, fd) => {
if (err) {
console.log("Can't open output file");
@ -65,8 +69,18 @@ fs.createReadStream(__dirname+'/country.csv')
fs.write(fd, "// Generated by mkFourTwentyTz.js\n", handleWrite);
fs.write(fd, `// ${Date()}\n`, handleWrite);
fs.write(fd, "// Data source: https://timezonedb.com/files/timezonedb.csv.zip\n", handleWrite);
fs.write(fd, "exports.timezones = ", handleWrite);
fs.write(fd, JSON.stringify(offsdict, null, 4), handleWrite);
fs.write(fd, "exports.offsets = ", handleWrite);
fs.write(fd, JSON.stringify(offsets), handleWrite);
fs.write(fd, ";\n", handleWrite);
fs.write(fd, "exports.timezones = function(offs) {\n", handleWrite);
fs.write(fd, " switch (offs) {\n", handleWrite);
for (i=0; i<offsets.length; i++) {
let o = offsets[i].toString();
fs.write(fd, ` case ${o}: return ${JSON.stringify(offsdict[o])};\n`, handleWrite);
}
fs.write(fd, " default: return [\"Houston, we have a bug.\"];\n", handleWrite);
fs.write(fd, " }\n", handleWrite);
fs.write(fd, "};\n", handleWrite);
console.log('Done.');
});
})

View File

@ -9,4 +9,5 @@
0.09: Tab anywhere to open the launcher.
0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements.
0.11: Show the gadgetbridge weather temperature (settings).
0.12: Added humidity to data.
0.12: Added humidity to data.
0.13: Improved battery visualization.

View File

@ -20,7 +20,7 @@ let cOrange = "#FF9900";
let cPurple = "#FF00DC";
let cWhite = "#FFFFFF";
let cBlack = "#000000";
let cGrey = "#9E9E9E";
let cGrey = "#424242";
/*
* Global lcars variables
@ -143,7 +143,7 @@ function printData(key, y, c){
} else if (key == "HUMIDITY"){
text = "HUM";
var weather = getWeather();
value = parseInt(weather.hum) + "%";
value = weather.hum + "%";
} else if(key == "CORET"){
value = locale.temp(parseInt(E.getTemperature()));
@ -242,9 +242,14 @@ function drawPosition0(){
// The last line is a battery indicator too
var bat = E.getBattery() / 100.0;
var batX2 = parseInt((172 - 35) * bat + 35);
drawHorizontalBgLine(cOrange, 35, batX2-5, 171, 5);
drawHorizontalBgLine(cGrey, batX2+5, 172, 171, 5);
var batStart = 19;
var batWidth = 172 - batStart;
var batX2 = parseInt(batWidth * bat + batStart);
drawHorizontalBgLine(cOrange, batStart, batX2, 171, 5);
drawHorizontalBgLine(cGrey, batX2, 172, 171, 5);
for(var i=0; i+batStart<=172; i+=parseInt(batWidth/4)){
drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8)
}
// Draw Infos
drawInfo();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -3,4 +3,6 @@
0.03: Bangle.js 2 support
0.04: Move Quiet Mode LCD options from global settings to this app
0.05: Avoid immediately redrawing widgets on load
0.06: Fix: don't try to redraw widget when widgets not loaded
0.06: Fix: don't try to redraw widget when widgets not loaded
0.07: Option to switch theme
Changed time selection to 5-minute intervals

View File

@ -9,6 +9,11 @@ Automatically turn Quiet Mode on or off at set times, and display a widget when
| ![Edit Schedule menu](screenshot_b1_edit.png) | ![Edit Schedule menu](screenshot_b2_edit.png) |
| ![LCD Options menu](screenshot_b1_lcd.png) | ![LCD Options menu](screenshot_b2_lcd.png) |
### Switch Theme:
Switch to dark theme during Quiet Mode.
* **NOTE**: This switches between the default "Dark BW" and "Light BW" themes, so custom theme settings will be lost.
### LCD Settings:
If set, these override the default LCD settings while Quiet Mode is active.

View File

@ -3,7 +3,7 @@ Bangle.drawWidgets();
const modeNames = ["Off", "Alarms", "Silent"];
// load global brightness setting
// load global settings
let bSettings = require('Storage').readJSON('setting.json',true)||{};
let current = 0|bSettings.quiet;
delete bSettings; // we don't need any other global settings
@ -18,6 +18,7 @@ delete bSettings; // we don't need any other global settings
*/
function save() {
require('Storage').writeJSON('qmsched.json', settings);
eval(require('Storage').read('qmsched.boot.js')); // apply new schedules right away
}
function get(key, def) {
return (key in settings) ? settings[key] : def;
@ -77,37 +78,66 @@ function formatTime(t) {
const mins = Math.round((t-hrs)*60);
return (" "+hrs).substr(-2)+":"+("0"+mins).substr(-2);
}
/**
* Apply theme
*/
function applyTheme() {
const theme = (require("Storage").readJSON("setting.json", 1) || {}).theme;
if (theme && theme.dark===g.theme.dark) return; // already correct
g.theme = theme;
delete g.reset;
g._reset = g.reset;
g.reset = function(n) { return g._reset().setColor(g.theme.fg).setBgColor(g.theme.bg); };
g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); };
g.clear(1);
Bangle.drawWidgets();
delete m.lastIdx; // force redraw
m.draw();
}
/**
* Library uses this to make the app update itself
* @param {int} mode New Quite Mode
*/
function setAppQuietMode(mode) {
if (mode === current) return;
current = mode;
delete m.lastIdx; // force redraw
applyTheme();
if (m.lastIdx===undefined) m.draw(); // applyTheme didn't redraw menu, but we need to show updated mode
}
let m;
function showMainMenu() {
let _m, menu = {
let menu = {
"": {"title": "Quiet Mode"},
"< Exit": () => load()
};
// "Current Mode""Silent" won't fit on Bangle.js 2
menu["Current"+((process.env.HWVERSION===2) ? "" : " Mode")] = {
value: current,
format: v => modeNames[v],
onchange: function(v) {
if (v<0) {v = 2;}
if (v>2) {v = 0;}
require("qmsched").setMode(v);
current = v;
this.value = v;
},
min:0, max:2, wrap: true,
format: () => modeNames[current],
onchange: require("qmsched").setMode, // library calls setAppMode(), which updates `current`
};
scheds.sort((a, b) => (a.hr-b.hr));
scheds.forEach((sched, idx) => {
menu[formatTime(sched.hr)] = {
format: () => modeNames[sched.mode], // abuse format to right-align text
onchange: function() {
_m.draw = ()=> {}; // prevent redraw of main menu over edit menu
onchange: () => {
m.draw = ()=> {}; // prevent redraw of main menu over edit menu (needed because we abuse format/onchange)
showEditMenu(idx);
}
};
});
menu["Add Schedule"] = () => showEditMenu(-1);
menu["Switch Theme"] = {
value: !!get("switchTheme"),
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => v ? set("switchTheme", v) : unset("switchTheme"),
};
menu["LCD Settings"] = () => showOptionsMenu();
_m = E.showMenu(menu);
m = E.showMenu(menu);
}
function showEditMenu(index) {
@ -125,31 +155,19 @@ function showEditMenu(index) {
"< Cancel": () => showMainMenu(),
"Hours": {
value: hrs,
onchange: function(v) {
if (v<0) {v = 23;}
if (v>23) {v = 0;}
hrs = v;
this.value = v;
}, // no arrow fn -> preserve 'this'
min:0, max:23, wrap:true,
onchange: v => {hrs = v;},
},
"Minutes": {
value: mins,
onchange: function(v) {
if (v<0) {v = 59;}
if (v>59) {v = 0;}
mins = v;
this.value = v;
}, // no arrow fn -> preserve 'this'
min:0, max:55, step:5, wrap:true,
onchange: v => {mins = v;},
},
"Switch to": {
value: mode,
min:0, max:2, wrap:true,
format: v => modeNames[v],
onchange: function(v) {
if (v<0) {v = 2;}
if (v>2) {v = 0;}
mode = v;
this.value = v;
}, // no arrow fn -> preserve 'this'
onchange: v => {mode = v;},
},
};
function getSched() {
@ -174,7 +192,7 @@ function showEditMenu(index) {
showMainMenu();
};
}
return E.showMenu(menu);
m = E.showMenu(menu);
}
function showOptionsMenu() {
@ -244,7 +262,7 @@ function showOptionsMenu() {
onchange: () => {toggle("wakeOnTwist");},
},
};
return E.showMenu(oMenu);
m = E.showMenu(oMenu);
}
loadSettings();

View File

@ -1,5 +1,7 @@
// apply Quiet Mode schedules
(function qm() {
if (Bangle.qmTimeout) clearTimeout(Bangle.qmTimeout); // so the app can eval() this file to apply changes right away
delete Bangle.qmTimeout;
let bSettings = require('Storage').readJSON('setting.json',true)||{};
const curr = 0|bSettings.quiet;
delete bSettings;
@ -18,7 +20,7 @@
let t = 3600000*(next.hr-hr); // timeout in milliseconds
if (t<0) {t += 86400000;} // scheduled for tomorrow: add a day
/* update quiet mode at the correct time. */
setTimeout(() => {
Bangle.qmTimeout=setTimeout(() => {
require("qmsched").setMode(mode);
qm(); // schedule next update
}, t);

View File

@ -1,5 +1,31 @@
/**
* Apply LCD options for given mode
* Apply appropriate theme for given mode
* @param {int} mode Quiet Mode
*/
function switchTheme(mode) {
if (!!mode === g.theme.dark) return; // nothing to do
let s = require("Storage").readJSON("setting.json", 1) || {};
// default themes, copied from settings.js:showThemeMenu()
function cl(x) { return g.setColor(x).getColor(); }
s.theme = mode ? {
// 'Dark BW'
fg: cl("#fff"), bg: cl("#000"),
fg2: cl("#0ff"), bg2: cl("#000"),
fgH: cl("#fff"), bgH: cl("#00f"),
dark: true
} : {
// 'Light BW'
fg: cl("#000"), bg: cl("#fff"),
fg2: cl("#000"), bg2: cl("#cff"),
fgH: cl("#000"), bgH: cl("#0ff"),
dark: false
};
require("Storage").writeJSON("setting.json", s);
// reload clocks with new theme, otherwise just wait for user to switch apps
if (Bangle.CLOCK) load(global.__FILE__);
}
/**
* Apply LCD options and theme for given mode
* @param {int} mode Quiet Mode
*/
exports.applyOptions = function(mode) {
@ -8,6 +34,7 @@ exports.applyOptions = function(mode) {
Bangle.setOptions(get("options", {}));
Bangle.setLCDBrightness(get("brightness", 1));
Bangle.setLCDTimeout(get("timeout", 10));
if ((require("Storage").readJSON("qmsched.json", 1) || {}).switchTheme) switchTheme(mode);
};
/**
* Set new Quiet Mode and apply Bangle options
@ -20,4 +47,5 @@ exports.setMode = function(mode) {
));
exports.applyOptions(mode);
if (typeof WIDGETS === "object" && "qmsched" in WIDGETS) WIDGETS["qmsched"].draw();
if (global.setAppQuietMode) setAppQuietMode(mode); // current app knows how to update itself
};

View File

@ -18,7 +18,7 @@
return; // drawWidgets will call draw again
}
let x = this.x, y = this.y;
g.clearRect(x, y, x+23, y+23);
g.reset().clearRect(x, y, x+23, y+23);
// quiet mode: draw red one-way-street sign (dim red on Bangle.js 1)
x = this.x+11;y = this.y+11; // center of widget
g.setColor(process.env.HWVERSION===2 ? 1 : 0.8, 0, 0).fillCircle(x, y, 8);

View File

@ -8,3 +8,6 @@
Fix execution of other recorders (*.recorder.js)
Modified icons and colors for better visibility
Only show plotting speed if Latitude is available
0.07: Add recording for Barometer
Record all HRM events
Move recording for CoreTemp to its own app

View File

@ -52,19 +52,17 @@
};
},
hrm:function() {
var bpm = 0, bpmConfidence = 0;
var bpm = "", bpmConfidence = "";
function onHRM(h) {
if (h.confidence >= bpmConfidence) {
bpmConfidence = h.confidence;
bpm = h.bpm;
}
bpmConfidence = h.confidence;
bpm = h.bpm;
}
return {
name : "HR",
fields : ["Heartrate", "Confidence"],
getValues : () => {
var r = [bpm,bpmConfidence];
bpm = 0; bpmConfidence = 0;
bpm = ""; bpmConfidence = "";
return r;
},
start : () => {
@ -92,32 +90,6 @@
draw : (x,y) => g.setColor(Bangle.isCharging() ? "#0f0" : "#ff0").drawImage(atob("DAwBAABgH4G4EYG4H4H4H4GIH4AA"),x,y)
};
},
temp:function() {
var core = 0, skin = 0;
var hasCore = false;
function onCore(c) {
core=c.core;
skin=c.skin;
hasCore = true;
}
return {
name : "Core",
fields : ["Core","Skin"],
getValues : () => {
var r = [core,skin];
return r;
},
start : () => {
hasCore = false;
Bangle.on('CoreTemp', onCore);
},
stop : () => {
hasCore = false;
Bangle.removeListener('CoreTemp', onCore);
},
draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAwBAAAOAKPOfgZgZgZgZgfgPAAA"),x,y)
};
},
steps:function() {
var lastSteps = 0;
return {
@ -133,8 +105,38 @@
draw : (x,y) => g.reset().drawImage(atob("DAwBAAMMeeeeeeeecOMMAAMMMMAA"),x,y)
};
}
// TODO: recAltitude from pressure sensor
};
if (Bangle.getPressure){
recorders['baro'] = function() {
var temp="",press="",alt="";
function onPress(c) {
temp=c.temperature;
press=c.pressure;
alt=c.altitude;
}
return {
name : "Baro",
fields : ["Barometer Temperature", "Barometer Pressure", "Barometer Altitude"],
getValues : () => {
var r = [temp,press,alt];
temp="";
press="";
alt="";
return r;
},
start : () => {
Bangle.setBarometerPower(1,"recorder");
Bangle.on('pressure', onPress);
},
stop : () => {
Bangle.setBarometerPower(0,"recorder");
Bangle.removeListener('pressure', onPress);
},
draw : (x,y) => g.setColor("#0f0").drawImage(atob("DAwBAAH4EIHIEIHIEIHIEIEIH4AA"),x,y)
};
}
}
/* eg. foobar.recorder.js
(function(recorders) {
recorders.foobar = {

View File

@ -1,6 +1,7 @@
# Run App
This app allows you to display the status of your run.
This app allows you to display the status of your run, it
shows distance, time, steps, cadence, pace and more.
To use it, start the app and press the middle button so that
the red `STOP` in the bottom right turns to a green `RUN`.

View File

@ -0,0 +1 @@
0.1: New app

View File

@ -0,0 +1,5 @@
# Time and Life
A simple watchface which displays the time when the screen is tapped and decays according to the rules of [Conway's game of life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life).
![](screenshot.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkB/4AGCY4PHC/4X/C/4X/C/4XvJ/4X/C/4X/C/4X3AH4A/AH4A/AH4A/"))

225
apps/timeandlife/app.js Normal file
View File

@ -0,0 +1,225 @@
// Globals
const X = 176,
Y = 176; // screen resolution of bangle 2
const STEP_TIMEOUT = 1000;
const PAUSE_TIME = 3000;
const ONE = [
[0, 1, 0],
[1, 1, 0],
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
[1, 1, 1],
];
const TWO = [
[0, 1, 0],
[1, 0, 1],
[0, 0, 1],
[0, 1, 0],
[1, 0, 0],
[1, 0, 0],
[1, 1, 1],
];
const THREE = [
[0, 1, 0],
[1, 0, 1],
[0, 0, 1],
[0, 1, 0],
[0, 0, 1],
[1, 0, 1],
[0, 1, 0],
];
const FOUR = [
[0, 0, 1],
[1, 0, 1],
[1, 0, 1],
[1, 1, 1],
[0, 0, 1],
[0, 0, 1],
[0, 0, 1],
];
const FIVE = [
[1, 1, 1],
[1, 0, 0],
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[1, 0, 1],
[0, 1, 0],
];
const SIX = [
[0, 1, 0],
[1, 0, 1],
[1, 0, 0],
[1, 1, 0],
[1, 0, 1],
[1, 0, 1],
[0, 1, 0],
];
const SEVEN = [
[1, 1, 1],
[1, 0, 1],
[0, 0, 1],
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
];
const EIGHT = [
[0, 1, 0],
[1, 0, 1],
[1, 0, 1],
[0, 1, 0],
[1, 0, 1],
[1, 0, 1],
[0, 1, 0],
];
const NINE = [
[0, 1, 0],
[1, 0, 1],
[1, 0, 1],
[0, 1, 1],
[0, 0, 1],
[1, 0, 1],
[0, 1, 0],
];
const ZERO = [
[0, 1, 0],
[1, 0, 1],
[1, 0, 1],
[1, 0, 1],
[1, 0, 1],
[1, 0, 1],
[0, 1, 0],
];
const NUMBERS = [ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE];
// Arraybuffers to store game state
// 484 8 bit integers that are either 1 or 0 form the 22 x 22 grid
let data = new Uint8Array(484);
let nextData = new Uint8Array(484);
let palette = new Uint16Array(256); // palette for rendering data
palette[0] = g.theme.bg;
palette[1] = g.theme.fg;
let lastPaused = new Date();
// Conway's game of life
// if < 2 neighbours, set off
// if 2 or 3 neighbours, set on
// if > 3 neighbours, set off
/*const updateStateC = E.compiledC(`
// void run(int, int)
void run(char* n, char* m){
// n is a pointer to the first byte in data, m is for nextdata
int count = 0;
for (int i=0;i<484;i++) {
// Add 8 neighbours, wrapping around
count =
*(n+(i+484-23)%484) +
*(n+(i+484-22)%484) +
*(n+(i+484-21)%484) +
*(n+(i+484-1)%484) +
*(n+(i+484+1)%484) +
*(n+(i+484+21)%484) +
*(n+(i+484+22)%484) +
*(n+(i+484+23)%484);
if (count < 2 || count > 3) {
*(m+i) = 0;
} else {
*(m+i) = 1;
}
}
}
`);*/
// precompiled - taken from file downloaded from Bangle.js storage after
// Web IDE upload
const updateStateC=function(a){return a=atob('ACLwtU/08nMBJxZGAvLNFQL1536V+/P0A/sUVJ778/UD+xXlAvLPHhD4BMBEXZ778/UD+xXlZERFXQLy4x4sRJ778/UD+xXlAvLlHkVdLESe+/P1A/sV5QLy+R5FXSxEnvvz9QP7FeUC9f1+RV0sRJ778/UD+xXlAvL7HkVdLESe+/P1A/sV5UVdLEQCPAIsNL88RjRGjFQBMrL18n+10fC9AAA='),{run:E.nativeCall(1,'void(int, int)',a)}}();
function draw() {
g.drawImage({
width:22, height:22, bpp: 8,
palette : palette, // ideally we'd just have BPP 1 and would render direct but it makes the code tricky
buffer : data.buffer,
},0,0,{scale:8});
}
const step = () => {
if (new Date() - lastPaused < PAUSE_TIME) {
return;
}
let startTime = new Date();
const dataAddr = E.getAddressOf(data, true);
const nextDataAddr = E.getAddressOf(nextData, true);
updateStateC.run(dataAddr, nextDataAddr);
draw();
data.set(nextData);
};
const setPixel = (i, j) => {
data[i * 22 + j] = 1;
nextData[i * 22 + j] = 1;
};
const setNum = (character, i, j) => {
const startJ = j;
character.forEach(row => {
j = startJ;
row.forEach(pixel => {
if (pixel) setPixel(i, j);
j++;
});
i++;
});
};
const setDots = () => {
setPixel(10, 10);
setPixel(12, 10);
};
const drawTime = () => {
lastPaused = new Date();
g.clear();
data.fill(0);
const d = new Date();
const hourTens = Math.floor(d.getHours() / 10);
const hourOnes = d.getHours() % 10;
const minuteTens = Math.floor(d.getMinutes() / 10);
const minuteOnes = d.getMinutes() % 10;
setNum(NUMBERS[hourTens], 8, 1);
setNum(NUMBERS[hourOnes], 8, 6);
setDots();
setNum(NUMBERS[minuteTens], 8, 13);
setNum(NUMBERS[minuteOnes], 8, 18);
draw();
};
const start = () => {
Bangle.setUI("clock"); // Show launcher when middle button pressed
g.clear();
Bangle.setLCDTimeout(20); // backlight/lock timeout in seconds
let stepInterval = setInterval(step, STEP_TIMEOUT);
// Handlers
Bangle.on('touch', drawTime);
// Sleep mode
Bangle.on('lock', isLocked => {
if (stepInterval) {
clearInterval(stepInterval);
}
stepInterval = undefined;
if (!isLocked) {
drawTime();
stepInterval = setInterval(step, STEP_TIMEOUT);
}
});
drawTime();
};
start();

BIN
apps/timeandlife/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,3 +1,4 @@
0.01: First commit
0.02: Handle new firmwares with 'lock' event
0.03: Don't try to be fancy - just bail out on firmwares without a lock event
0.04: Set sortorder to -1 so that widget always takes up the furthest left position

2
apps/widpa/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: First release
0.02: Size widget after step count is reset

16
apps/widpa/README.md Normal file
View File

@ -0,0 +1,16 @@
# Simple Pedometer Widget
*Displays the current step count from `Bangle.getHealthStatus("day").steps` in (6x8,2) font, Requires firmware v2.11.21 or later*
* Designed to be small, minimal, does one thing well, no settings
* Supports Bangle 1 and Bangle 2
## Notes
* Requires firmware v2.11.21 or later
* `Bangle.getHealthStatus("day").steps` is reset to zero if you reboot your watch with a long BTN Press
* The step count displayed may be a few steps more than that reported by widpedpm as widpedom may not always be loaded.
![](screenshot_widpa.png)
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

17
apps/widpa/widpa.wid.js Normal file
View File

@ -0,0 +1,17 @@
Bangle.on('step', function(s) { WIDGETS["widpa"].draw(); });
Bangle.on('lcdPower', function(on) {
if (on) WIDGETS["widpa"].draw();
});
WIDGETS["widpa"]={area:"tl",width:13,draw:function() {
if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off
var steps = Bangle.getHealthStatus("day").steps;
var w = 1 + (steps.toString().length)*12;
if (w != this.width) {this.width = w; setTimeout(() => Bangle.drawWidgets(),10); return;}
g.reset();
g.setColor(g.theme.bg);
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23);
g.setColor(g.theme.fg);
g.setFont('6x8',2);
g.setFontAlign(-1, 0);
g.drawString(steps, this.x, this.y + 12);
}};

2
apps/widpb/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: First release
0.02: Fixed widget id to wibpb, Size widget after step count is reset

17
apps/widpb/README.md Normal file
View File

@ -0,0 +1,17 @@
# Lato Pedometer Widget
*Displays the current step count from `Bangle.getHealthStatus("day").steps` in the Lato font, Requires firmware v2.11.21 or later*
* Designed to be minimal, does one thing well, no settings
* Supports Bangle 1 and Bangle 2
## Notes
* Requires firmware v2.11.21 or later
* Uses the Lato custom font, so memory footprint is 500 bytes larger than 'Simple Pedometer Widget'
* `Bangle.getHealthStatus("day").steps` is reset to zero if you reboot your watch with a long BTN Press
* The step count displayed may be a few steps more than that reported by widpedpm as widpedom may not always be loaded.
![](screenshot_widpb.png)
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

20
apps/widpb/widpb.wid.js Normal file
View File

@ -0,0 +1,20 @@
// on.step version
Bangle.on('step', function(s) { WIDGETS["bata"].draw(); });
Bangle.on('lcdPower', function(on) {
if (on) WIDGETS["widpb"].draw();
});
WIDGETS["widpb"]={area:"tl",width:13,draw:function() {
if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off
var steps = Bangle.getHealthStatus("day").steps;
var w = 1 + (steps.toString().length)*12;
if (w != this.width) {this.width = w; setTimeout(() => Bangle.drawWidgets(),10); return;}
g.reset();
g.setColor(g.theme.bg);
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
g.setColor(g.theme.fg);
// Lato from fonts.google.com, Actual height 17 (17 - 1), Numeric only
const scale = 1;
g.setFontCustom(atob("AAAAABwAAOAAAgAAHAADwAD4AB8AB8AA+AAeAADAAAAOAAP+AH/8B4DwMAGBgAwMAGBgAwOAOA//gD/4AD4AAAAAAAABgAAcAwDAGAwAwP/+B//wAAGAAAwAAGAAAAAAAAIAwHgOA4DwMA+BgOwMDmBg4wOeGA/gwDwGAAAAAAAAAGAHA8A4DwMAGBhAwMMGBjgwOcOA+/gDj4AAAAABgAAcAAHgADsAA5gAOMAHBgBwMAP/+B//wABgAAMAAAAAAAgD4OB/AwOYGBjAwMYGBjBwMe8Bh/AIHwAAAAAAAAAfAAP8AHxwB8GAdgwPMGBxgwMOOAB/gAH4AAAAAAABgAAMAABgAwMAeBgPgMHwBj4AN8AB+AAPAABAAAAAAAMfAH38B/xwMcGBhgwMMGBjgwP+OA+/gDj4AAAAAAAAOAAH4AA/gQMMGBgzwME8BhvAOPgA/4AD8AAEAAAAAAGAwA4OAHBwAAA="), 46, atob("BAgMDAwMDAwMDAwMBQ=="), 21+(scale<<8)+(1<<16));
g.setFontAlign(-1, 0);
g.drawString(steps, this.x, this.y + 12);
}};

2
core

@ -1 +1 @@
Subproject commit b05af96b2522a7a7225a56d804faf9383f8a8f97
Subproject commit 649489412e27ef770bc0c8ed12cfca6a17a98c0d