Merge branch 'espruino:master' into master

master
wagnerf42 2022-11-06 10:45:34 +01:00 committed by GitHub
commit 5cab67f226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
805 changed files with 30306 additions and 10466 deletions

View File

@ -22,9 +22,6 @@ jobs:
- name: Install typescript dependencies
working-directory: ./typescript
run: npm ci
- name: Build types
working-directory: ./typescript
run: npm run build:types
- name: Build all TS apps and widgets
working-directory: ./typescript
run: npm run build

3
.gitignore vendored
View File

@ -11,3 +11,6 @@ tests/Layout/testresult.bmp
apps.local.json
_site
.jekyll-cache
.owncloudsync.log
Desktop.ini
.sync_*.db*

View File

@ -324,7 +324,7 @@ and which gives information about the app for the Launcher.
```
* name, icon and description present the app in the app loader.
* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher` or empty.
* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher`, `bluetooth` or empty.
* storage is used to identify the app files and how to handle them
* data is used to clean up files when the app is uninstalled

View File

@ -149,11 +149,11 @@ function drawHSeg(x1,y1,x2,y2,Num,Color,Size) {
if (Color == "fg") {
g.setColor(g.theme.fg);
} else {
g.setColor(mColor[0],mColor[1],mColor[2]);
g.setColor(mColor[0],mColor[1],mColor[2]);
}
g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,Size);
} else {
g.setColor(bColor[0],bColor[1],bColor[2]);
g.setColor(bColor[0],bColor[1],bColor[2]);
g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1);
}
}
@ -166,7 +166,7 @@ function drawSSeg(x1,y1,x2,y2,Num,Color,Size) {
for (let j = 1; j < 8; j++) {
if (Font[Num][j-1][i-1] == 1) {
if (Color == "fg") {
g.setColor(sColor[0],sColor[1],sColor[2]);
g.setColor(sColor[0],sColor[1],sColor[2]);
} else {
g.setColor(g.theme.fg);
//g.setColor(0.7,0.7,0.7);
@ -253,8 +253,8 @@ function actions(v){
if(BTN1.read() === true) {
print("BTN pressed");
Bangle.showLauncher();
}
}
if(v==-1){
print("up swipe event");
if(settings.swupApp != "") load(settings.swupApp);
@ -269,7 +269,7 @@ function actions(v){
}
// Get Messages status
var messages = require("Storage").readJSON("messages.json",1)||[];
var messages_installed = require("Storage").read("messages") !== undefined;
//var BTconnected = NRF.getSecurityStatus().connected;
//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected);
@ -289,27 +289,27 @@ function drawWidgeds() {
g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
else
g.setColor(g.theme.dark ? "#666" : "#999");
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt);
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt);
//Battery
//print(E.getBattery());
//print(Bangle.isCharging());
var x1B = 130;
var y1B = 2;
var x2B = x1B + 20;
var y2B = y1B + 15;
g.setColor(g.theme.bg);
g.clearRect(x1B,y1B,x2B,y2B);
g.setColor(g.theme.fg);
g.drawRect(x1B,y1B,x2B,y2B);
g.fillRect(x1B,y1B,x1B+(E.getBattery()*(x2B-x1B)/100),y2B);
g.fillRect(x2B,y1B+(y2B-y1B)/2-3,x2B+4,y1B+(y2B-y1B)/2+3);
//Messages
@ -318,25 +318,25 @@ function drawWidgeds() {
var x2M = x1M + 25;
var y2M = y2B;
if (messages.some(m=>m.new)) {
if (messages_installed && require("messages").status() == "new") {
g.setColor(g.theme.fg);
g.fillRect(x1M,y1M,x2M,y2M);
g.setColor(g.theme.bg);
g.drawLine(x1M,y1M,x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2);
g.drawLine(x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2,x2M,y1M);
}
var strDow = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
var d = new Date();
var dow = d.getDay(),day = d.getDate(), month = d.getMonth() + 1, year = d.getFullYear();
print(strDow[dow] + ' ' + day + '.' + month + ' ' + year);
g.setColor(g.theme.fg);
g.setFontAlign(-1, -1,0);
g.setFont("Vector", 20);
g.drawString(strDow[dow] + ' ' + day, 0, 0, true);
}
@ -354,7 +354,7 @@ function SetFull(on) {
} else {
Ys = 30;
Bangle.setUI("updown",actions);
Bangle.on('swipe', function(direction) {
Bangle.on('swipe', function(direction) {
switch (direction) {
case 1:
print("swipe left event");
@ -362,7 +362,7 @@ function SetFull(on) {
print(settings.swleftApp);
break;
case -1:
print("swipe right event");
print("swipe right event");
if(settings.swrightApp != "") load(settings.swrightApp);
print(settings.swrightApp);
break;
@ -374,7 +374,7 @@ function SetFull(on) {
SegH = (Ye-Ys)/2;
Dy = SegH/16;
draw();
if (on != true) {

View File

@ -1,2 +1,3 @@
0.01: Initial version for upload
0.02: better theme support, configurable colors, small improvements
0.02: Better theme support, configurable colors, small improvements
0.03: Use `messages` library to check for new messages

View File

@ -1,7 +1,7 @@
{ "id": "7x7dotsclock",
"name": "7x7 Dots Clock",
"shortName":"7x7 Dots Clock",
"version":"0.02",
"version":"0.03",
"description": "A clock with a big 7x7 dots Font",
"icon": "dotsfontclock.png",
"tags": "clock",

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Fullscreen settings.
0.02: Fullscreen settings.
0.03: Tell clock widgets to hide.

View File

@ -115,6 +115,9 @@ function draw() {
}
}
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();
// Clear the screen once, at startup
@ -140,5 +143,3 @@ Bangle.on('lock', function(isLocked) {
});
// Show launcher when middle button pressed
Bangle.setUI("clock");

View File

@ -1,7 +1,7 @@
{
"id": "90sclk",
"name": "90s Clock",
"version": "0.02",
"version": "0.03",
"description": "A 90s style watch-face",
"readme": "README.md",
"icon": "app.png",

View File

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

View File

@ -0,0 +1,13 @@
# a_dndtoggle - Toggle Quiet Mode of the watch
When Quiet mode is off, just start this app to set quiet mode. Start it again to turn off quiet mode.
Work in progress.
#ToDo
Settings page, current status indicator.
## Creator
Hank - contact at http://forum.espruino.com

View File

@ -0,0 +1,43 @@
const modeNames = [/*LANG*/"Noisy", /*LANG*/"Alarms", /*LANG*/"Silent"];
let bSettings = require('Storage').readJSON('setting.json',true)||{};
let current = 0|bSettings.quiet;
//0 off
//1 alarms
//2 silent
console.log("old: " + current);
switch (current) {
case 0:
bSettings.quiet = 2;
Bangle.buzz();
setTimeout('Bangle.buzz();',500);
break;
case 1:
bSettings.quiet = 0;
Bangle.buzz();
break;
case 2:
bSettings.quiet = 0;
Bangle.buzz();
break;
default:
bSettings.quiet = 0;
Bangle.buzz();
}
console.log("new: " + bSettings.quiet);
E.showMessage(modeNames[current] + " -> " + modeNames[bSettings.quiet]);
setTimeout('exitApp();', 2000);
function exitApp(){
require("Storage").writeJSON("setting.json", bSettings);
// reload clocks with new theme, otherwise just wait for user to switch apps
load()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwJC/AAl/Agf/AAUAgIFDwEHAofgh/g/0Ag/wj+AnwVB/EegEfEIN4nkAh+AgE8vgVBAoV4Aoce/EAgfADQIFcjwpFHYIFCnxBFJopZBn5ZCMopxFPoqJFSowA/gA="))

View File

@ -0,0 +1,16 @@
{
"id": "a_dndtoggle",
"name": "a_dndtoggle - Toggle Quiet Mode of the watch",
"shortName": "A_DND Toggle",
"version": "0.01",
"description": "Toggle Quiet Mode of the watch just by starting this app.",
"icon": "a_dndtoggle.png",
"type": "app",
"tags": "tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"a_dndtoggle.app.js","url":"a_dndtoggle.app.js"},
{"name":"a_dndtoggle.img","url":"app-icon.js","evaluate":true}
],
"readme": "README.md"
}

View File

@ -10,3 +10,4 @@
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
0.11: Bangle.js2: New pixels, btn1 to exit
0.12: Actual pixels as of 29th Nov 2021
0.13: Bangle.js 2: Use setUI to add software back button

View File

@ -10,7 +10,7 @@ var img = atob("sIwDkm2S66DYwA2AAAAAHAHGSRxJEkAAgmGGBxDIADIdAFJIbAHF9HP00kBUC6Dt
var imgHeight = g.imageMetrics(img).height;
var imgScroll = Math.floor(Math.random()*imgHeight);
g.reset().setFont("6x15").setFontAlign(0,0);
g.clear(1).setFont("6x15").setFontAlign(0,0);
g.drawString(ENV.VERSION + " " + NRF.getAddress(), g.getWidth()/2, 171);
g.drawImage(img,0,24);
@ -69,4 +69,7 @@ function drawImage() {
// TODO: a nice little animation before
setTimeout(drawInfo, 1000);
setWatch(_=>load(), BTN1);
Bangle.setUI({
mode : "custom",
back : load
});

View File

@ -1,7 +1,7 @@
{
"id": "about",
"name": "About",
"version": "0.12",
"version": "0.13",
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
"icon": "app.png",
"tags": "tool,system",

View File

@ -2,3 +2,4 @@
0.02: Use the new multiplatform 'Layout' library
0.03: Exit as first menu option, dont show decimal places for seconds
0.04: Localisation, change Exit->Back to allow back-arrow to appear on 2v13 firmware
0.05: Add max G values during recording, record actual G values and magnitude to CSV

View File

@ -1,5 +1,6 @@
var fileNumber = 0;
var MAXLOGS = 9;
var logRawData = false;
function getFileName(n) {
return "accellog."+n+".csv";
@ -24,6 +25,11 @@ function showMenu() {
/*LANG*/"View Logs" : function() {
viewLogs();
},
/*LANG*/"Log raw data" : {
value : logRawData,
format : v => v?/*LANG*/"Yes":/*LANG*/"No",
onchange : v => { logRawData=v; }
},
};
E.showMenu(menu);
}
@ -78,6 +84,7 @@ function viewLogs() {
}
function startRecord(force) {
var stopped = false;
if (!force) {
// check for existing file
var f = require("Storage").open(getFileName(fileNumber), "r");
@ -92,39 +99,101 @@ function startRecord(force) {
var Layout = require("Layout");
var layout = new Layout({ type: "v", c: [
{type:"txt", font:"6x8", label:/*LANG*/"Samples", pad:2},
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
{type:"txt", font:"6x8", label:/*LANG*/"Time", pad:2},
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
{type:"txt", font:"6x8:2", label:/*LANG*/"RECORDING", bgCol:"#f00", pad:5, fillx:1},
]
},{btns:[ // Buttons...
{label:/*LANG*/"STOP", cb:()=>{
Bangle.removeListener('accel', accelHandler);
showMenu();
{ type: "h", c: [
{ type: "v", c: [
{type:"txt", font:"6x8", label:/*LANG*/"Samples", pad:2},
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
]},
{ type: "v", c: [
{type:"txt", font:"6x8", label:/*LANG*/"Time", pad:2},
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
]},
]},
{ type: "h", c: [
{ type: "v", c: [
{type:"txt", font:"6x8", label:/*LANG*/"Max X", pad:2},
{type:"txt", id:"maxX", font:"6x8", label:" - ", pad:5, bgCol:g.theme.bg},
]},
{ type: "v", c: [
{type:"txt", font:"6x8", label:/*LANG*/"Max Y", pad:2},
{type:"txt", id:"maxY", font:"6x8", label:" - ", pad:5, bgCol:g.theme.bg},
]},
{ type: "v", c: [
{type:"txt", font:"6x8", label:/*LANG*/"Max Z", pad:2},
{type:"txt", id:"maxZ", font:"6x8", label:" - ", pad:5, bgCol:g.theme.bg},
]},
]},
{type:"txt", font:"6x8", label:/*LANG*/"Max G", pad:2},
{type:"txt", id:"maxMag", font:"6x8:4", label:" - ", pad:5, bgCol:g.theme.bg},
{type:"txt", id:"state", font:"6x8:2", label:/*LANG*/"RECORDING", bgCol:"#f00", pad:5, fillx:1},
]},
{
btns:[ // Buttons...
{id: "btnStop", label:/*LANG*/"STOP", cb:()=>{
if (stopped) {
showMenu();
}
else {
Bangle.removeListener('accel', accelHandler);
layout.state.label = /*LANG*/"STOPPED";
layout.state.bgCol = /*LANG*/"#0f0";
stopped = true;
layout.render();
}
}}
]});
layout.render();
// now start writing
var f = require("Storage").open(getFileName(fileNumber), "w");
f.write("Time (ms),X,Y,Z\n");
f.write("Time (ms),X,Y,Z,Total\n");
var start = getTime();
var sampleCount = 0;
var maxMag = 0;
var maxX = 0;
var maxY = 0;
var maxZ = 0;
function accelHandler(accel) {
var t = getTime()-start;
f.write([
t*1000,
accel.x*8192,
accel.y*8192,
accel.z*8192].map(n=>Math.round(n)).join(",")+"\n");
if (logRawData) {
f.write([
t*1000,
accel.x*8192,
accel.y*8192,
accel.z*8192,
accel.mag*8192,
].map(n=>Math.round(n)).join(",")+"\n");
} else {
f.write([
Math.round(t*1000),
accel.x,
accel.y,
accel.z,
accel.mag,
].join(",")+"\n");
}
if (accel.mag > maxMag) {
maxMag = accel.mag.toFixed(2);
}
if (accel.x > maxX) {
maxX = accel.x.toFixed(2);
}
if (accel.y > maxY) {
maxY = accel.y.toFixed(2);
}
if (accel.z > maxZ) {
maxZ = accel.z.toFixed(2);
}
sampleCount++;
layout.samples.label = sampleCount;
layout.time.label = Math.round(t)+"s";
layout.render(layout.samples);
layout.render(layout.time);
layout.maxX.label = maxX;
layout.maxY.label = maxY;
layout.maxZ.label = maxZ;
layout.maxMag.label = maxMag;
layout.render();
}
Bangle.setPollInterval(80); // 12.5 Hz - the default

View File

@ -2,7 +2,7 @@
"id": "accellog",
"name": "Acceleration Logger",
"shortName": "Accel Log",
"version": "0.04",
"version": "0.05",
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
"icon": "app.png",
"tags": "outdoor",

View File

@ -6,3 +6,5 @@
0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night
0.07: Fix bug on the cutting edge firmware
0.08: Use default Bangle formatter for booleans
0.09: New app screen (instead of showing settings or the alert) and some optimisations
0.10: Add software back button via setUI

View File

@ -0,0 +1,37 @@
(function () {
// load variable before defining functions cause it can trigger a ReferenceError
const activityreminder = require("activityreminder");
const storage = require("Storage");
let activityreminder_data = activityreminder.loadData();
function run() {
E.showPrompt("Inactivity detected", {
title: "Activity reminder",
buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 }
}).then(function (v) {
if (v == 1) {
activityreminder_data.okDate = new Date();
}
if (v == 2) {
activityreminder_data.dismissDate = new Date();
}
if (v == 3) {
activityreminder_data.pauseDate = new Date();
}
activityreminder.saveData(activityreminder_data);
load();
});
// Obey system quiet mode:
if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
Bangle.buzz(400);
}
setTimeout(load, 20000);
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
run();
})();

View File

@ -1,46 +1,58 @@
(function () {
// load variable before defining functions cause it can trigger a ReferenceError
const activityreminder = require("activityreminder");
const storage = require("Storage");
const activityreminder_settings = activityreminder.loadSettings();
let activityreminder_data = activityreminder.loadData();
// load variable before defining functions cause it can trigger a ReferenceError
const activityreminder = require("activityreminder");
let activityreminder_data = activityreminder.loadData();
let W = g.getWidth();
// let H = g.getHeight();
function drawAlert() {
E.showPrompt("Inactivity detected", {
title: "Activity reminder",
buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 }
}).then(function (v) {
if (v == 1) {
activityreminder_data.okDate = new Date();
}
if (v == 2) {
activityreminder_data.dismissDate = new Date();
}
if (v == 3) {
activityreminder_data.pauseDate = new Date();
}
activityreminder.saveData(activityreminder_data);
load();
});
// Obey system quiet mode:
if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
Bangle.buzz(400);
}
setTimeout(load, 20000);
}
function run() {
if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) {
drawAlert();
} else {
eval(storage.read("activityreminder.settings.js"))(() => load());
}
}
function getHoursMins(date){
var h = date.getHours();
var m = date.getMinutes();
return (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
}
function drawData(name, value, y){
g.drawString(name, 10, y);
g.drawString(value, 100, y);
}
function drawInfo() {
var h=18, y = h;
g.setColor(g.theme.fg);
g.setFont("Vector",h).setFontAlign(-1,-1);
// Header
g.drawLine(0,25,W,25);
g.drawLine(0,26,W,26);
g.drawString("Current Cycle", 10, y+=h);
drawData("Start", getHoursMins(activityreminder_data.stepsDate), y+=h);
drawData("Steps", getCurrentSteps(), y+=h);
/*
g.drawString("Button Press", 10, y+=h*2);
drawData("Ok", getHoursMins(activityreminder_data.okDate), y+=h);
drawData("Dismiss", getHoursMins(activityreminder_data.dismissDate), y+=h);
drawData("Pause", getHoursMins(activityreminder_data.pauseDate), y+=h);
*/
}
function getCurrentSteps(){
let health = Bangle.getHealthStatus("day");
return health.steps - activityreminder_data.stepsOnDate;
}
function run() {
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
run();
})();
drawInfo();
Bangle.setUI({
mode : "custom",
back : load
})
}
run();
})();

View File

@ -1,70 +1,81 @@
(function () {
// load variable before defining functions cause it can trigger a ReferenceError
const activityreminder = require("activityreminder");
const activityreminder_settings = activityreminder.loadSettings();
let activityreminder_data = activityreminder.loadData();
if (activityreminder_data.firstLoad) {
activityreminder_data.firstLoad = false;
// load variable before defining functions cause it can trigger a ReferenceError
const activityreminder = require("activityreminder");
const activityreminder_settings = activityreminder.loadSettings();
let activityreminder_data = activityreminder.loadData();
if (activityreminder_data.firstLoad) {
activityreminder_data.firstLoad = false;
activityreminder.saveData(activityreminder_data);
}
function run() {
if (isNotWorn()) return;
let now = new Date();
let h = now.getHours();
if (isDuringAlertHours(h)) {
let health = Bangle.getHealthStatus("day");
if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed
|| health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch
activityreminder_data.stepsOnDate = health.steps;
activityreminder_data.stepsDate = now;
activityreminder.saveData(activityreminder_data);
}
function run() {
if (isNotWorn()) return;
let now = new Date();
let h = now.getHours();
if (isDuringAlertHours(h)) {
let health = Bangle.getHealthStatus("day");
if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed
|| health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch
activityreminder_data.stepsOnDate = health.steps;
activityreminder_data.stepsDate = now;
activityreminder.saveData(activityreminder_data);
/* todo in a futur release
Add settimer to trigger like 30 secs after going in this part cause the person have been walking
(pass some argument to run() to handle long walks and not triggering so often)
*/
}
if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) {
load('activityreminder.app.js');
}
}
}
function isNotWorn() {
return (Bangle.isCharging() || activityreminder_settings.tempThreshold >= E.getTemperature());
}
function isDuringAlertHours(h) {
if (activityreminder_settings.startHour < activityreminder_settings.endHour) { // not passing through midnight
return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour);
} else { // passing through midnight
return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour);
}
}
Bangle.on('midnight', function () {
/*
Usefull trick to have the app working smothly for people using it at night
*/
let now = new Date();
let h = now.getHours();
if (activityreminder_settings.enabled && isDuringAlertHours(h)) {
// updating only the steps and keeping the original stepsDate on purpose
activityreminder_data.stepsOnDate = 0;
activityreminder.saveData(activityreminder_data);
}
});
if (activityreminder_settings.enabled) {
setInterval(run, 60000);
/* todo in a futur release
increase setInterval time to something that is still sensible (5 mins ?)
when we added a settimer
Add settimer to trigger like 30 secs after going in this part cause the person have been walking
(pass some argument to run() to handle long walks and not triggering so often)
*/
}
if (mustAlert(now)) {
load('activityreminder.alert.js');
}
}
}
function isNotWorn() {
return (Bangle.isCharging() || activityreminder_settings.tempThreshold >= E.getTemperature());
}
function isDuringAlertHours(h) {
if (activityreminder_settings.startHour < activityreminder_settings.endHour) { // not passing through midnight
return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour);
} else { // passing through midnight
return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour);
}
}
function mustAlert(now) {
if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected
if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago
(now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago
(now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago
return true;
}
}
return false;
}
Bangle.on('midnight', function () {
/*
Usefull trick to have the app working smothly for people using it at night
*/
let now = new Date();
let h = now.getHours();
if (activityreminder_settings.enabled && isDuringAlertHours(h)) {
// updating only the steps and keeping the original stepsDate on purpose
activityreminder_data.stepsOnDate = 0;
activityreminder.saveData(activityreminder_data);
}
});
if (activityreminder_settings.enabled) {
setInterval(run, 60000);
/* todo in a futur release
increase setInterval time to something that is still sensible (5 mins ?)
when we added a settimer
*/
}
})();

View File

@ -1,56 +1,44 @@
exports.loadSettings = function () {
return Object.assign({
enabled: true,
startHour: 9,
endHour: 20,
maxInnactivityMin: 30,
dismissDelayMin: 15,
pauseDelayMin: 120,
minSteps: 50,
tempThreshold: 27
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
return Object.assign({
enabled: true,
startHour: 9,
endHour: 20,
maxInnactivityMin: 30,
dismissDelayMin: 15,
pauseDelayMin: 120,
minSteps: 50,
tempThreshold: 27
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
};
exports.writeSettings = function (settings) {
require("Storage").writeJSON("activityreminder.s.json", settings);
require("Storage").writeJSON("activityreminder.s.json", settings);
};
exports.saveData = function (data) {
require("Storage").writeJSON("activityreminder.data.json", data);
require("Storage").writeJSON("activityreminder.data.json", data);
};
exports.loadData = function () {
let health = Bangle.getHealthStatus("day");
let data = Object.assign({
firstLoad: true,
stepsDate: new Date(),
stepsOnDate: health.steps,
okDate: new Date(1970),
dismissDate: new Date(1970),
pauseDate: new Date(1970),
},
let health = Bangle.getHealthStatus("day");
let data = Object.assign({
firstLoad: true,
stepsDate: new Date(),
stepsOnDate: health.steps,
okDate: new Date(1970),
dismissDate: new Date(1970),
pauseDate: new Date(1970),
},
require("Storage").readJSON("activityreminder.data.json") || {});
if(typeof(data.stepsDate) == "string")
data.stepsDate = new Date(data.stepsDate);
if(typeof(data.okDate) == "string")
data.okDate = new Date(data.okDate);
if(typeof(data.dismissDate) == "string")
data.dismissDate = new Date(data.dismissDate);
if(typeof(data.pauseDate) == "string")
data.pauseDate = new Date(data.pauseDate);
if (typeof (data.stepsDate) == "string")
data.stepsDate = new Date(data.stepsDate);
if (typeof (data.okDate) == "string")
data.okDate = new Date(data.okDate);
if (typeof (data.dismissDate) == "string")
data.dismissDate = new Date(data.dismissDate);
if (typeof (data.pauseDate) == "string")
data.pauseDate = new Date(data.pauseDate);
return data;
return data;
};
exports.mustAlert = function(activityreminder_data, activityreminder_settings) {
let now = new Date();
if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected
if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago
(now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago
(now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago
return true;
}
}
return false;
}

View File

@ -3,7 +3,7 @@
"name": "Activity Reminder",
"shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
"version":"0.08",
"version":"0.10",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",
@ -13,11 +13,12 @@
{"name": "activityreminder.app.js", "url":"app.js"},
{"name": "activityreminder.boot.js", "url": "boot.js"},
{"name": "activityreminder.settings.js", "url": "settings.js"},
{"name": "activityreminder.alert.js", "url": "alert.js"},
{"name": "activityreminder", "url": "lib.js"},
{"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true}
],
"data": [
{"name": "activityreminder.s.json"},
{"name": "activityreminder.data.json"}
{"name": "activityreminder.data.json", "storageFile": true}
]
}

View File

@ -1,80 +1,86 @@
(function (back) {
// Load settings
const activityreminder = require("activityreminder");
let settings = activityreminder.loadSettings();
// Load settings
const activityreminder = require("activityreminder");
let settings = activityreminder.loadSettings();
// Show the menu
E.showMenu({
"": { "title": "Activity Reminder" },
"< Back": () => back(),
'Enable': {
value: settings.enabled,
onchange: v => {
settings.enabled = v;
activityreminder.writeSettings(settings);
}
},
'Start hour': {
value: settings.startHour,
min: 0, max: 24,
onchange: v => {
settings.startHour = v;
activityreminder.writeSettings(settings);
}
},
'End hour': {
value: settings.endHour,
min: 0, max: 24,
onchange: v => {
settings.endHour = v;
activityreminder.writeSettings(settings);
}
},
'Max inactivity': {
value: settings.maxInnactivityMin,
min: 15, max: 120,
onchange: v => {
settings.maxInnactivityMin = v;
activityreminder.writeSettings(settings);
},
format: x => x + "m"
},
'Dismiss delay': {
value: settings.dismissDelayMin,
min: 5, max: 60,
onchange: v => {
settings.dismissDelayMin = v;
activityreminder.writeSettings(settings);
},
format: x => x + "m"
},
'Pause delay': {
value: settings.pauseDelayMin,
min: 30, max: 240, step: 5,
onchange: v => {
settings.pauseDelayMin = v;
activityreminder.writeSettings(settings);
},
format: x => {
return x + "m";
}
},
'Min steps': {
value: settings.minSteps,
min: 10, max: 500, step: 10,
onchange: v => {
settings.minSteps = v;
activityreminder.writeSettings(settings);
}
},
'Temp Threshold': {
value: settings.tempThreshold,
min: 20, max: 40, step: 0.5,
format: v => v + "°C",
onchange: v => {
settings.tempThreshold = v;
activityreminder.writeSettings(settings);
}
function getMainMenu(){
var mainMenu = {
"": { "title": "Activity Reminder" },
"< Back": () => back(),
'Enable': {
value: settings.enabled,
onchange: v => {
settings.enabled = v;
activityreminder.writeSettings(settings);
}
});
},
'Start hour': {
value: settings.startHour,
min: 0, max: 24,
onchange: v => {
settings.startHour = v;
activityreminder.writeSettings(settings);
}
},
'End hour': {
value: settings.endHour,
min: 0, max: 24,
onchange: v => {
settings.endHour = v;
activityreminder.writeSettings(settings);
}
},
'Max inactivity': {
value: settings.maxInnactivityMin,
min: 15, max: 120,
onchange: v => {
settings.maxInnactivityMin = v;
activityreminder.writeSettings(settings);
},
format: x => x + "m"
},
'Dismiss delay': {
value: settings.dismissDelayMin,
min: 5, max: 60,
onchange: v => {
settings.dismissDelayMin = v;
activityreminder.writeSettings(settings);
},
format: x => x + "m"
},
'Pause delay': {
value: settings.pauseDelayMin,
min: 30, max: 240, step: 5,
onchange: v => {
settings.pauseDelayMin = v;
activityreminder.writeSettings(settings);
},
format: x => {
return x + "m";
}
},
'Min steps': {
value: settings.minSteps,
min: 10, max: 500, step: 10,
onchange: v => {
settings.minSteps = v;
activityreminder.writeSettings(settings);
}
},
'Temp Threshold': {
value: settings.tempThreshold,
min: 20, max: 40, step: 0.5,
format: v => v + "°C",
onchange: v => {
settings.tempThreshold = v;
activityreminder.writeSettings(settings);
}
}
};
return mainMenu;
}
// Show the menu
E.showMenu(getMainMenu());
})

View File

@ -1 +1,3 @@
0.01: AdvCasio first version
0.02: Remove un-needed fonts to improve memory usage
0.03: Tell clock widgets to hide.

View File

@ -1,7 +1,5 @@
const storage = require('Storage');
require("Font6x12").add(Graphics);
require("Font6x8").add(Graphics);
require("Font8x12").add(Graphics);
require("Font7x11Numeric7Seg").add(Graphics);
@ -257,7 +255,6 @@ function draw() {
g.setColor(0, 0, 0);
g.setFont("6x12");
if(dataJson && dataJson.weather) drawWeather(dataJson.weather);
if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks);
@ -297,9 +294,10 @@ Bangle.on("lock", (locked) => {
});
Bangle.setUI("clock");
// Load widgets, but don't show them
Bangle.loadWidgets();
Bangle.setUI("clock");
g.reset();
g.clear();

View File

@ -1,7 +1,7 @@
{ "id": "advcasio",
"name": "Advanced Casio Clock",
"shortName":"advcasio",
"version":"0.01",
"version":"0.03",
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
"icon": "app.png",
"tags": "clock",

View File

@ -1,2 +1,7 @@
0.01: Basic agenda with events from GB
0.02: Added settings page to force calendar sync
0.03: Disable past events display from settings
0.04: Added awareness of allDay field
0.05: Displaying calendar colour and name
0.06: Added clkinfo for clocks.
0.07: Clkinfo improvements.

View File

@ -1,3 +1,30 @@
# Agenda
Basic agenda reading the events synchronised from GadgetBridge
Basic agenda reading the events synchronised from GadgetBridge.
### Functionalities
* List all events in the next week (or whatever is synchronized)
* Optionally view past events (until GB removes them)
* Show start time and location of the events in the list
* Show the colour of the calendar in the list
* Display description, location and calendar name after tapping on events
### Troubleshooting
For the events sync to work, GadgetBridge needs to have the calendar permission and calendar sync should be enabled in the devices settings (gear sign in GB, also check the blacklisted calendars there, if events are missing).
Keep in mind that GadgetBridge won't synchronize all events on your calendar, just the ones in a time window of 7 days (you don't want your watch to explode), ideally every day old events get deleted since they appear out of such window.
#### Force Sync
If for any reason events still cannot sync or some are missing, you can try any of the following (just one, you normally don't need to do this):
1. from GB open the burger menu (side), tap debug and set time.
2. from the bangle, open settings > apps > agenda > Force calendar sync, then select not to delete the local events (this is equivalent to option 1).
3. do like option 2 but delete events, GB will synchronize a fresh database instead of patching the old one (good in case you somehow cannot get rid of older events)
After any of the options, you may need to disconnect/force close Gadgetbridge before reconnecting and let it sync (give it some time for that too), restart the agenda app on the bangle after a while to see the changes.
### Report a bug
You can easily open an issue in the espruino repo, but I won't be notified and it might take time.
If you want a (hopefully) quicker response, just report [on my fork](https://github.com/glemco/BangleApps).

View File

@ -0,0 +1,29 @@
(function() {
var agendaItems = {
name: "Agenda",
img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="),
items: []
};
var now = new Date();
var agenda = storage.readJSON("android.calendar.json")
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
.sort((a,b)=>a.timestamp - b.timestamp);
agenda.forEach((entry, i) => {
var title = entry.title.slice(0,18);
var date = new Date(entry.timestamp*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
agendaItems.items.push({
name: null,
get: () => ({ text: title + "\n" + dateStr, img: null}),
show: function() { agendaItems.items[i].emit("redraw"); },
hide: function () {}
});
});
return agendaItems;
})

View File

@ -6,6 +6,8 @@
title,
description,
location,
color:int,
calName,
allDay: bool,
}
*/
@ -24,19 +26,23 @@ var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
//FIXME maybe write the end from GB already? Not durationInSeconds here (or do while receiving?)
var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
var settings = require("Storage").readJSON("agenda.settings.json",true)||{};
CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp)
CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
function getDate(timestamp) {
return new Date(timestamp*1000);
}
function formatDateLong(date, includeDay) {
if(includeDay)
return Locale.date(date)+" "+Locale.time(date,1);
return Locale.time(date,1);
function formatDateLong(date, includeDay, allDay) {
let shortTime = Locale.time(date,1)+Locale.meridian(date);
if(allDay) shortTime = "";
if(includeDay || allDay)
return Locale.date(date)+" "+shortTime;
return shortTime;
}
function formatDateShort(date) {
return Locale.date(date).replace(/\d\d\d\d/,"")+Locale.time(date,1);
function formatDateShort(date, allDay) {
return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay?
"" : Locale.time(date,1)+Locale.meridian(date));
}
var lines = [];
@ -45,7 +51,7 @@ function showEvent(ev) {
if(!ev) return;
g.setFont(bodyFont);
//var lines = [];
if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10)
if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10);
var titleCnt = lines.length;
var start = getDate(ev.timestamp);
var end = getDate((+ev.timestamp) + (+ev.durationInSeconds));
@ -53,22 +59,24 @@ function showEvent(ev) {
if (titleCnt) lines.push(""); // add blank line after title
if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
includeDay = false;
if(includeDay) {
if(includeDay || ev.allDay) {
lines = lines.concat(
/*LANG*/"Start:",
g.wrapString(formatDateLong(start, includeDay), g.getWidth()-10),
g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10),
/*LANG*/"End:",
g.wrapString(formatDateLong(end, includeDay), g.getWidth()-10));
g.wrapString(formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10));
} else {
lines = lines.concat(
g.wrapString(Locale.date(start), g.getWidth()-10),
g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay), g.getWidth()-10),
g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay), g.getWidth()-10));
g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10),
g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10));
}
if(ev.location)
lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10));
if(ev.description)
lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10));
if(ev.calName)
lines = lines.concat(/*LANG*/"Calendar"+": ", g.wrapString(ev.calName, g.getWidth()-10));
lines = lines.concat(["",/*LANG*/"< Back"]);
E.showScroller({
h : g.getFontHeight(), // height of each menu item in pixels
@ -89,6 +97,12 @@ function showEvent(ev) {
}
function showList() {
//it might take time for GB to delete old events, decide whether to show them grayed out or hide entirely
if(!settings.pastEvents) {
let now = new Date();
//TODO add threshold here?
CALENDAR = CALENDAR.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000);
}
if(CALENDAR.length == 0) {
E.showMessage("No events");
return;
@ -101,24 +115,21 @@ function showList() {
g.setColor(g.theme.fg);
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
if (!ev) return;
var isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
var isPast = false;
var x = r.x+2, title = ev.title;
var body = formatDateShort(getDate(ev.timestamp))+"\n"+ev.location;
var m = ev.title+"\n"+ev.location, longBody=false;
var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
if (title) g.setFontAlign(-1,-1).setFont(fontBig)
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x,r.y+2);
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2);
if (body) {
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
var l = g.wrapString(body, r.w-(x+14));
if (l.length>3) {
l = l.slice(0,3);
l[l.length-1]+="...";
}
longBody = l.length>2;
g.drawString(l.join("\n"), x+10,r.y+20);
g.drawString(body, x+10,r.y+20);
}
//if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2);
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
if(ev.color) {
g.setColor("#"+(0x1000000+Number(ev.color)).toString(16).padStart(6,"0"));
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
}
},
select : idx => showEvent(CALENDAR[idx]),
back : () => load()

View File

@ -1,7 +1,7 @@
{
"id": "agenda",
"name": "Agenda",
"version": "0.02",
"version": "0.07",
"description": "Simple agenda",
"icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
@ -12,6 +12,8 @@
"storage": [
{"name":"agenda.app.js","url":"agenda.js"},
{"name":"agenda.settings.js","url":"settings.js"},
{"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"},
{"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
]
],
"data": [{"name":"agenda.settings.json"}]
}

View File

@ -3,6 +3,10 @@
Bluetooth.println("");
Bluetooth.println(JSON.stringify(message));
}
var settings = require("Storage").readJSON("agenda.settings.json",1)||{};
function updateSettings() {
require("Storage").writeJSON("agenda.settings.json", settings);
}
var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
var mainmenu = {
"" : { "title" : "Agenda" },
@ -32,6 +36,13 @@
E.showAlert(/*LANG*/"You are not connected").then(()=>E.showMenu(mainmenu));
}
},
/*LANG*/"Show past events" : {
value : !!settings.pastEvents,
onchange: v => {
settings.pastEvents = v;
updateSettings();
}
},
};
E.showMenu(mainmenu);
})

View File

@ -1 +1,4 @@
0.01: First, proof of concept
0.02: Load AGPS data on app start and automatically in background
0.03: Do not load AGPS data on boot
Increase minimum interval to 6 hours

View File

@ -1,18 +1,19 @@
# A-GPS Data
Load assisted GPS data directly to the watch using the new http requests on Android GadgetBridge.
Load assisted GPS (A-GPS) data directly to your Bangle.js using the new http requests on Android GadgetBridge.
Will download A-GPS data in background (if enabled in settings).
The GNSS type can be configured in the settings.
Make sure:
* your GadgetBridge version supports http requests
* turn on internet access in GadgetBridge settings
Currently proof of concept on Bangle2 only. Will eventually add a widget for automatic download.
![](screenshot.png)
![](screenshot2.png)
![](screenshot3.png)
![](screenshot4.png)
![](screenshot5.png)
Currently proof of concept on Bangle.js 2 only.
## Creator
[@pidajo](https://github.com/pidajo)
## Contributor
[@myxor](https://github.com/myxor)

View File

@ -1,125 +1,54 @@
var _GB = global.GB;
var counter = 0;
function GB(msg) {
console.log(msg);
if (msg.t == "http") {
display("Received", "(" + msg.resp.length + ") Touch to apply", () => {
display("Apply data..", "");
setTimeout(() => {
if (setAGPS(msg.resp)) {
display("Success", "Touch for restart", httpTest);
}
else {
display("Error", "Touch for restart", httpTest);
}
}, 1);
});
}
if (_GB) {
_GB(msg);
}
}
function setAGPS(data) {
var js = jsFromBase64(data);
console.log(js);
try {
eval(js);
return true;
}
catch(e) {
console.log("Error:", e);
}
return false;
}
function jsFromBase64(b64) {
var bin = atob(b64);
var chunkSize = 128;
var js = "Bangle.setGPSPower(1);\n"; // turn GPS on
var gnss_select="1";
js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
// What about:
// NAV-TIMEUTC (0x01 0x10)
// NAV-PV (0x01 0x03)
// or AGPS.zip uses AID-INI (0x0B 0x01)
for (var i=0;i<bin.length;i+=chunkSize) {
var chunk = bin.substr(i,chunkSize);
js += `Serial1.write(atob("${btoa(chunk)}"))\n`;
}
return js;
}
function CASIC_CHECKSUM(cmd) {
var cs = 0;
for (var i=1;i<cmd.length;i++)
cs = cs ^ cmd.charCodeAt(i);
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
}
function display(text1, text2, next) {
function display(text1, text2) {
g.reset();
g.clear();
var img = require("Storage").read("agpsdata.img");
if (img) {
g.drawImage(img, g.getWidth() - 48, g.getHeight()-48-24);
g.drawImage(img, g.getWidth() - 48, g.getHeight() - 48 - 24);
}
g.setFont("Vector", 30);
g.setFont("Vector", 18);
g.setFontAlign(0, 1);
g.drawString(text1, g.getWidth() / 2, g.getHeight() / 3 + 24);
g.setFont("Vector", 15);
g.setFontAlign(-1, -1);
g.drawString(text2, 0, g.getHeight() / 3 + 29);
if (text2 != undefined) {
g.setFont("Vector", 12);
g.setFontAlign(-1, -1);
g.drawString(text2, 5, g.getHeight() / 3 + 29);
}
Bangle.drawWidgets();
nextStep = null;
if (next) {
nextStep = next;
}
}
function httpTest() {
counter++;
display("AGPS Data", "Touch for start", () => {
display("Request...", "Touch for restart", httpTest);
if (Bluetooth.println) {
console.log("On device");
Bluetooth.println(JSON.stringify({t:"info", msg:"HTTP Request"}));
Bluetooth.println(JSON.stringify({t:"http", url:"https://www.espruino.com/agps/casic.base64"}));
}
else {
console.log("Testing on Emulator");
setTimeout(() => {
GB({t:"http", resp:testData});
}, 1);
}
});
}
var nextStep = null;
Bangle.on("touch", () => {
if (nextStep) {
nextStep();
}
});
httpTest();
// Show launcher when middle button pressed
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
let waiting = false;
/*
require("Storage").write("httptest.info",{
"id":"httptest",
"name":"Http Test",
"src":"httptest.js",
"icon":"wristlight.img"
});
*/
function start() {
g.reset();
g.clear();
waiting = false;
display("Retry?", "touch to retry");
Bangle.on("touch", () => { updateAgps(); });
}
var testData = "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";
function updateAgps() {
g.reset();
g.clear();
if (!waiting) {
waiting = true;
display("Updating A-GPS...");
require("agpsdata").pull(function() {
waiting = false;
display("A-GPS updated.", "touch to close");
Bangle.on("touch", () => { load(); });
},
function(error) {
waiting = false;
E.showAlert(error, "Error")
.then(() => { start(); });
});
} else {
display("Waiting...");
}
}
updateAgps();

26
apps/agpsdata/boot.js Normal file
View File

@ -0,0 +1,26 @@
(function() {
let waiting = false;
let settings = require("Storage").readJSON("agpsdata.settings.json", 1) || {
enabled: true,
refresh: 1440
};
if (settings.refresh == undefined) settings.refresh = 1440;
function successCallback(){
waiting = false;
}
function errorCallback(){
waiting = false;
}
if (settings.enabled) {
setInterval(() => {
if (!waiting && NRF.getSecurityStatus().connected){
waiting = true;
require("agpsdata").pull(successCallback, errorCallback);
}
}, settings.refresh * 1000 * 60);
}
})();

View File

@ -0,0 +1 @@
{"enabled":true,"refresh":1440,"gnsstype":1}

75
apps/agpsdata/lib.js Normal file
View File

@ -0,0 +1,75 @@
function readSettings() {
settings = Object.assign(
require('Storage').readJSON("agpsdata.default.json", true) || {},
require('Storage').readJSON(FILE, true) || {});
}
var FILE = "agpsdata.settings.json";
var settings;
readSettings();
function setAGPS(data) {
var js = jsFromBase64(data);
try {
eval(js);
return true;
}
catch(e) {
console.log("error:", e);
}
return false;
}
function jsFromBase64(b64) {
var bin = atob(b64);
var chunkSize = 128;
var js = "Bangle.setGPSPower(1);\n"; // turn GPS on
var gnsstype = settings.gnsstype || 1; // default GPS
js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode
// What about:
// NAV-TIMEUTC (0x01 0x10)
// NAV-PV (0x01 0x03)
// or AGPS.zip uses AID-INI (0x0B 0x01)
for (var i=0;i<bin.length;i+=chunkSize) {
var chunk = bin.substr(i,chunkSize);
js += `Serial1.write(atob("${btoa(chunk)}"))\n`;
}
return js;
}
function CASIC_CHECKSUM(cmd) {
var cs = 0;
for (var i=1;i<cmd.length;i++)
cs = cs ^ cmd.charCodeAt(i);
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
}
function updateLastUpdate() {
const file = "agpsdata.json";
let data = require("Storage").readJSON(file, 1) || {};
data.lastUpdate = Math.round(Date.now());
require("Storage").writeJSON(file, data);
}
exports.pull = function(successCallback, failureCallback) {
let uri = "https://www.espruino.com/agps/casic.base64";
if (Bangle.http){
Bangle.http(uri, {timeout:10000}).then(event => {
let result = setAGPS(event.resp);
if (result) {
updateLastUpdate();
if (successCallback) successCallback();
} else {
console.log("error applying AGPS data");
if (failureCallback) failureCallback("Error applying AGPS data");
}
}).catch((e)=>{
console.log("error", e);
if (failureCallback) failureCallback(e);
});
} else {
console.log("error: No http method found");
if (failureCallback) failureCallback(/*LANG*/"No http method");
}
};

View File

@ -1,16 +1,24 @@
{ "id": "agpsdata",
"name": "A-GPS Data",
"shortName":"AGPS Data",
"name": "A-GPS Data Downloader App",
"shortName":"A-GPS Data",
"icon": "agpsdata.png",
"version":"0.01",
"description": "Download assisted GPS data directly to watch",
"tags": "assisted,gps,agps,http",
"version":"0.03",
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"tags": "boot,tool,assisted,gps,agps,http",
"allow_emulator":true,
"supports": ["BANGLEJS2"],
"readme":"README.md",
"screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" }, { "url":"screenshot3.png" }, { "url":"screenshot4.png" }, { "url":"screenshot5.png" } ],
"screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ],
"storage": [
{"name":"agpsdata.app.js","url":"app.js"},
{"name":"agpsdata.img","url":"agpsdata-icon.js","evaluate":true}
{"name":"agpsdata.img","url":"agpsdata-icon.js","evaluate":true},
{"name":"agpsdata.default.json","url":"default.json"},
{"name":"agpsdata.boot.js","url":"boot.js"},
{"name":"agpsdata","url":"lib.js"},
{"name":"agpsdata.settings.js","url":"settings.js"}
],
"data": [
{"name": "agpsdata.json"},
{"name": "agpsdata.settings.json"}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

71
apps/agpsdata/settings.js Normal file
View File

@ -0,0 +1,71 @@
(function(back) {
function writeSettings(key, value) {
var s = Object.assign(
require('Storage').readJSON(settingsDefaultFile, true) || {},
require('Storage').readJSON(settingsFile, true) || {});
s[key] = value;
require('Storage').writeJSON(settingsFile, s);
readSettings();
}
function readSettings() {
settings = Object.assign(
require('Storage').readJSON(settingsDefaultFile, true) || {},
require('Storage').readJSON(settingsFile, true) || {});
}
var settingsFile = "agpsdata.settings.json";
var settingsDefaultFile = "agpsdata.default.json";
var settings;
readSettings();
const gnsstypes = [
"", "GPS", "BDS", "GPS+BDS", "GLONASS", "GPS+GLONASS", "BDS+GLONASS",
"GPS+BDS+GLON."
];
function buildMainMenu() {
var mainmenu = {
'' : {'title' : 'AGPS download'},
'< Back' : back,
"Enabled" : {
value : !!settings.enabled,
onchange : v => { writeSettings("enabled", v); }
},
"Refresh every" : {
value : settings.refresh / 60,
min : 6,
max : 168,
step : 1,
format : v => v + "h",
onchange : v => { writeSettings("refresh", Math.round(v * 60)); }
},
"GNSS type" : {
value : settings.gnsstype,
min : 1,
max : 7,
step : 1,
format : v => gnsstypes[v],
onchange : x => writeSettings('gnsstype', x)
},
"Force refresh" : () => {
E.showMessage("Loading A-GPS data");
require("agpsdata")
.pull(
function() {
E.showAlert("Success").then(
() => { E.showMenu(buildMainMenu()); });
},
function(error) {
E.showAlert(error, "Error")
.then(() => { E.showMenu(buildMainMenu()); });
});
}
};
return mainmenu;
}
E.showMenu(buildMainMenu());
});

3
apps/aiclock/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: New app!
0.02: Design improvements and fixes.
0.03: Indicate battery level through line occurrence.

23
apps/aiclock/README.md Normal file
View File

@ -0,0 +1,23 @@
# AI Clock
This clock was designed by stable diffusion ([paper](https://arxiv.org/abs/2112.10752)) using the following prompt:
`A rectangle banglejs watchface`
The original output of stable diffusion is shown here:
![](orig.png)
My implementation is shown below. Note that horizontal lines occur randomly, but the
probability is correlated with the battery level. So if your screen contains only
a few lines its time to charge your bangle again ;)
![](impl.png)
# Thanks to
The great open-source community: I used an open-source diffusion model (https://github.com/CompVis/stable-diffusion)
to generate a watch face for the open-source smartwatch BangleJs.
## Creator
- [David Peer](https://github.com/peerdavid).

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

@ -0,0 +1,225 @@
/**
* AI Clock
*/
require("Font7x11Numeric7Seg").add(Graphics);
Graphics.prototype.setFontGochiHand = function(scale) {
// Actual height 27 (29 - 3)
this.setFontCustom(
atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAA8AAAAADwAAAAAPAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAA/+AAAB//4AAH///gAH///gAAf//AAAB/+AAAAH8AAAAAAAAAAAAAAAAAAAAH8AAAAB/8AAAAP/4AAAB//wAAAPx/AAAB8B+AAAHgD4AAA+AHgAADwAeAAAPAB4AAA8AHgAAD4AeAAAPgB4AAAeAPgAAB8A8AAAH4HwAAAP/+AAAAf/wAAAA/+AAAAB/wAAAAB8AAAAAAAAAAADgAAAAAfAAAAAB4AAAAAPAAAAAB8AAAAAHgAAAAA8AAAAADwAAAAAf4AAAAB//8AAAD//4AAAH//gAAAD/+AAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AHgAAHgA+AAA/AD4AAD4AfgAAfAD+AAB4Af4AAHgD/gAAeAfeAAB4D54AAHw/HgAAf/4fAAA//B8AAD/4DwAAH+APAAAHgA8AAAAADwAAAAAOAAAAAAAAABgAAAAAPAAAAAB8AAAAAHwAYAAAeAD4AAD4APwAAPA4fgAA8Hw+AADwfB4AAPh4HwAA+HgPAAB/+A8AAH/4DwAAP/weAAAf/j4AAAc//gAAAB/8AAAAD/gAAAAD8AAAAAAAAAAAAAAAAAADAAAAAA+AAAAAP4AAAAB/wAAAAP/AAAAD+8AAAAfzwAAAf8HAAAB/gcAAAH/hwAAAf//gAAA//+AAAAf//gAAAP//gAAAD/+AAAAB/4AAAAH/AAAAAeAAAAAAgAAAAAAAAAAAAcAAAB8H8AAAP4f4AAA/x/wAAD/H/gAAf+A+AAB74B4AAHnwHgAAefAfAAB58A8AAHj4DwAAePgPAAB4fA8AAHh+HgAAeD8+AAB4P/4AAHgf/AAAeA/4AAAAA+AAAAAAAAAAAAAAAAAAHgAAAAD/wAAAA//gAAAH//AAAA//+AAAD4H8AAAfA/wAAB4D/AAAHgP+AAAeB54AAB4HngAAHweeAAAfB54AAA4HngAAAAeeAAAAB/4AAAAH/AAAAAP4AAAAAfAAADwAAAAAPAAAAAA8HgAAADweAAAAPB4AAAA8HgAAADweAAAAPh4AAAA+HgAAAB4eAAAAHx4AAAAf//8AAA///wAAD//+AAAH//4AAAAeAAAAAB4AAAAAHgAAAAAeAAAAAB4AAAAAHgAAAAAAAAAAAAAAAAAAD+AAAA+f+AAAH//8AAA///wAAH/4fgAAePgeAAB4+B4AAHj4HwAAePgPAAB4+A8AAHz4DwAAfngeAAA//B4AAD/+HgAAH//8AAAP//wAAAAf+AAAAA/wAAAAAYAAAAAAAAAAA/gAAAAH/AAAAA/8AAAAD34AAAAeHgAAAB4eAAAAHh4AAAA8HgAAADweAAAAPDwAAAA8PAAAADx4AAAAPvgAAAAf///AAB///8AAH///wAAP///AAA/wA4AABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOA4AAAA8DwAAADwPAAAAPA8AAAAYBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
46,
atob("DQoXEBQVExUUFRYUDQ=="),
40+(scale<<8)+(1<<16)
);
return this;
}
/*
* Set some important constants such as width, height and center
*/
var W = g.getWidth(),R=W/2;
var H = g.getHeight();
var cx = W/2;
var cy = H/2;
var drawTimeout;
/*
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/
*/
Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
angle = angle % 360;
var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
{x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
};
function drawBackground() {
g.setFontAlign(0,0);
g.setColor(g.theme.fg);
var bat = E.getBattery() / 100.0;
var y = 0;
while(y < H){
// Show less lines in case of small battery level.
if(Math.random() > bat){
y += 5;
continue;
}
y += 3 + Math.floor(Math.random() * 10);
g.drawLine(0, y, W, y);
g.drawLine(0, y+1, W, y+1);
g.drawLine(0, y+2, W, y+2);
y += 2;
}
}
function drawCircle(isLocked){
g.setColor(g.theme.fg);
g.fillCircle(cx, cy, 12);
var c = isLocked ? "#f00" : g.theme.bg;
g.setColor(c);
g.fillCircle(cx, cy, 6);
}
function toAngle(a){
if (a < 0){
return 360 + a;
}
if(a > 360) {
return 360 - a;
}
return a
}
function drawTime(){
var drawHourHand = g.drawRotRect.bind(g,8,12,R-38);
var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 );
g.setFontAlign(0,0);
// Compute angles
var date = new Date();
var m = parseInt(date.getMinutes() * 360 / 60);
var h = date.getHours();
h = h > 12 ? h-12 : h;
h += date.getMinutes()/60.0;
h = parseInt(h*360/12);
// Draw minute and hour bg
g.setColor(g.theme.bg);
drawHourHand(toAngle(h-3));
drawHourHand(toAngle(h+3));
drawMinuteHand(toAngle(m-2));
drawMinuteHand(toAngle(m+3));
// Draw minute and hour fg
g.setColor(g.theme.fg);
drawHourHand(h);
drawMinuteHand(m);
}
function drawDate(){
var date = new Date();
g.setFontAlign(0,0);
g.setFontGochiHand();
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
var w = g.stringWidth(text);
g.setColor(g.theme.bg);
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
g.setColor(g.theme.fg);
// Draw right line as designed by stable diffusion
g.drawLine(cx+w/2+5, 20, cx+w/2+5, 40+12);
g.drawLine(cx+w/2+6, 20, cx+w/2+6, 40+12);
g.drawLine(cx+w/2+7, 20, cx+w/2+7, 40+12);
// And finally the text
g.drawString(text, cx, 40);
}
function drawDigits(){
var date = new Date();
g.setFontAlign(0,0);
g.setFont("7x11Numeric7Seg",3);
var text = ("0"+date.getHours()).substr(-2) + ":" + ("0"+date.getMinutes()).substr(-2); //Bangle.getHealthStatus("day").steps;
var w = g.stringWidth(text);
g.setColor(g.theme.bg);
g.fillRect(cx-w/2-4, 120, cx+w/2+4, 140+20);
// Draw right line as designed by stable diffusion
g.setColor(g.theme.fg);
g.drawLine(cx+w/2+5, 120, cx+w/2+5, 140+20);
g.drawLine(cx+w/2+6, 120, cx+w/2+6, 140+20);
g.drawLine(cx+w/2+7, 120, cx+w/2+7, 140+20);
// And the 7set text
g.setColor("#BBB");
g.drawString("88:88", cx, 140);
g.drawString("88:88", cx+1, 140);
g.drawString("88:88", cx, 141);
g.setColor(g.theme.fg);
g.drawString(text, cx, 140);
g.drawString(text, cx+1, 140);
g.drawString(text, cx, 141);
}
function draw(){
// Queue draw in one minute
queueDraw();
g.reset();
g.clearRect(0, 0, g.getWidth(), g.getHeight());
g.setColor(1,1,1);
drawBackground();
drawDate();
drawDigits();
drawTime();
drawCircle(Bangle.isLocked());
}
/*
* Listeners
*/
Bangle.on('lcdPower',on=>{
if (on) {
draw(true);
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
Bangle.on('lock', function(isLocked) {
drawCircle(isLocked);
});
/*
* Some helpers
*/
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
/*
* Lets start widgets, listen for btn etc.
*/
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();
/*
* 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.
*/
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
// Clear the screen once, at startup and draw clock
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
draw();
// After drawing the watch face, we can draw the widgets
// Bangle.drawWidgets();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgP/ACfAEZU/ECZELIKhSR/+PAoWAv4FDhk/x/ggP+j0fx/AgP8n8PCIX8CwIFC/F/w4FBgP4gEHC4QFE//w//DC4QFB8YFC+P/8IdCAoYdBAoPxDoQAd+CiKh4dQwDhfAA4A="))

BIN
apps/aiclock/aiclock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
apps/aiclock/impl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,20 @@
{
"id": "aiclock",
"name": "AI Clock",
"shortName":"AI Clock",
"icon": "aiclock.png",
"version":"0.03",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
"type": "clock",
"tags": "clock",
"screenshots": [
{"url":"orig.png"},
{"url":"impl.png"}
],
"storage": [
{"name":"aiclock.app.js","url":"aiclock.app.js"},
{"name":"aiclock.img","url":"aiclock.icon.js","evaluate":true}
]
}

BIN
apps/aiclock/orig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

2
apps/alpinenav/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Added adjustment for Bangle.js magnetometer heading fix

View File

@ -224,7 +224,7 @@ Bangle.on('mag', function (m) {
if (isNaN(m.heading))
compass_heading = "---";
else
compass_heading = 360 - Math.round(m.heading);
compass_heading = Math.round(m.heading);
current_colour = g.getColor();
g.reset();
g.setColor(background_colour);

View File

@ -1,7 +1,7 @@
{
"id": "alpinenav",
"name": "Alpine Nav",
"version": "0.01",
"version": "0.02",
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
"icon": "app-icon.png",
"tags": "outdoors,gps",

View File

@ -11,4 +11,6 @@
0.10: Fix SMS bug
0.12: Use default Bangle formatter for booleans
0.13: Added Bangle.http function (see Readme file for more info)
0.14: Fix timeout of http function not beeing cleaned up
0.14: Fix timeout of http function not being cleaned up
0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)

View File

@ -139,6 +139,8 @@
// options = {id,timeout,xpath}
Bangle.http = (url,options)=>{
options = options||{};
if (!NRF.getSecurityStatus().connected)
return Promise.reject("Not connected to Bluetooth");
if (Bangle.httpRequest === undefined)
Bangle.httpRequest={};
if (options.id === undefined) {
@ -150,6 +152,9 @@
//send the request
var req = {t: "http", url:url, id:options.id};
if (options.xpath) req.xpath = options.xpath;
if (options.method) req.method = options.method;
if (options.body) req.body = options.body;
if (options.headers) req.headers = options.headers;
gbSend(req);
//create the promise
var promise = new Promise(function(resolve,reject) {

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.14",
"version": "0.16",
"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,gadgetbridge",

View File

@ -1,3 +1,5 @@
0.01: New App!
0.02: Fix bug if image clock wasn't installed
0.03: Update to use setUI
0.04: Tell clock widgets to hide. Move loadWidgets() so it only runs on
startup and not on every draw.

View File

@ -87,7 +87,6 @@ if (g.drawImages) {
draw();
var secondInterval = setInterval(draw,100);
// load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// Stop when LCD goes off
Bangle.on('lcdPower',on=>{
@ -104,3 +103,5 @@ if (g.drawImages) {
}
// Show launcher when button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();

View File

@ -2,7 +2,7 @@
"id": "animclk",
"name": "Animated Clock",
"shortName": "Anim Clock",
"version": "0.03",
"version": "0.04",
"description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art",
"icon": "app.png",
"type": "clock",

View File

@ -10,4 +10,7 @@
week is buffered until date or timezone changes
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
0.08: fixed calendar weeknumber not shortened to two digits
0.09: Use default Bangle formatter for booleans
0.09: Use default Bangle formatter for booleans
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
0.11: Moved enhanced Anton clock to 'Anton Clock Plus' and stripped this clock back down to make it faster for new users (270ms -> 170ms)
Modified to avoid leaving functions defined when using setUI({remove:...})

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,9 +1,8 @@
{
"id": "antonclk",
"name": "Anton Clock",
"version": "0.09",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"version": "0.11",
"description": "A simple clock using the bold Anton font. See `Anton Clock Plus` for an enhanced version",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
@ -12,8 +11,6 @@
"allow_emulator": true,
"storage": [
{"name":"antonclk.app.js","url":"app.js"},
{"name":"antonclk.settings.js","url":"settings.js"},
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"antonclk.json"}]
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,15 @@
0.01: New App!
0.02: Load widgets after setUI so widclk knows when to hide
0.03: Clock now shows day of week under date.
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
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
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
0.08: fixed calendar weeknumber not shortened to two digits
0.09: Use default Bangle formatter for booleans
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
Modified to avoid leaving functions defined when using setUI({remove:...})

View File

@ -1,6 +1,6 @@
# Anton Clock - Large font digital watch with seconds and date
# Anton Clock Plus - Large font digital watch with seconds and date
Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
Anton Clock Plus uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
## Features
@ -16,16 +16,16 @@ The basic time representation only shows hours and minutes of the current time.
## Usage
Install Anton clock through the Bangle.js app loader.
Configure it through the default Bangle.js configuration mechanism
* Install Anton Clock Plus through the Bangle.js app loader.
* Configure it through the default Bangle.js configuration mechanism
(Settings app, "Apps" menu, "Anton clock" submenu).
If you like it, make it your default watch face
* If you like it, make it your default watch face
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
## Configuration
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu.
Anton Clock is configured by the standard settings mechanism of Bangle.js's operating system:
Open the `Settings` app, then the `Apps` submenu and below it the `Anton Clock+` menu.
You configure Anton clock through several "on/off" switches in two menus.
### The main menu

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgf/AH4At/l/Aofgh4DB+EAj4REQoM/AgP4AoeACIoLCg4FB4AFDCIwLCgAROgYIB8EBAoUH/gVBCIxQBCKYHBCJp9DI4ICBLJYRCn4RQEYMOR5ARDIgIRMYQZZBgARGZwZBDCKQrCgEDR5AdBUIQRJDoLXFCJD7J/xrICIQFCn4RH/4LDAoTaCCI4Ar/LLDCBfypMkCgMkyV/CJOSCIOf5IRGFwOfCJNP//JnmT588z/+pM/BYIRCk4RC/88+f/n4RCngRCz1JCIf5/nzGoQRIHwXPCIPJI4f8CJHJGQJKCCI59LCI5ZCCJ/+v/kBoM/+V/HIJrHBYJWB/JKB5x9JEYP8AQKdBpwRL841Dp41KZoTxBHYTXBWY77PCKKhJ/4/CcgMkXoQAiA="))

238
apps/antonclkplus/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/antonclkplus/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,20 @@
{
"id": "antonclkplus",
"name": "Anton Clock Plus",
"shortName": "Anton Clock+",
"version": "0.10",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"antonclkplus.app.js","url":"app.js"},
{"name":"antonclkplus.settings.js","url":"settings.js"},
{"name":"antonclkplus.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"antonclkplus.json"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,11 +1,12 @@
{
"id": "assistedgps",
"name": "Assisted GPS Update (AGPS)",
"name": "Assisted GPS Updater (AGPS)",
"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.",
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
"sortorder": -1,
"icon": "app.png",
"type": "RAM",
"tags": "tool,outdoors,agps",
"tags": "tool,outdoors,agps,gps,a-gps",
"supports": ["BANGLEJS","BANGLEJS2"],
"custom": "custom.html",
"customConnect": true,

View File

@ -1,3 +1,5 @@
0.01: Create astral clock app
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
0.03: Update to use Bangle.setUI instead of setWatch
0.04: Tell clock widgets to hide.
0.05: Added adjustment for Bangle.js magnetometer heading fix

View File

@ -767,6 +767,24 @@ function draw() {
g.clear();
current_moonphase = getMoonPhase();
Bangle.setUI("clockupdown", btn => {
if (btn==0) {
if (!processing) {
if (!modeswitch) {
modeswitch = true;
if (mode == "planetary") mode = "extras";
else mode = "planetary";
}
else
modeswitch = false;
}
} else {
if (!processing)
ready_to_compute = true;
}
});
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -799,23 +817,6 @@ Bangle.setGPSPower(1);
// Show launcher when button pressed
Bangle.setClockMode();
Bangle.setUI("clockupdown", btn => {
if (btn==0) {
if (!processing) {
if (!modeswitch) {
modeswitch = true;
if (mode == "planetary") mode = "extras";
else mode = "planetary";
}
else
modeswitch = false;
}
} else {
if (!processing)
ready_to_compute = true;
}
});
setWatch(function () {
if (!astral_settings.astral_default) {
colours_switched = true;
@ -833,7 +834,7 @@ Bangle.on('mag', function (m) {
if (isNaN(m.heading))
compass_heading = "---";
else
compass_heading = 360 - Math.round(m.heading);
compass_heading = Math.round(m.heading);
// g.setColor("#000000");
// g.fillRect(160, 10, 160, 20);
g.setColor(display_colour);

View File

@ -1,7 +1,7 @@
{
"id": "astral",
"name": "Astral Clock",
"version": "0.03",
"version": "0.05",
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
"icon": "app-icon.png",
"type": "clock",

View File

@ -2,3 +2,4 @@
0.02: Add sit ups
Add more feedback to the user about the exercises
Clean up code
0.03: Add software back button on main menu

View File

@ -71,7 +71,8 @@ function showMainMenu() {
let menu;
menu = {
"": {
title: "BanglExercise"
title: "BanglExercise",
back: load
}
};
@ -381,4 +382,5 @@ Bangle.on('HRM', function(hrm) {
});
g.clear(1);
Bangle.loadWidgets();
showMainMenu();

View File

@ -1,7 +1,7 @@
{ "id": "banglexercise",
"name": "BanglExercise",
"shortName":"BanglExercise",
"version":"0.02",
"version":"0.03",
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -12,3 +12,4 @@
0.12: Add settings to hide date,widgets
0.13: Add font setting
0.14: Use ClockFace_menu.addItems
0.15: Add Power saving option

View File

@ -7,4 +7,5 @@ A simple digital clock showing seconds as a horizontal bar.
## Settings
* `Show date`: display date at the bottom of screen
* `Font`: choose between bitmap or vector fonts
* `Font`: choose between bitmap or vector fonts
* `Power saving`: (Bangle.js 2 only) don't draw the seconds bar while the watch is locked

View File

@ -13,16 +13,20 @@ let locale = require("locale");
locale.hasMeridian = (locale.meridian(date)!=="");
}
let barW = 0, prevX = 0;
function renderBar(l) {
if (!this.fraction) {
// zero-size fillRect stills draws one line of pixels, we don't want that
return;
}
const width = this.fraction*l.w;
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
"ram";
if (l) prevX = 0; // called from Layout: drawing area was cleared
else l = clock.layout.bar;
let x2 = l.x+barW;
if (clock.powerSave && Bangle.isLocked()) x2 = 0; // hide bar
if (x2===prevX) return; // nothing to do
if (x2===0) x2--; // don't leave 1px line
if (x2<Math.max(0, prevX)) g.setBgColor(l.bgCol || g.theme.bg).clearRect(x2+1, l.y, prevX, l.y2);
else g.setColor(l.col || g.theme.fg).fillRect(prevX+1, l.y, x2, l.y2);
prevX = x2;
}
function timeText(date) {
if (!clock.is12Hour) {
return locale.time(date, true);
@ -47,22 +51,21 @@ function dateText(date) {
return `${dayName} ${dayMonth}`;
}
const ClockFace = require("ClockFace"),
clock = new ClockFace({
precision:1,
settingsFile:'barclock.settings.json',
precision: 1,
settingsFile: "barclock.settings.json",
init: function() {
const Layout = require("Layout");
this.layout = new Layout({
type: "v", c: [
{
type: "h", c: [
{id: "time", label: "88:88", type: "txt", font: "6x8:5", col:g.theme.fg, bgCol: g.theme.bg}, // updated below
{id: "ampm", label: " ", type: "txt", font: "6x8:2", col:g.theme.fg, bgCol: g.theme.bg},
{id: "time", label: "88:88", type: "txt", font: "6x8:5", col: g.theme.fg, bgCol: g.theme.bg}, // updated below
{id: "ampm", label: " ", type: "txt", font: "6x8:2", col: g.theme.fg, bgCol: g.theme.bg},
],
},
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
{id: "bar", type: "custom", fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
this.showDate ? {height: 40} : {},
this.showDate ? {id: "date", type: "txt", font: "10%", valign: 1} : {},
],
@ -76,7 +79,8 @@ const ClockFace = require("ClockFace"),
this.layout.ampm.label = "";
thickness = Math.floor(Bangle.appRect.w/(5*6));
}
this.layout.bar.height = thickness+1;
let bar = this.layout.bar;
bar.height = thickness+1;
if (this.font===1) { // vector
const B2 = process.env.HWVERSION>1;
if (this.is12Hour && locale.hasMeridian) {
@ -89,17 +93,32 @@ const ClockFace = require("ClockFace"),
this.layout.time.font = "6x8:"+thickness;
}
this.layout.update();
bar.y2 = bar.y+bar.height-1;
},
update: function(date, c) {
"ram";
if (c.m) this.layout.time.label = timeText(date);
if (c.h) this.layout.ampm.label = ampmText(date);
if (c.d && this.showDate) this.layout.date.label = dateText(date);
const SECONDS_PER_MINUTE = 60;
if (c.s) this.layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
this.layout.render();
if (c.m) this.layout.render();
if (c.s) {
barW = Math.round(date.getSeconds()/60*this.layout.bar.w);
renderBar();
}
},
resume: function() {
prevX = 0; // force redraw of bar
this.layout.forgetLazyState();
},
});
// power saving: only update once a minute while locked, hide bar
if (clock.powerSave) {
Bangle.on("lock", lock => {
clock.precision = lock ? 60 : 1;
clock.tick();
renderBar(); // hide/redraw bar right away
});
}
clock.start();

View File

@ -1,7 +1,7 @@
{
"id": "barclock",
"name": "Bar Clock",
"version": "0.14",
"version": "0.15",
"description": "A simple digital clock showing seconds as a bar",
"icon": "clock-bar.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],

View File

@ -17,10 +17,14 @@
onchange: v => save("font", v),
},
};
require("ClockFace_menu").addItems(menu, save, {
let items = {
showDate: s.showDate,
loadWidgets: s.loadWidgets,
});
};
// Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway)
if (process.env.HWVERSION>1) {
items.powerSave = s.powerSave;
}
require("ClockFace_menu").addItems(menu, save, items);
E.showMenu(menu);
});

View File

@ -7,3 +7,4 @@
0.07: Step count resets at midnight
0.08: Step count stored in memory to survive reloads. Now shows step count daily and since last reboot.
0.09: NOW it really should reset daily (instead of every other day...)
0.10: Tell clock widgets to hide.

View File

@ -416,13 +416,13 @@ var layout = new Layout( {
// Clear the screen once, at startup
g.clear();
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setUI("clock");
layout.render();
Bangle.on('lock', function(locked) {
if(!locked) {
layout.render();
}
});
});

View File

@ -2,7 +2,7 @@
"name": "Barcode clock",
"shortName":"Barcode clock",
"icon": "barcode.icon.png",
"version":"0.09",
"version":"0.10",
"description": "EAN-8 compatible barcode clock.",
"tags": "barcode,ean,ean-8,watchface,clock,clockface",
"type": "clock",

View File

@ -1,3 +1,4 @@
0.01: Display pressure as number and hand
0.02: Use theme color
0.03: workaround for some firmwares that return 'undefined' for first call to barometer
0.04: Update every second, go back with short button press

View File

@ -59,6 +59,7 @@ function drawTicks(){
function drawScaleLabels(){
g.setColor(g.theme.fg);
g.setFont("Vector",12);
g.setFontAlign(-1,-1);
let label = MIN;
for (let i=0;i <= NUMBER_OF_LABELS; i++){
@ -103,22 +104,29 @@ function drawIcons() {
}
g.setBgColor(g.theme.bg);
g.clear();
drawTicks();
drawScaleLabels();
drawIcons();
try {
function baroHandler(data) {
if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429
setTimeout(() => Bangle.getPressure().then(baroHandler), 500);
else
g.clear();
drawTicks();
drawScaleLabels();
drawIcons();
if (data!==undefined) {
drawHand(Math.round(data.pressure));
}
}
Bangle.getPressure().then(baroHandler);
setInterval(() => Bangle.getPressure().then(baroHandler), 1000);
} catch(e) {
print(e.message);
print("barometer not supporter, show a demo value");
if (e !== undefined) {
print(e.message);
}
print("barometer not supported, show a demo value");
drawHand(MIN);
}
Bangle.setUI({
mode : "custom",
back : function() {load();}
});

View File

@ -1,7 +1,7 @@
{ "id": "barometer",
"name": "Barometer",
"shortName":"Barometer",
"version":"0.03",
"version":"0.04",
"description": "A simple barometer that displays the current air pressure",
"icon": "barometer.png",
"tags": "tool,outdoors",

View File

@ -1,2 +1,3 @@
0.01: App Created!
0.02: Update to use Bangle.setUI instead of setWatch
0.03: Tell clock widgets to hide.

View File

@ -249,6 +249,9 @@ g.clear();
g.setColor(0, 0.5, 0).drawImage(bg_crack);
g.setColor(1, 1, 1).drawImage(batman);
// Show launcher when button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -256,5 +259,3 @@ Bangle.drawWidgets();
timeInterval = setInterval(showTime, 1000);
showTime();
// Show launcher when button pressed
Bangle.setUI("clock");

Some files were not shown because too many files have changed in this diff Show More