Merge branch 'espruino:master' into master
|
@ -514,7 +514,6 @@ The [`testing`](testing) folder contains snippets of code that might be useful f
|
|||
|
||||
* `testing/colors.js` - 16 bit colors as name value pairs
|
||||
* `testing/gpstrack.js` - code to store a GPS track in Bangle.js storage and output it back to the console
|
||||
* `testing/map` - code for splitting an image into map tiles and then displaying them
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Faster maze generation
|
||||
0.03: Avoid clearing bottom widgets
|
||||
|
|
|
@ -11,13 +11,10 @@ function Maze(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();
|
||||
// This voodoo is needed because otherwise
|
||||
// bottom line widgets (like digital clock)
|
||||
// disappear during maze generation
|
||||
Bangle.drawWidgets();
|
||||
g.setColor(g.theme.fg);
|
||||
for (let i=0; i<=n; i++) {
|
||||
g.drawRect(
|
||||
|
@ -66,7 +63,7 @@ function Maze(n) {
|
|||
if (Math.random()<0.5 && candidates_down.length || !candidates_right.length) {
|
||||
trying_down = true;
|
||||
}
|
||||
let candidates = trying_down ? candidates_down : candidates_right;
|
||||
let candidates = trying_down ? candidates_down : candidates_right,
|
||||
candidate_index = Math.floor(Math.random()*candidates.length),
|
||||
cell = candidates.splice(candidate_index, 1)[0],
|
||||
r = Math.floor(cell/n),
|
||||
|
@ -105,11 +102,6 @@ function Maze(n) {
|
|||
}
|
||||
}
|
||||
}
|
||||
this.clearScreen = function() {
|
||||
g.clearRect(
|
||||
0, MARGIN, g.getWidth(), g.getHeight()-MARGIN-1
|
||||
);
|
||||
};
|
||||
this.clearCell = function(r, c) {
|
||||
if (!r && !c) {
|
||||
g.setColor("#ffff00");
|
||||
|
@ -263,7 +255,7 @@ let mazeMenu = {
|
|||
"< Exit": function() { setTimeout(load, 100); } // timeout voodoo prevents deadlock
|
||||
};
|
||||
|
||||
g.clear(true);
|
||||
g.reset();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLocked(false);
|
||||
|
@ -289,7 +281,7 @@ let maze_interval = setInterval(
|
|||
duration = Date.now()-start_time;
|
||||
g.setFontAlign(0,0).setColor(g.theme.fg);
|
||||
g.setFont("Vector",18);
|
||||
g.drawString(`Solved ${maze.n}X${maze.n} in\n ${timeToText(duration)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true);
|
||||
g.drawString(`Solved ${maze.n}X${maze.n} in\n ${timeToText(duration)} \nBtn1 to play again`, g.getWidth()/2, g.getHeight()/2, true);
|
||||
}
|
||||
}
|
||||
}, 25);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "acmaze",
|
||||
"name": "AccelaMaze",
|
||||
"shortName":"AccelaMaze",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Tilt the watch to roll a ball through a maze.",
|
||||
"icon": "app.png",
|
||||
"tags": "game",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(() => {
|
||||
(() => {
|
||||
BANGLEJS2 = process.env.HWVERSION==2;
|
||||
Bangle.setLCDTimeout(0);
|
||||
let intervalID;
|
||||
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
||||
|
@ -6,7 +7,9 @@
|
|||
// density, elasticity of bounces, "drag coefficient"
|
||||
const rho = 100, e = 0.3, C = 0.01;
|
||||
// screen width & height in pixels
|
||||
const sW = 240, sH = 160;
|
||||
const sW = g.getWidth();
|
||||
const sH = g.getHeight()*2/3;
|
||||
const bgColour ="#f00"; // only for Bangle.js 2
|
||||
// gravity constant (lowercase was already taken)
|
||||
const G = 9.80665;
|
||||
|
||||
|
@ -17,14 +20,16 @@
|
|||
// The play area is 240x160, sizes are the ball radius, so we can use common
|
||||
// denominators of 120x80 to get square rooms
|
||||
// Reverse the order to show the easiest on top of the menu
|
||||
const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(),
|
||||
// even size 1 actually works, but larger mazes take forever to generate
|
||||
minSize = 4, defaultSize = 10;
|
||||
const sizeNames = {
|
||||
1: "Insane", 2: "Gigantic", 4: "Enormous", 5: "Huge", 8: "Large",
|
||||
10: "Medium", 16: "Small", 20: "Tiny", 40: "Trivial",
|
||||
};
|
||||
|
||||
// even size 1 actually works, but larger mazes take forever to generate
|
||||
if (!BANGLEJS2) {
|
||||
const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(), minSize = 4, defaultSize = 10;
|
||||
} else {
|
||||
const sizes = [1, 2, 4, 5, 8, 10, 16, 20 ].reverse(), minSize = 4, defaultSize = 10;
|
||||
}
|
||||
/**
|
||||
* Draw something to all screen buffers
|
||||
* @param draw {function} Callback which performs the drawing
|
||||
|
@ -45,17 +50,17 @@
|
|||
|
||||
// use unbuffered graphics for UI stuff
|
||||
function showMessage(message, title) {
|
||||
Bangle.setLCDMode();
|
||||
if (!BANGLEJS2) Bangle.setLCDMode();
|
||||
return E.showMessage(message, title);
|
||||
}
|
||||
|
||||
function showPrompt(prompt, options) {
|
||||
Bangle.setLCDMode();
|
||||
if (!BANGLEJS2) Bangle.setLCDMode();
|
||||
return E.showPrompt(prompt, options);
|
||||
}
|
||||
|
||||
function showMenu(menu) {
|
||||
Bangle.setLCDMode();
|
||||
if (!BANGLEJS2) Bangle.setLCDMode();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
@ -105,7 +110,7 @@
|
|||
generateMaze(); // this shows unbuffered progress messages
|
||||
if (settings.cheat && r>1) findRoute(); // not enough memory for r==1 :-(
|
||||
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered");
|
||||
clearAll();
|
||||
drawAll(drawMaze);
|
||||
intervalID = setInterval(tick, 100);
|
||||
|
@ -307,6 +312,7 @@
|
|||
const range = {top: 0, left: 0, bottom: rows, right: cols};
|
||||
const w = sW/cols, h = sH/rows;
|
||||
g.clear();
|
||||
if (BANGLEJS2) g.setBgColor(bgColour);
|
||||
g.setColor(0.76, 0.60, 0.42);
|
||||
for(let row = range.top; row<=range.bottom; row++) {
|
||||
for(let col = range.left; col<=range.right; col++) {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial version of Balltastic released! Happy!
|
||||
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||
0.03: Now also works on Bangle.js 2
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
BANGLEJS2 = process.env.HWVERSION==2;
|
||||
Bangle.setLCDBrightness(1);
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered");
|
||||
Bangle.setLCDTimeout(0);
|
||||
|
||||
let points = 0;
|
||||
let level = 1;
|
||||
let levelSpeedStart = 0.8;
|
||||
let nextLevelPoints = 20;
|
||||
let nextLevelPoints = 10;
|
||||
let levelSpeedFactor = 0.2;
|
||||
let counterWidth = 10;
|
||||
let gWidth = g.getWidth() - counterWidth;
|
||||
|
@ -81,12 +82,23 @@ function drawLevelText() {
|
|||
g.setColor("#26b6c7");
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("4x6", 5);
|
||||
g.drawString("Level " + level, 120, 80);
|
||||
g.drawString("Level " + level, g.getWidth()/2, g.getHeight()/2);
|
||||
}
|
||||
|
||||
function drawPointsText() {
|
||||
g.setColor("#26b6c7");
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("4x6", 2);
|
||||
g.drawString("Points " + points, g.getWidth()/2, g.getHeight()-20);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
//bg
|
||||
g.setColor("#71c6cf");
|
||||
if (!BANGLEJS2) {
|
||||
g.setColor("#71c6cf");
|
||||
} else {
|
||||
g.setColor("#002000");
|
||||
}
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
//counter
|
||||
|
@ -94,6 +106,7 @@ function draw() {
|
|||
|
||||
//draw level
|
||||
drawLevelText();
|
||||
drawPointsText();
|
||||
|
||||
//dot
|
||||
g.setColor("#ff0000");
|
||||
|
@ -152,7 +165,7 @@ function count() {
|
|||
if (counter <= 0) {
|
||||
running = false;
|
||||
clearInterval(drawInterval);
|
||||
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50);
|
||||
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Game over!");},50);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"id": "balltastic",
|
||||
"name": "Balltastic",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Simple but fun ball eats dots game.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"bangle2-balltastic-screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "game,fun",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"balltastic.app.js","url":"app.js"},
|
||||
{"name":"balltastic.img","url":"app-icon.js","evaluate":true}
|
||||
|
|
|
@ -5,3 +5,5 @@
|
|||
0.03: Prevent readings from internal sensor mixing into BT values
|
||||
Mark events with src property
|
||||
Show actual source of event in app
|
||||
0.04: Automatically reconnect BT sensor
|
||||
App buzzes if no BTHRM events for more than 3 seconds
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
(function() {
|
||||
var log = function() {};//print
|
||||
//var sf = require("Storage").open("bthrm.log","a");
|
||||
var log = function(text, param){
|
||||
/*var logline = Date.now().toFixed(3) + " - " + text;
|
||||
if (param){
|
||||
logline += " " + JSON.stringify(param);
|
||||
}
|
||||
sf.write(logline + "\n");
|
||||
print(logline);*/
|
||||
}
|
||||
|
||||
log("Start");
|
||||
|
||||
var blockInit = false;
|
||||
var gatt;
|
||||
var status;
|
||||
var currentRetryTimeout;
|
||||
var initialRetryTime = 40;
|
||||
var maxRetryTime = 60000;
|
||||
var retryTime = initialRetryTime;
|
||||
|
||||
var origIsHRMOn = Bangle.isHRMOn;
|
||||
|
||||
Bangle.isBTHRMOn = function(){
|
||||
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
||||
}
|
||||
return (gatt!==undefined && gatt.connected);
|
||||
};
|
||||
|
||||
Bangle.isHRMOn = function() {
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
@ -18,16 +33,135 @@
|
|||
return Bangle.isBTHRMOn();
|
||||
}
|
||||
return origIsHRMOn() || Bangle.isBTHRMOn();
|
||||
};
|
||||
|
||||
var serviceFilters = [{
|
||||
services: [
|
||||
"180d"
|
||||
]
|
||||
}];
|
||||
|
||||
function retry(){
|
||||
log("Retry with time " + retryTime);
|
||||
if (currentRetryTimeout){
|
||||
log("Clearing timeout " + currentRetryTimeout);
|
||||
clearTimeout(currentRetryTimeout);
|
||||
currentRetryTimeout = undefined;
|
||||
}
|
||||
|
||||
var clampedTime = retryTime < 200 ? 200 : initialRetryTime;
|
||||
currentRetryTimeout = setTimeout(() => {
|
||||
log("Set timeout for retry as " + clampedTime);
|
||||
initBt();
|
||||
}, clampedTime);
|
||||
|
||||
retryTime = Math.pow(retryTime, 1.1);
|
||||
if (retryTime > maxRetryTime){
|
||||
retryTime = maxRetryTime;
|
||||
}
|
||||
}
|
||||
|
||||
function onDisconnect(reason) {
|
||||
log("Disconnect: " + reason);
|
||||
log("Gatt: ", gatt);
|
||||
retry();
|
||||
}
|
||||
|
||||
function onCharacteristic(event) {
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
var dv = event.target.value;
|
||||
var flags = dv.getUint8(0);
|
||||
// 0 = 8 or 16 bit
|
||||
// 1,2 = sensor contact
|
||||
// 3 = energy expended shown
|
||||
// 4 = RR interval
|
||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||
/* var idx = 2 + (flags&1); // index of next field
|
||||
if (flags&8) idx += 2; // energy expended
|
||||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
|
||||
Bangle.emit(settings.replace ? "HRM" : "BTHRM", {
|
||||
bpm: bpm,
|
||||
confidence: bpm == 0 ? 0 : 100,
|
||||
src: settings.replace ? "bthrm" : undefined
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.setBTHRMPower = function(isOn, app) {
|
||||
|
||||
var reUseCounter=0;
|
||||
|
||||
function initBt() {
|
||||
log("initBt with blockInit: " + blockInit);
|
||||
if (blockInit){
|
||||
retry();
|
||||
return;
|
||||
}
|
||||
|
||||
blockInit = true;
|
||||
|
||||
var connectionPromise;
|
||||
|
||||
if (reUseCounter > 3){
|
||||
log("Reuse counter to high")
|
||||
if (gatt.connected == true){
|
||||
try {
|
||||
log("Force disconnect with gatt: ", gatt);
|
||||
gatt.disconnect();
|
||||
} catch(e) {
|
||||
log("Error during force disconnect", e);
|
||||
}
|
||||
}
|
||||
gatt=undefined;
|
||||
reUseCounter = 0;
|
||||
}
|
||||
|
||||
if (!gatt){
|
||||
var requestPromise = NRF.requestDevice({ filters: serviceFilters });
|
||||
connectionPromise = requestPromise.then(function(device) {
|
||||
gatt = device.gatt;
|
||||
log("Gatt after request:", gatt);
|
||||
gatt.device.on('gattserverdisconnected', onDisconnect);
|
||||
});
|
||||
} else {
|
||||
reUseCounter++;
|
||||
log("Reusing gatt:", gatt);
|
||||
connectionPromise = gatt.connect();
|
||||
}
|
||||
|
||||
|
||||
var servicePromise = connectionPromise.then(function() {
|
||||
return gatt.getPrimaryService(0x180d);
|
||||
});
|
||||
|
||||
var characteristicPromise = servicePromise.then(function(service) {
|
||||
log("Got service:", service);
|
||||
return service.getCharacteristic(0x2A37);
|
||||
});
|
||||
|
||||
var notificationPromise = characteristicPromise.then(function(c) {
|
||||
log("Got characteristic:", c);
|
||||
c.on('characteristicvaluechanged', onCharacteristic);
|
||||
return c.startNotifications();
|
||||
});
|
||||
notificationPromise.then(()=>{
|
||||
log("Wait for notifications");
|
||||
retryTime = initialRetryTime;
|
||||
blockInit=false;
|
||||
});
|
||||
notificationPromise.catch((e) => {
|
||||
log("Error:", e);
|
||||
blockInit = false;
|
||||
retry();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Bangle.setBTHRMPower = function(isOn, app) {
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
log("setBTHRMPower ->", isOn, app);
|
||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
||||
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
||||
|
@ -35,63 +169,19 @@
|
|||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
log("setBTHRMPower on", app);
|
||||
if (!Bangle.isBTHRMOn()) {
|
||||
log("BTHRM not already on");
|
||||
status = "searching";
|
||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||
log("Found device "+device.id);
|
||||
status = "connecting";
|
||||
device.on('gattserverdisconnected', function(reason) {
|
||||
gatt = undefined;
|
||||
});
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
log("Connected");
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService(0x180D);
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic(0x2A37);
|
||||
}).then(function(characteristic) {
|
||||
log("Got characteristic");
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var flags = dv.getUint8(0);
|
||||
// 0 = 8 or 16 bit
|
||||
// 1,2 = sensor contact
|
||||
// 3 = energy expended shown
|
||||
// 4 = RR interval
|
||||
var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit
|
||||
/* var idx = 2 + (flags&1); // index of next field
|
||||
if (flags&8) idx += 2; // energy expended
|
||||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
|
||||
Bangle.emit(settings.replace?"HRM":"BTHRM", {
|
||||
bpm:bpm,
|
||||
confidence:100,
|
||||
src:settings.replace?"bthrm":undefined
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
log("Ready");
|
||||
status = "ok";
|
||||
}).catch(function(err) {
|
||||
log("Error",err);
|
||||
gatt = undefined;
|
||||
status = "error";
|
||||
});
|
||||
initBt();
|
||||
}
|
||||
} else { // not on
|
||||
log("setBTHRMPower off", app);
|
||||
log("Power off for " + app);
|
||||
if (gatt) {
|
||||
log("BTHRM connected - disconnecting");
|
||||
status = undefined;
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("BTHRM disconnect error", e);
|
||||
try {
|
||||
log("Disconnect with gatt: ", gatt);
|
||||
gatt.disconnect();
|
||||
} catch(e) {
|
||||
log("Error during disconnect", e);
|
||||
}
|
||||
blockInit = false;
|
||||
gatt = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -100,24 +190,29 @@
|
|||
var origSetHRMPower = Bangle.setHRMPower;
|
||||
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
log("setHRMPower for " + app + ":" + (isOn?"on":"off"));
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
if (settings.enabled || !isOn){
|
||||
log("Enable BTHRM power");
|
||||
Bangle.setBTHRMPower(isOn, app);
|
||||
}
|
||||
if ((settings.enabled && !settings.replace) || !settings.enabled || !isOn){
|
||||
log("Enable HRM power");
|
||||
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;
|
||||
log("Replace HRM event");
|
||||
if (!(Bangle._PWR===undefined) && !(Bangle._PWR.HRM===undefined)){
|
||||
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||
var app = Bangle._PWR.HRM[i];
|
||||
log("Moving app " + app);
|
||||
origSetHRMPower(0, app);
|
||||
Bangle.setBTHRMPower(1, app);
|
||||
if (Bangle._PWR.HRM===undefined) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -10,7 +10,9 @@ function draw(y, event, type, counter) {
|
|||
g.reset();
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,y,g.getWidth(),y+75);
|
||||
if (type == null || event == null || counter == 0) return;
|
||||
if (type == null || event == null || counter == 0){
|
||||
return;
|
||||
}
|
||||
var str = event.bpm + "";
|
||||
g.setFontVector(40).drawString(str,px,y+20);
|
||||
str = "Confidence: " + event.confidence;
|
||||
|
@ -21,21 +23,27 @@ function draw(y, event, type, counter) {
|
|||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
print("Event for BT " + JSON.stringify(e));
|
||||
counterBt += 5;
|
||||
//print("Event for BT " + JSON.stringify(e));
|
||||
if (e.bpm == 0){
|
||||
Bangle.buzz(100,0.2);
|
||||
}
|
||||
if (counterBt == 0){
|
||||
Bangle.buzz(200,0.5);
|
||||
}
|
||||
counterBt += 3;
|
||||
eventBt = e;
|
||||
}
|
||||
|
||||
function onHrm(e) {
|
||||
print("Event for Int " + JSON.stringify(e));
|
||||
counterInt += 5;
|
||||
//print("Event for Int " + JSON.stringify(e));
|
||||
counterInt += 3;
|
||||
eventInt = e;
|
||||
}
|
||||
|
||||
Bangle.on('BTHRM', onBtHrm);
|
||||
Bangle.on('HRM', onHrm);
|
||||
|
||||
Bangle.setHRMPower(1,'bthrm')
|
||||
Bangle.setHRMPower(1,'bthrm');
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
|
@ -47,13 +55,13 @@ g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
|||
function drawInt(){
|
||||
counterInt--;
|
||||
if (counterInt < 0) counterInt = 0;
|
||||
if (counterInt > 5) counterInt = 5;
|
||||
if (counterInt > 3) counterInt = 3;
|
||||
draw(24, eventInt, "HRM", counterInt);
|
||||
}
|
||||
function drawBt(){
|
||||
counterBt--;
|
||||
if (counterBt < 0) counterBt = 0;
|
||||
if (counterBt > 5) counterBt = 5;
|
||||
if (counterBt > 3) counterBt = 3;
|
||||
draw(100, eventBt, "BTHRM", counterBt);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -11,3 +11,5 @@
|
|||
Support to choose between humidity and wind speed for weather circle progress
|
||||
Support to show time and progress until next sunrise or sunset
|
||||
Load daily steps from Bangle health if available
|
||||
0.07: Allow configuration of minimal heart rate confidence
|
||||
0.08: Allow configuration of up to 4 circles in a row
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Circles clock
|
||||
|
||||
A clock with circles for different data at the bottom in a probably familiar style
|
||||
A clock with three or four circles for different data at the bottom in a probably familiar style
|
||||
|
||||
By default the time, date and day of week is shown.
|
||||
|
||||
|
@ -18,6 +18,8 @@ It can show the following information (this can be configured):
|
|||
## Screenshots
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
|
|
@ -23,30 +23,27 @@ const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED
|
|||
const sunSetDown = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wLDg1ggfACoo"));
|
||||
const sunSetUp = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wRFgfAg1gBIY"));
|
||||
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = storage.readJSON("circlesclock.json", 1) || {
|
||||
'minHR': 40,
|
||||
'maxHR': 200,
|
||||
'stepGoal': 10000,
|
||||
'stepDistanceGoal': 8000,
|
||||
'stepLength': 0.8,
|
||||
'batteryWarn': 30,
|
||||
'showWidgets': false,
|
||||
'weatherCircleData': 'humidity',
|
||||
'circle1': 'hr',
|
||||
'circle2': 'steps',
|
||||
'circle3': 'battery'
|
||||
};
|
||||
// Load step goal from pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
const d = require('Storage').readJSON("wpedom.json", 1) || {};
|
||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
}
|
||||
let settings = storage.readJSON("circlesclock.json", 1) || {
|
||||
'minHR': 40,
|
||||
'maxHR': 200,
|
||||
'confidence': 0,
|
||||
'stepGoal': 10000,
|
||||
'stepDistanceGoal': 8000,
|
||||
'stepLength': 0.8,
|
||||
'batteryWarn': 30,
|
||||
'showWidgets': false,
|
||||
'weatherCircleData': 'humidity',
|
||||
'circleCount': 3,
|
||||
'circle1': 'hr',
|
||||
'circle2': 'steps',
|
||||
'circle3': 'battery',
|
||||
'circle4': 'weather'
|
||||
};
|
||||
// Load step goal from pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
const d = require('Storage').readJSON("wpedom.json", 1) || {};
|
||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
}
|
||||
loadSettings();
|
||||
|
||||
|
||||
/*
|
||||
* Read location from myLocation app
|
||||
|
@ -57,6 +54,7 @@ function getLocation() {
|
|||
let location = getLocation();
|
||||
|
||||
const showWidgets = settings.showWidgets || false;
|
||||
const circleCount = settings.circleCount || 3;
|
||||
|
||||
let hrtValue;
|
||||
let now = Math.round(new Date().getTime() / 1000);
|
||||
|
@ -77,11 +75,33 @@ const hOffset = 30 - widgetOffset;
|
|||
const h1 = Math.round(1 * h / 5 - hOffset);
|
||||
const h2 = Math.round(3 * h / 5 - hOffset);
|
||||
const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position
|
||||
const circlePosX = [Math.round(w / 6), Math.round(3 * w / 6), Math.round(5 * w / 6)]; // cirle x positions
|
||||
const radiusOuter = 25;
|
||||
const radiusInner = 20;
|
||||
const circleFont = "Vector:15";
|
||||
const circleFontBig = "Vector:16";
|
||||
|
||||
/*
|
||||
* circle x positions
|
||||
* depending on circleCount
|
||||
*
|
||||
* | 1 2 3 4 5 6 |
|
||||
* | (1) (2) (3) |
|
||||
* => circles start at 1,3,5 / 6
|
||||
*
|
||||
* | 1 2 3 4 5 6 7 8 |
|
||||
* | (1) (2) (3) (4) |
|
||||
* => circles start at 1,3,5,7 / 8
|
||||
*/
|
||||
const parts = circleCount * 2;
|
||||
const circlePosX = [
|
||||
Math.round(1 * w / parts), // circle1
|
||||
Math.round(3 * w / parts), // circle2
|
||||
Math.round(5 * w / parts), // circle3
|
||||
Math.round(7 * w / parts), // circle4
|
||||
];
|
||||
|
||||
const radiusOuter = circleCount == 3 ? 25 : 20;
|
||||
const radiusInner = circleCount == 3 ? 20 : 15;
|
||||
const circleFont = circleCount == 3 ? "Vector:15" : "Vector:12";
|
||||
const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:13";
|
||||
const defaultCircleTypes = ["steps", "hr", "battery", "weather"];
|
||||
|
||||
|
||||
function draw() {
|
||||
g.clear(true);
|
||||
|
@ -121,10 +141,9 @@ function draw() {
|
|||
drawCircle(1);
|
||||
drawCircle(2);
|
||||
drawCircle(3);
|
||||
if (circleCount >= 4) drawCircle(4);
|
||||
}
|
||||
|
||||
const defaultCircleTypes = ["steps", "hr", "battery"];
|
||||
|
||||
function drawCircle(index) {
|
||||
let type = settings['circle' + index];
|
||||
if (!type) type = defaultCircleTypes[index - 1];
|
||||
|
@ -146,6 +165,7 @@ function drawCircle(index) {
|
|||
drawWeather(w);
|
||||
break;
|
||||
case "sunprogress":
|
||||
case "sunProgress":
|
||||
drawSunProgress(w);
|
||||
break;
|
||||
case "empty":
|
||||
|
@ -168,7 +188,7 @@ function getCirclePosition(type) {
|
|||
if (circlePositionsCache[type] >= 0) {
|
||||
return circlePosX[circlePositionsCache[type]];
|
||||
}
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
for (let i = 1; i <= circleCount; i++) {
|
||||
const setting = settings['circle' + i];
|
||||
if (setting == type) {
|
||||
circlePositionsCache[type] = i - 1;
|
||||
|
@ -318,6 +338,8 @@ function drawWeather(w) {
|
|||
if (code > 0) {
|
||||
const icon = getWeatherIconByCode(code);
|
||||
if (icon) g.drawImage(icon, w - 6, h3 + radiusOuter - 10);
|
||||
} else {
|
||||
g.drawString("?", w, h3 + radiusOuter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,9 +621,11 @@ Bangle.on('lock', function(isLocked) {
|
|||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
if (isCircleEnabled("hr")) {
|
||||
hrtValue = hrm.bpm;
|
||||
if (Bangle.isLCDOn())
|
||||
drawHeartRate();
|
||||
if (hrm.confidence >= (settings.confidence || 0)) {
|
||||
hrtValue = hrm.bpm;
|
||||
if (Bangle.isLCDOn())
|
||||
drawHeartRate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.06",
|
||||
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
||||
"version":"0.08",
|
||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}],
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
|
|
After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
@ -35,6 +35,16 @@
|
|||
},
|
||||
onchange: x => save('maxHR', x),
|
||||
},
|
||||
'hr confidence': {
|
||||
value: "confidence" in settings ? settings.confidence : 0,
|
||||
min: 0,
|
||||
max : 100,
|
||||
step: 10,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('confidence', x),
|
||||
},
|
||||
'step goal': {
|
||||
value: "stepGoal" in settings ? settings.stepGoal : 10000,
|
||||
min: 2000,
|
||||
|
@ -86,23 +96,36 @@
|
|||
format: v => weatherData[v],
|
||||
onchange: x => save('weatherCircleData', weatherData[x]),
|
||||
},
|
||||
'left': {
|
||||
'circle count': {
|
||||
value: "circleCount" in settings ? settings.circleCount : 3,
|
||||
min: 3,
|
||||
max : 4,
|
||||
step: 1,
|
||||
onchange: x => save('circleCount', x),
|
||||
},
|
||||
'circle1': {
|
||||
value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle1', valuesCircleTypes[x]),
|
||||
},
|
||||
'middle': {
|
||||
'circle2': {
|
||||
value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle2', valuesCircleTypes[x]),
|
||||
},
|
||||
'right': {
|
||||
'circle3': {
|
||||
value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle3', valuesCircleTypes[x]),
|
||||
},
|
||||
'circle4': {
|
||||
value: settings.circle4 ? valuesCircleTypes.indexOf(settings.circle4) : 4,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle4', valuesCircleTypes[x]),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,12 +24,7 @@ if (!settings) resetSettings();
|
|||
function showMenu() {
|
||||
const datemenu = {
|
||||
'': {
|
||||
'title': 'Set Date',
|
||||
'predraw': function() {
|
||||
datemenu.Day.value = settings.day;
|
||||
datemenu.Month.value = settings.month;
|
||||
datemenu.Year.value = settings.year;
|
||||
}
|
||||
'title': 'Set Date'
|
||||
},
|
||||
'Day': {
|
||||
value: settings.day,
|
||||
|
@ -65,4 +60,3 @@ function showMenu() {
|
|||
}
|
||||
|
||||
showMenu();
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Tweaked proximity identification settings
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
## Usage
|
||||
|
||||
Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/).
|
||||
Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/). See our [Bangle.js Development Guide](https://reelyactive.github.io/diy/banglejs-dev/) for details.
|
||||
|
||||
|
||||
## Features
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright reelyActive 2017-2021
|
||||
* Copyright reelyActive 2017-2022
|
||||
* We believe in an open Internet of Things
|
||||
*
|
||||
* DirAct is jointly developed by reelyActive and Code Blue Consulting
|
||||
|
@ -11,14 +11,14 @@ const NAMESPACE_FILTER_ID = [ 0xc0, 0xde, 0xb1, 0x0e, 0x1d,
|
|||
0xd1, 0xe0, 0x1b, 0xed, 0x0c ];
|
||||
const EXCITER_INSTANCE_IDS = new Uint32Array([ 0xe8c17e45 ]);
|
||||
const RESETTER_INSTANCE_IDS = new Uint32Array([ 0x4e5e77e4 ]);
|
||||
const PROXIMITY_RSSI_THRESHOLD = -65;
|
||||
const PROXIMITY_LED_RSSI_THRESHOLD = -65;
|
||||
const PROXIMITY_RSSI_THRESHOLD = -85;
|
||||
const PROXIMITY_LED_RSSI_THRESHOLD = -85;
|
||||
const PROXIMITY_TABLE_SIZE = 8;
|
||||
const DIGEST_TABLE_SIZE = 32;
|
||||
const OBSERVE_PERIOD_MILLISECONDS = 400;
|
||||
const BROADCAST_PERIOD_MILLISECONDS = 3600;
|
||||
const BROADCAST_PERIOD_MILLISECONDS = 1600;
|
||||
const BROADCAST_DIGEST_PAGE_MILLISECONDS = 400;
|
||||
const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 400;
|
||||
const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 200;
|
||||
const DIGEST_PACKET_INTERVAL_MILLISECONDS = 100;
|
||||
const DIGEST_TIME_CYCLE_THRESHOLD = 86400;
|
||||
const EXCITER_HOLDOFF_SECONDS = 60;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "diract",
|
||||
"name": "DirAct",
|
||||
"shortName": "DirAct",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Proximity interaction detection.",
|
||||
"icon": "diract.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files",
|
||||
"icon": "icons8-filing-cabinet-48.png",
|
||||
"tags": "tools",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"fileman.app.js","url":"fileman.app.js"},
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
Take 'beta' tag off
|
||||
0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later
|
||||
Add CRC checks for common bootloaders that we know don't work
|
||||
0.04: Include a precompiled bootloader for easy bootloader updates
|
||||
|
|
|
@ -3,33 +3,42 @@
|
|||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<p><b>THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE
|
||||
INSTRUCTIONS FOR <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">BANGLE.JS</a> 1 AND <a href="https://www.espruino.com/Bangle.js2#firmware-updates" target="_blank">BANGLE.JS 2</a></b>. For usage on Bangle.js 2 you'll likely need to have an updated bootloader.</p>
|
||||
<p>This tool allows you to update the bootloader on <a href="https://www.espruino.com/Bangle.js2">Bangle.js 2</a> devices
|
||||
from within the App Loader.</p>
|
||||
|
||||
<div id="fw-unknown">
|
||||
<p><b>Firmware updates using the App Loader are only possible on
|
||||
Bangle.js 2. For firmware updates on Bangle.js 1 please
|
||||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
|
||||
</div>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span></p>
|
||||
<ul>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and bootloader is <span id="boot-version" style="font-weight:bold">unknown</span></p>
|
||||
</ul>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p>If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x bootloader, the Firmware Update
|
||||
will fail with a message about the bootloader version. If so, please <a href="bootloader_espruino_2v11.52_banglejs2.hex" class="fw-link">click here to update to bootloader 2v11.52</a> and then click the 'Upload' button that appears.</p>
|
||||
<div id="latest-firmware" style="display:none">
|
||||
<p>The currently available Espruino firmware releases are:</p>
|
||||
<ul id="latest-firmware-list">
|
||||
</ul>
|
||||
<p>To update, click the link and then click the 'Upload' button that appears.</p>
|
||||
<p>To update, click a link above and then click the 'Upload' button that appears.</p>
|
||||
</div>
|
||||
<a href="#" id="advanced-btn">Advanced ▼</a>
|
||||
<div id="advanced-div" style="display:none">
|
||||
<p>Firmware updates via this tool work differently to the NRF Connect method mentioned on
|
||||
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js 2 page</a>. Firmware
|
||||
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
|
||||
the new firmware into internal Storage.</p>
|
||||
<p>In addition to the links above, you can upload a hex or zip file directly below. This file should be an <code>.app_hex</code>
|
||||
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
|
||||
<p><b>DANGER!</b> No verification is performed on uploaded ZIP or HEX files - you could
|
||||
potentially overwrite your bootloader with the wrong binary and brick your Bangle.</p>
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
|
||||
</div>
|
||||
|
||||
<p>Or you can upload a hex or zip file here. This file should be an <code>.app_hex</code>
|
||||
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
|
||||
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
|
||||
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
|
||||
</div>
|
||||
|
||||
<p>Firmware updates via this tool work differently to the NRF Connect method mentioned on
|
||||
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js page</a>. Firmware
|
||||
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
|
||||
the new firmware into internal Storage.</p>
|
||||
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
|
@ -38,7 +47,6 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js"></script>
|
||||
|
||||
<script>
|
||||
var hex;
|
||||
var hexJS; // JS to upload hex
|
||||
var HEADER_LEN = 16; // size of app flash header
|
||||
var APP_START = 0x26000;
|
||||
|
@ -47,79 +55,115 @@ var MAX_ADDRESS = 0x1000000; // discount anything in hex file above this
|
|||
var VERSION = 0x12345678; // VERSION! Use this to test firmware in JS land
|
||||
var DEBUG = false;
|
||||
|
||||
function clearLog() {
|
||||
document.getElementById('log').innerText = "";
|
||||
console.log("Log Cleared");
|
||||
}
|
||||
function log(t) {
|
||||
document.getElementById('log').innerText += t+"\n";
|
||||
console.log(t);
|
||||
}
|
||||
|
||||
function onInit(device) {
|
||||
console.log(device);
|
||||
console.log("fwupdate init", device);
|
||||
if (device && device.version)
|
||||
document.getElementById("fw-version").innerText = device.version;
|
||||
if (device && device.id=="BANGLEJS2") {
|
||||
document.getElementById("fw-unknown").style = "display:none";
|
||||
document.getElementById("fw-ok").style = "";
|
||||
}
|
||||
|
||||
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
|
||||
console.log("Bootloader CRC = "+crc);
|
||||
var version = `unknown (CRC ${crc})`;
|
||||
if (crc==1339551013) version = "2v10.219";
|
||||
if (crc==1207580954) version = "2v10.236";
|
||||
if (crc==3435933210) version = "2v11.52";
|
||||
if (crc==46757280) version = "2v11.58";
|
||||
document.getElementById("boot-version").innerText = version;
|
||||
});
|
||||
}
|
||||
|
||||
function checkForFileOnServer() {
|
||||
function getURL(url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = callback;
|
||||
baseURL = url;
|
||||
xhr.open("GET", baseURL);
|
||||
xhr.open("GET", url);
|
||||
xhr.responseType = "document";
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function getFilesFromURL(url, regex, callback) {
|
||||
getURL(url, function() {
|
||||
console.log(this.responseXML)
|
||||
var files = [];
|
||||
var elements = this.responseXML.getElementsByTagName("a");
|
||||
for (var i=0;i<elements.length;i++) {
|
||||
var href = elements[i].href;
|
||||
if (regex.exec(href)) {
|
||||
files.push(href);
|
||||
}
|
||||
}
|
||||
callback(files);
|
||||
});
|
||||
getURL(url, function() {
|
||||
//console.log(this.responseXML)
|
||||
var files = [];
|
||||
var elements = this.responseXML.getElementsByTagName("a");
|
||||
for (var i=0;i<elements.length;i++) {
|
||||
var href = elements[i].href;
|
||||
if (regex.exec(href)) {
|
||||
files.push(href);
|
||||
}
|
||||
}
|
||||
callback(files);
|
||||
});
|
||||
}
|
||||
|
||||
var regex = new RegExp("_banglejs2.*zip$");
|
||||
|
||||
var domFirmwareList = document.getElementById("latest-firmware-list");
|
||||
var domFirmwareList = document.getElementById("latest-firmware-list");
|
||||
var domFirmware = document.getElementById("latest-firmware");
|
||||
console.log("Checking server...");
|
||||
|
||||
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
|
||||
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
|
||||
releaseFiles.sort().reverse().forEach(function(f) {
|
||||
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
|
||||
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
|
||||
console.log("Found "+name);
|
||||
domFirmwareList.innerHTML += '<li>Release: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
|
||||
domFirmwareList.innerHTML += '<li>Release: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
|
||||
domFirmware.style = "";
|
||||
});
|
||||
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
|
||||
travisFiles.forEach(function(f) {
|
||||
var name = f.substr(f.lastIndexOf('/')+1);
|
||||
});
|
||||
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
|
||||
travisFiles.forEach(function(f) {
|
||||
var name = f.substr(f.lastIndexOf('/')+1);
|
||||
console.log("Found "+name);
|
||||
domFirmwareList.innerHTML += '<li>Cutting Edge build: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
|
||||
domFirmwareList.innerHTML += '<li>Cutting Edge build: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
|
||||
domFirmware.style = "";
|
||||
});
|
||||
});
|
||||
console.log("Finished check for firmware files...");
|
||||
var fwlinks = document.querySelectorAll(".fw-link");
|
||||
for (var i=0;i<fwlinks.length;i++)
|
||||
fwlinks[i].addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
var url = e.target.href;
|
||||
downloadZipFile(url).then(info=>{
|
||||
downloadURL(e.target.href).then(info=>{
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function downloadURL(url) {
|
||||
clearLog();
|
||||
log("Downloading "+url);
|
||||
if (url.endsWith(".zip")) {
|
||||
return downloadZipFile(url);
|
||||
} else if (url.endsWith(".hex")) {
|
||||
return downloadHexFile(url);
|
||||
} else {
|
||||
log("Unknown URL "+url+" - expecting .hex or .zip extension");
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
function downloadHexFile(url) {
|
||||
return new Promise(resolve => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = function() {
|
||||
hexFileLoaded(this.responseText.toString());
|
||||
resolve();
|
||||
};
|
||||
xhr.open("GET", url);
|
||||
xhr.responseType = "text";
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
function downloadZipFile(url) {
|
||||
|
@ -154,15 +198,15 @@ function convertZipFile(binary) {
|
|||
if (info.bin_file.byteLength > APP_MAX_LENGTH) throw new Error("Firmware file is too big!");
|
||||
info.storageContents = new Uint8Array(info.bin_file.byteLength + HEADER_LEN)
|
||||
info.storageContents.set(new Uint8Array(info.bin_file), HEADER_LEN);
|
||||
console.log("ZIP downloaded and decoded",info);
|
||||
createJS_app(info.storageContents, APP_START, APP_START+info.bin_file.byteLength);
|
||||
log("Download complete");
|
||||
console.log("Download complete",info);
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
return info;
|
||||
}).catch(err => log("ERROR:" + err));
|
||||
}
|
||||
|
||||
function handleFileSelect(event) {
|
||||
clearLog();
|
||||
if (event.target.files.length!=1) {
|
||||
log("More than one file selected!");
|
||||
return;
|
||||
|
@ -172,13 +216,14 @@ function handleFileSelect(event) {
|
|||
var reader = new FileReader();
|
||||
if (file.name.endsWith(".hex") || file.name.endsWith(".app_hex")) {
|
||||
reader.onload = function(event) {
|
||||
hex = event.target.result.split("\n");
|
||||
log("HEX uploaded");
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
fileLoaded();
|
||||
hexFileLoaded(event.target.result);
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
} else if (file.name.endsWith(".zip")) {
|
||||
reader.onload = function(event) {
|
||||
log("ZIP uploaded");
|
||||
convertZipFile(event.target.result);
|
||||
};
|
||||
reader.readAsArrayBuffer(event.target.files[0]);
|
||||
|
@ -187,25 +232,6 @@ function handleFileSelect(event) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
function parseLines(dataCallback) {
|
||||
var addrHi = 0;
|
||||
hex.forEach(function(hexline) {
|
||||
if (DEBUG) console.log(hexline);
|
||||
var bytes = hexline.substr(1,2);
|
||||
var addrLo = parseInt(hexline.substr(3,4),16);
|
||||
var cmd = hexline.substr(7,2);
|
||||
if (cmd=="02") addrHi = parseInt(hexline.substr(9,4),16) << 4; // Extended Segment Address
|
||||
else if (cmd=="04") addrHi = parseInt(hexline.substr(9,4),16) << 16; // Extended Linear Address
|
||||
else if (cmd=="00") {
|
||||
var addr = addrHi + addrLo;
|
||||
var data = [];
|
||||
for (var i=0;i<16;i++) data.push(parseInt(hexline.substr(9+(i*2),2),16));
|
||||
dataCallback(addr,data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function CRC32(data) {
|
||||
var crc = 0xFFFFFFFF;
|
||||
data.forEach(function(d) {
|
||||
|
@ -278,6 +304,7 @@ function createJS_app(binary, startAddress, endAddress) {
|
|||
}
|
||||
hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
|
||||
hexJS += '\x10setTimeout(()=>E.reboot(), 1000);\n';
|
||||
log("Firmware update ready for upload");
|
||||
}
|
||||
|
||||
|
||||
|
@ -302,12 +329,32 @@ function createJS_bootloader(binary, startAddress, endAddress) {
|
|||
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `f.write(_fw,${startAddress});\n`;
|
||||
hexJS += `})()\n`;
|
||||
log("Bootloader ready for upload");
|
||||
}
|
||||
|
||||
function fileLoaded() {
|
||||
function hexFileLoaded(hexString) {
|
||||
var hex = hexString.split("\n"); // array of lines of the hex file
|
||||
function hexParseLines(dataCallback) {
|
||||
var addrHi = 0;
|
||||
hex.forEach(function(hexline) {
|
||||
if (DEBUG) console.log(hexline);
|
||||
var bytes = hexline.substr(1,2);
|
||||
var addrLo = parseInt(hexline.substr(3,4),16);
|
||||
var cmd = hexline.substr(7,2);
|
||||
if (cmd=="02") addrHi = parseInt(hexline.substr(9,4),16) << 4; // Extended Segment Address
|
||||
else if (cmd=="04") addrHi = parseInt(hexline.substr(9,4),16) << 16; // Extended Linear Address
|
||||
else if (cmd=="00") {
|
||||
var addr = addrHi + addrLo;
|
||||
var data = [];
|
||||
for (var i=0;i<16;i++) data.push(parseInt(hexline.substr(9+(i*2),2),16));
|
||||
dataCallback(addr,data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Work out addresses
|
||||
var startAddress, endAddress = 0;
|
||||
parseLines(function(addr, data) {
|
||||
hexParseLines(function(addr, data) {
|
||||
if (addr>MAX_ADDRESS) return; // ignore data out of range
|
||||
if (startAddress === undefined || addr<startAddress)
|
||||
startAddress = addr;
|
||||
|
@ -319,7 +366,7 @@ function fileLoaded() {
|
|||
// Work out data
|
||||
var binary = new Uint8Array(HEADER_LEN + endAddress-startAddress);
|
||||
binary.fill(0); // actually seems to assume a block is filled with 0 if not complete
|
||||
parseLines(function(addr, data) {
|
||||
hexParseLines(function(addr, data) {
|
||||
if (addr>MAX_ADDRESS) return; // ignore data out of range
|
||||
var binAddr = HEADER_LEN + addr - startAddress;
|
||||
binary.set(data, binAddr);
|
||||
|
@ -351,6 +398,10 @@ function handleUpload() {
|
|||
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
document.getElementById("upload").addEventListener("click", handleUpload);
|
||||
document.getElementById("advanced-btn").addEventListener("click", function() {
|
||||
document.getElementById("advanced-btn").style = "display:none";
|
||||
document.getElementById("advanced-div").style = "";
|
||||
});
|
||||
setTimeout(checkForFileOnServer, 10);
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "fwupdate",
|
||||
"name": "Firmware Update",
|
||||
"version": "0.03",
|
||||
"description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
|
||||
"version": "0.04",
|
||||
"description": "Uploads new Espruino firmwares to Bangle.js 2",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tools,system",
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.07: Added coloured bar charts
|
||||
0.08: Suppress bleed through of E.showMenu's when displaying bar charts
|
||||
0.09: Fix file naming so months are 1-based (not 0) (fix #1119)
|
||||
0.10: Adds additional 3 minute setting for HRM
|
||||
|
|
|
@ -28,8 +28,8 @@ function menuSettings() {
|
|||
"< Back":()=>menuMain(),
|
||||
"Heart Rt":{
|
||||
value : 0|s.hrm,
|
||||
min : 0, max : 2,
|
||||
format : v=>["Off","10 mins","Always"][v],
|
||||
min : 0, max : 3,
|
||||
format : v=>["Off","3 mins","10 mins","Always"][v],
|
||||
onchange : v => { s.hrm=v;setSettings(s); }
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
(function(){
|
||||
var settings = require("Storage").readJSON("health.json",1)||{};
|
||||
var hrm = 0|settings.hrm;
|
||||
if (hrm==1) {
|
||||
function onHealth() {
|
||||
Bangle.setHRMPower(1, "health");
|
||||
setTimeout(()=>Bangle.setHRMPower(0, "health"),2*60000); // give it 2 minutes
|
||||
var settings = require("Storage").readJSON("health.json",1)||{};
|
||||
var hrm = 0|settings.hrm;
|
||||
if (hrm == 1 || hrm == 2) {
|
||||
function onHealth() {
|
||||
Bangle.setHRMPower(1, "health");
|
||||
setTimeout(()=>Bangle.setHRMPower(0, "health"),hrm*60000); // give it 1 minute detection time for 3 min setting and 2 minutes for 10 min setting
|
||||
if (hrm == 1){
|
||||
for (var i = 1; i <= 2; i++){
|
||||
setTimeout(()=>{
|
||||
Bangle.setHRMPower(1, "health");
|
||||
setTimeout(()=>{
|
||||
Bangle.setHRMPower(0, "health");
|
||||
}, (i * 200000) + 60000);
|
||||
}, (i * 200000));
|
||||
}
|
||||
}
|
||||
Bangle.on("health", onHealth);
|
||||
Bangle.on('HRM', h => {
|
||||
if (h.confidence>80) Bangle.setHRMPower(0, "health");
|
||||
});
|
||||
if (Bangle.getHealthStatus().bpmConfidence) return;
|
||||
onHealth();
|
||||
} else Bangle.setHRMPower(hrm!=0, "health");
|
||||
}
|
||||
Bangle.on("health", onHealth);
|
||||
Bangle.on('HRM', h => {
|
||||
if (h.confidence>80) Bangle.setHRMPower(0, "health");
|
||||
});
|
||||
if (Bangle.getHealthStatus().bpmConfidence) return;
|
||||
onHealth();
|
||||
} else Bangle.setHRMPower(hrm!=0, "health");
|
||||
})();
|
||||
|
||||
Bangle.on("health", health => {
|
||||
|
|
|
@ -51,7 +51,7 @@ function saveCSV(data, date, title) {
|
|||
}
|
||||
|
||||
function downloadHealth(filename, callback) {
|
||||
Util.showModal("Downloading Track...");
|
||||
Util.showModal("Downloading Health info...");
|
||||
Util.readStorage(filename, data => {
|
||||
Util.hideModal();
|
||||
callback(data);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "Logs health data and provides an app to view it (requires firmware 2v10.100 or later)",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.06: Move the next strike time to the first row of display
|
||||
0.07: Change the boot function to avoid reloading the entire watch
|
||||
0.08: Default to no strikes. Fix file-not-found issue during the first boot. Add data file.
|
||||
0.09: Add some customisation options
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const storage = require('Storage');
|
||||
var settings = storage.readJSON('hourstrike.json', 1);
|
||||
const chimes = ["Buzz", "Beep"];
|
||||
|
||||
function updateSettings() {
|
||||
storage.write('hourstrike.json', settings);
|
||||
|
@ -26,6 +27,12 @@ function showMainMenu() {
|
|||
mainmenu.Strength = {
|
||||
value: settings.vlevel*10, min: 1, max: 10, format: v=>v/10,
|
||||
onchange: v=> {settings.vlevel = v/10; updateSettings();}};
|
||||
mainmenu.Strikecount = {
|
||||
value: settings.scount, min: 1, max: 2, format: v=>v,
|
||||
onchange: v=> {settings.scount = v; updateSettings();}};
|
||||
mainmenu.Chimetype = {
|
||||
value: settings.buzzOrBeep, min: 0, max: 1, format: v => chimes[v],
|
||||
onchange: v=> {settings.buzzOrBeep = v; updateSettings();}};
|
||||
mainmenu['< Back'] = ()=>load();
|
||||
return E.showMenu(mainmenu);
|
||||
}
|
||||
|
|
|
@ -30,9 +30,23 @@
|
|||
}
|
||||
function strike_func () {
|
||||
var setting = require('Storage').readJSON('hourstrike.json',1)||[];
|
||||
Bangle.buzz(200, setting.vlevel||0.5)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve,200)))
|
||||
.then(() => Bangle.buzz(200, setting.vlevel||0.5));
|
||||
if (0 == setting.buzzOrBeep) {
|
||||
if (2 == setting.scount) {
|
||||
Bangle.buzz(200, setting.vlevel||0.5)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve,200)))
|
||||
.then(() => Bangle.buzz(200, setting.vlevel||0.5));
|
||||
} else {
|
||||
Bangle.buzz(200, setting.vlevel||0.5);
|
||||
}
|
||||
} else {
|
||||
if (2 == setting.scount) {
|
||||
Bangle.beep(200)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve,100)))
|
||||
.then(() => Bangle.beep(300));
|
||||
} else {
|
||||
Bangle.beep(200);
|
||||
}
|
||||
}
|
||||
setup();
|
||||
}
|
||||
setup();
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"next_hour":-1,"next_minute":-1}
|
||||
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"scount":2,"buzzOrBeep":0,"next_hour":-1,"next_minute":-1}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hourstrike",
|
||||
"name": "Hour Strike",
|
||||
"shortName": "Hour Strike",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
|
||||
"icon": "app-icon.png",
|
||||
"tags": "tool,alarm",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
After Width: | Height: | Size: 553 B |
|
@ -0,0 +1,190 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Bangle.js Accelerometer streaming</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://www.puck-js.com/puck.js"></script>
|
||||
<button id="btnConnect">Connect</button>
|
||||
<button id="btnStop">Stop</button>
|
||||
<button id="btnReset">Reset</button>
|
||||
<button id="btnSave">Save CSV</button>
|
||||
<p id="result"></p>
|
||||
<script>
|
||||
var BANGLE_CODE = String.raw`
|
||||
var accData=[];
|
||||
var maxSize=0;
|
||||
var filename="log.csv";
|
||||
//0 print, 1 BT, 2 File
|
||||
var method=1;
|
||||
|
||||
var running = true;
|
||||
var gotHRMraw = false;
|
||||
var gotBTHRM = false;
|
||||
var gotHRM = false;
|
||||
var gotAcc = false;
|
||||
|
||||
function gotAll(){
|
||||
return running && gotBTHRM && gotHRM && gotHRMraw && gotAcc;
|
||||
}
|
||||
|
||||
Bangle.setHRMPower(1);
|
||||
if (Bangle.setBTHRMPower){
|
||||
print("Use BTHRM");
|
||||
Bangle.setBTHRMPower(1);
|
||||
Bangle.setBTHRMPower(1);
|
||||
} else {
|
||||
gotBTHRM = true;
|
||||
}
|
||||
|
||||
var write=null;
|
||||
|
||||
if (method == 2){
|
||||
var f = require('Storage').open(filename,"w");
|
||||
f.erase();
|
||||
f = require('Storage').open(filename,"a");
|
||||
write = function(str){f.write(str);};
|
||||
} else if (method == 1){
|
||||
write = function(str){Bluetooth.print("DATA: " + str);};
|
||||
} else {
|
||||
write=print;
|
||||
}
|
||||
|
||||
write("Time,Acc_x,Acc_y,Acc_z,HRM_b,HRM_c,HRM_r,HRM_f,PPG_r,PPG_o,BTHRM\n");
|
||||
|
||||
|
||||
function writeAcc(e){
|
||||
gotAcc = true;
|
||||
e.date=Date.now();
|
||||
accData.push(e);
|
||||
accData.splice(0, accData.length - maxSize);
|
||||
}
|
||||
|
||||
function writeAccDirect(e){
|
||||
gotAcc = true;
|
||||
if (!gotAll()) return;
|
||||
write(Date.now()+","+e.x+","+e.y+","+e.z+",,,,,,,,\n");
|
||||
}
|
||||
|
||||
function writeBTHRM(e){
|
||||
gotBTHRM = true;
|
||||
if (!gotAll()) return;
|
||||
write(Date.now()+",,,,,,,,,,"+e.bpm+"\n");
|
||||
}
|
||||
|
||||
function writeHRM(e){
|
||||
gotHRM = true;
|
||||
if (!gotAll()) return;
|
||||
while(accData.length > 0){
|
||||
var c = accData.shift();
|
||||
if (c) write(c.date+","+c.x+","+c.y+","+c.z+",,,,,,,,\n");
|
||||
}
|
||||
write(Date.now()+",,,,"+e.bpm+","+e.confidence+",,,,\n");
|
||||
}
|
||||
|
||||
function writeHRMraw(e){
|
||||
gotHRMraw = true;
|
||||
if (!gotAll()) return;
|
||||
write(Date.now()+",,,,,,"+e.raw+","+e.filt+","+e.vcPPG+","+e.vcPPGoffs+",\n");
|
||||
}
|
||||
|
||||
if(maxSize){
|
||||
Bangle.on("accel", writeAcc);
|
||||
} else {
|
||||
Bangle.on("accel", writeAccDirect);
|
||||
}
|
||||
Bangle.on("HRM-raw", writeHRMraw);
|
||||
Bangle.on("HRM", writeHRM);
|
||||
Bangle.on("BTHRM", writeBTHRM);
|
||||
|
||||
g.clear();
|
||||
g.setColor(1,0,0);
|
||||
g.fillRect(0,0,g.getWidth(),g.getHeight());
|
||||
|
||||
|
||||
var intervalId = -1;
|
||||
intervalId = setInterval(()=>{
|
||||
print("Checking... Acc:" + gotAcc + " BTHRM:" + gotBTHRM + " HRM:" + gotHRM + " HRM raw:" + gotHRMraw);
|
||||
if (gotAll()){
|
||||
g.setColor(0,1,0);
|
||||
g.fillRect(0,0,g.getWidth(),g.getHeight());
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
if (Bangle.setBTHRMPower){
|
||||
intervalId = setInterval(()=>{
|
||||
if (!Bangle.isBTHRMOn()) Bangle.setBTHRMPower(1);
|
||||
}, 5000);
|
||||
}
|
||||
`;
|
||||
|
||||
var connection;
|
||||
var lineCount=-1;
|
||||
|
||||
|
||||
function stop (){
|
||||
connection.write("running = false; \n");
|
||||
connection.close();
|
||||
connection = undefined;
|
||||
}
|
||||
|
||||
document.getElementById("btnSave").addEventListener("click", function() {
|
||||
var h = document.createElement('a');
|
||||
h.href = 'data:text/csv;charset=utf-8,' + encodeURI(localStorage.getItem("data"));
|
||||
h.target = '_blank';
|
||||
h.download = "DATA.csv";
|
||||
h.click();
|
||||
});
|
||||
document.getElementById("btnReset").addEventListener("click", function() {
|
||||
if (connection) {
|
||||
stop();
|
||||
}
|
||||
document.getElementById("result").innerText="";
|
||||
lineCount=-1;
|
||||
localStorage.removeItem("data");
|
||||
});
|
||||
document.getElementById("btnStop").addEventListener("click", function() {
|
||||
if (connection) {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
document.getElementById("btnConnect").addEventListener("click", function() {
|
||||
localStorage.setItem("data", "");
|
||||
if (connection) {
|
||||
stop();
|
||||
document.getElementById("result").innerText="0";
|
||||
lineCount=-1;
|
||||
}
|
||||
Puck.connect(function(c) {
|
||||
if (!c) {
|
||||
console.log("Couldn't connect!\n");
|
||||
return;
|
||||
}
|
||||
connection = c;
|
||||
var buf = "";
|
||||
connection.on("data", function(d) {
|
||||
buf += d;
|
||||
var l = buf.split("\n");
|
||||
buf = l.pop();
|
||||
l.forEach(onLine);
|
||||
});
|
||||
connection.write("reset();\n", function() {
|
||||
setTimeout(function() {
|
||||
connection.write("\x03\x10if(1){"+BANGLE_CODE+"}\n",
|
||||
function() { console.log("Ready..."); });
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function onLine(line) {
|
||||
console.log("RECEIVED:"+line);
|
||||
if (line.startsWith("DATA:")){
|
||||
localStorage.setItem("data", localStorage.getItem("data") + line.substr(5) + "\n");
|
||||
lineCount++;
|
||||
document.getElementById("result").innerText="Captured events: " + lineCount;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "hrmaccevents",
|
||||
"name": "HRM Accelerometer event recorder",
|
||||
"shortName": "HRM ACC recorder",
|
||||
"version": "0.01",
|
||||
"type": "ram",
|
||||
"description": "Record HRM and accelerometer events in high resolution to CSV files in your browser",
|
||||
"icon": "app.png",
|
||||
"tags": "debug",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": [ ]
|
||||
}
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Stopped watchface from flashing every interval
|
||||
0.03: Move to Bangle.setUI to launcher support
|
||||
0.04: Tweaks for compatibility with BangleJS2
|
||||
0.05: Time-word now readable on Bangle.js 2
|
||||
|
|
|
@ -46,7 +46,7 @@ const dy = big ? 22 : 16;
|
|||
const fontSize = big ? 3 : 2; // "6x8"
|
||||
const passivColor = 0x3186 /*grey*/ ;
|
||||
const activeColorNight = 0xF800 /*red*/ ;
|
||||
const activeColorDay = 0xFFFF /* white */;
|
||||
const activeColorDay = g.theme.fg;
|
||||
|
||||
var hidxPrev;
|
||||
var showDigitalTime = false;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "impwclock",
|
||||
"name": "Imprecise Word Clock",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.",
|
||||
"icon": "clock-impword.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: first release
|
||||
0.02: Themeable app icon
|
||||
0.03: Behave better on Bangle.js 1
|
||||
|
|
|
@ -5,7 +5,7 @@ let tStart;
|
|||
let tNow;
|
||||
let counter=-1;
|
||||
|
||||
const icon = require("heatshrink").decompress(atob("mEwwkBiIA/AH4A/AAkQgEBAREAC6oABdZQXkI6wuKC5iPUFxoXIOpoX/C6QFCC6IsCC6ZEDC/4XcPooXOFgoXQIgwX/C7IUFC5wsIC5ouCC6hcJC5h1DF9YwBChCPOAH4A/AH4Ap"));
|
||||
const icon = require("heatshrink").decompress(atob("mEwwI0xg+evPsAon+ApX8Aon4AonwAod78AFDv4FWvoFE/IFDz4FXvIFD3wFE/wFW7wFDh5xBAoUfAok/Aol/BZUXAogA6A="));
|
||||
|
||||
function timeToText(t) { // Courtesy of stopwatch app
|
||||
let hrs = Math.floor(t/3600000);
|
||||
|
@ -50,4 +50,5 @@ g.drawImage(icon,w/2-24,h/2-24);
|
|||
g.setFontAlign(0,0);
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12");
|
||||
g.drawString("Click button to count.", w/2, h/2+22);
|
||||
g.drawString("Click button 1 to count.", w/2, h/2+22);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "lapcounter",
|
||||
"name": "Lap Counter",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Click button to count laps. Shows count and total time snapshot (like a stopwatch, but laid back).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -35,6 +35,9 @@ Access different screens via tap on the left/ right side of the screen
|
|||

|
||||
|
||||
|
||||
# Ideas
|
||||
- Tap top / bottom to disable steps (also icon) and start a timer
|
||||
|
||||
## Contributors
|
||||
- [David Peer](https://github.com/peerdavid).
|
||||
- [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Add the option to enable touching the widget only on clock and settings.
|
|
@ -0,0 +1,102 @@
|
|||
# Light Switch Widget
|
||||
|
||||
Whis this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness.
|
||||
In addition it shows the lock status with the option to personalize the lock icon with a tiny image.
|
||||
|
||||
---
|
||||
### Control
|
||||
---
|
||||
* __On / off__
|
||||
Single touch the widget to en-/disable the backlight.
|
||||
* __Change brightness__ _(can be disabled)_
|
||||
First touch the widget, then quickly touch the screen again and drag up/down until you reach your wished brigthness.
|
||||
* __Double tap to flash backlight__ _(can be disabled)_
|
||||
By defaut you can double tap on the right side of your bangle to flash the backlight for a short duration.
|
||||
(While the backlight is active your bangle will be unlocked.)
|
||||
* __Double tap to unlock__ _(disabled by default)_
|
||||
If a side is defined in the app settings, your bangle will be unlocked if you double tap on that side.
|
||||
|
||||
---
|
||||
### Settings
|
||||
---
|
||||
#### Widget - Change the apperance of the widget:
|
||||
* __Bulb col__
|
||||
_red_ / _yellow_ / _green_ / __cyan__ / _blue_ / _magenta_
|
||||
Define the color used for the lightbulbs inner circle.
|
||||
The selected color will be dimmed depending on the actual brightness value.
|
||||
* __Image__
|
||||
__default__ / _random_ / _..._
|
||||
Set your favourite lock icon image. (If no image file is found _no image_ will be displayed.)
|
||||
* _random_ -> Select a random image on each time the widget is drawn.
|
||||
|
||||
#### Control - Change when and how to use the widget:
|
||||
* __Touch__
|
||||
_on def clk_ / _on all clk_ / _clk+setting_ / _clk+launch_ / _except apps_ / __always on__
|
||||
Select when touching the widget is active to en-/disable the backlight.
|
||||
* _on def clk_ -> only on your selected main clock face
|
||||
* _on all clk_ -> on all apps of the type _clock_
|
||||
* _clk+setting_ -> on all apps of the type _clock_ and in the settings
|
||||
* _clk+launch_ -> on all apps of the types _clock_ and _launch_
|
||||
* _except apps_ -> on all apps of the types _clock_ and _launch_ and in the settings
|
||||
* _always on_ -> always enabled when the widget is displayed
|
||||
* __Drag Delay__
|
||||
_off_ / _50ms_ / _100ms_ / _..._ / __500ms__ / _..._ / _1000ms_
|
||||
Change the maximum delay between first touch and re-touch/drag to change the brightness or disable changing the brightness completely.
|
||||
* __Min Value__
|
||||
_1%_ / _2%_ / _..._ / __10%__ / _..._ / _100%_
|
||||
Set the minimal level of brightness you can change to.
|
||||
|
||||
#### Unlock - Set double tap side to unlock:
|
||||
* __TapSide__
|
||||
__off__ / _left_ / _right_ / _top_ / _bottom_ / _front_ / _back_
|
||||
|
||||
#### Flash - Change if and how to flash the backlight:
|
||||
* __TapSide__
|
||||
_off_ / _left_ / __right__ / _top_ / _bottom_ / _front_ / _back_
|
||||
Set double tap side to flash the backlight or disable completely.
|
||||
* __Tap__
|
||||
_on locked_ / _on unlocked_ / __always on__
|
||||
Select when a double tap is recognised.
|
||||
* __Timeout__
|
||||
_0.5s_ / _1s_ / _..._ / __2s__ / _..._ / _10s_
|
||||
Change how long the backlight will be activated on a flash.
|
||||
* __Min Value__
|
||||
_1%_ / _2%_ / _..._ / __20%__ / _..._ / _100%_
|
||||
Set the minimal level of brightness for the backlight on a flash.
|
||||
|
||||
---
|
||||
### Images
|
||||
---
|
||||
|
||||
| Lightbulb | Default lock icon |
|
||||
|:-----------------------------:|:-----------------------:|
|
||||
|  |  |
|
||||
| ( _full_ / _dimmed_ / _off_ ) | ( _on_ / _off_ ) |
|
||||
|
||||
Examples in default light and dark theme.
|
||||
|
||||
| Lock | Heart | Invader | JS | Smiley | Skull | Storm |
|
||||
|:----:|:-----:|:-------:|:--:|:------:|:-----:|:-----:|
|
||||
|  |  |  |  |  |  |  |
|
||||
|
||||
This images are stored in a seperate file _(lightswitch.images.json)_.
|
||||
|
||||
---
|
||||
### Worth Mentioning
|
||||
---
|
||||
#### To do list
|
||||
* Catch the touch and draw input related to this widget to prevent actions in the active app.
|
||||
_(For now I have no idea how to achieve this, help is appreciated)_
|
||||
* Manage images for the lock icon through a _Customize and Upload App_ page.
|
||||
|
||||
#### Requests, Bugs and Feedback
|
||||
Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) or send me a [mail](mailto:banglejs@storm64.de).
|
||||
|
||||
#### Thanks
|
||||
Huge thanks to Gordon Williams and all the motivated developers.
|
||||
|
||||
#### Creator
|
||||
Storm64 ([Mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64))
|
||||
|
||||
#### License
|
||||
[MIT License](LICENSE)
|
|
@ -0,0 +1,17 @@
|
|||
// load settings
|
||||
var settings = Object.assign({
|
||||
value: 1,
|
||||
isOn: true
|
||||
}, require("Storage").readJSON("lightswitch.json", true) || {});
|
||||
|
||||
// set brightness
|
||||
Bangle.setLCDBrightness(settings.isOn ? settings.value : 0);
|
||||
|
||||
// remove tap listener to prevent uncertainties
|
||||
Bangle.removeListener("tap", require("lightswitch.js").tapListener);
|
||||
|
||||
// add tap listener to unlock and/or flash backlight
|
||||
if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
|
||||
|
||||
// clear variable
|
||||
settings = undefined;
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"lock": {
|
||||
"str": "BQcBAAEYxiA=",
|
||||
"x": 9,
|
||||
"y": 15,
|
||||
},
|
||||
"heart": {
|
||||
"str": "CQjBAQD4//+chAAACA4Pj+8=",
|
||||
"x": 7,
|
||||
"y": 14,
|
||||
},
|
||||
"invader": {
|
||||
"str": "DQqDASQASQASSEAAECSQEAEASQEkkkAQEgkgkAEkkkkkAgkkkggEEAAEEAAEgkAASQAAASQ=",
|
||||
"x": 5,
|
||||
"y": 13,
|
||||
},
|
||||
"js": {
|
||||
"str": "CAqBAd//2NfZ3tHfX78=",
|
||||
"x": 7,
|
||||
"y": 13,
|
||||
},
|
||||
"skull": {
|
||||
"str": "CQqBAcHAZTKcH/+OfMGfAA==",
|
||||
"x": 7,
|
||||
"y": 13,
|
||||
},
|
||||
"smiley": {
|
||||
"str": "CwqDASQAAASQNtsAQNttsANgMBsBsBgNgNtttsBsNsNgBsANgCBttgCSAAACQA==",
|
||||
"x": 6,
|
||||
"y": 13,
|
||||
},
|
||||
"storm": {
|
||||
"str": "CQmDASAAACBttgBgABgBttgCMAACQNsASRgASSBgCSSACSA=",
|
||||
"x": 7,
|
||||
"y": 13,
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# Light Switch Images
|
After Width: | Height: | Size: 944 B |
After Width: | Height: | Size: 936 B |
After Width: | Height: | Size: 773 B |
After Width: | Height: | Size: 803 B |
After Width: | Height: | Size: 776 B |
After Width: | Height: | Size: 923 B |
After Width: | Height: | Size: 777 B |
After Width: | Height: | Size: 789 B |
After Width: | Height: | Size: 773 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.7 KiB |
|
@ -0,0 +1,124 @@
|
|||
// from boot accassible functions
|
||||
exports = {
|
||||
// listener function //
|
||||
// tap listener to flash backlight
|
||||
tapListener: function(data) {
|
||||
// check for double tap and direction
|
||||
if (data.double) {
|
||||
// setup shortcut to this widget or load from storage
|
||||
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({
|
||||
unlockSide: "",
|
||||
tapSide: "right",
|
||||
tapOn: "always",
|
||||
}, require("Storage").readJSON("lightswitch.json", true) || {});
|
||||
|
||||
// cache lock status
|
||||
var locked = Bangle.isLocked();
|
||||
|
||||
// check to unlock
|
||||
if (locked && data.dir === w.unlockSide) Bangle.setLocked();
|
||||
|
||||
// check to flash
|
||||
if (data.dir === w.tapSide && (w.tapOn === "always" || locked === (w.tapOn === "locked"))) require("lightswitch.js").flash();
|
||||
|
||||
// clear variables
|
||||
w = undefined;
|
||||
locked = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
// external function //
|
||||
// function to flash backlight
|
||||
flash: function(tOut) {
|
||||
// setup shortcut to this widget or load from storage
|
||||
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({
|
||||
tOut: 3000,
|
||||
minFlash: 0.2,
|
||||
value: 1,
|
||||
isOn: true
|
||||
}, require("Storage").readJSON("lightswitch.json", true) || {});
|
||||
|
||||
// chack if locked, backlight off or actual value lower then minimal flash value
|
||||
if (Bangle.isLocked() || !w.isOn || w.value < w.minFlash) {
|
||||
|
||||
// set inner bulb and brightness
|
||||
var setBrightness = function(w, value) {
|
||||
if (w.drawInnerBulb) w.drawInnerBulb(value);
|
||||
Bangle.setLCDBrightness(value);
|
||||
};
|
||||
|
||||
// override timeout if defined
|
||||
if (!tOut) tOut = w.tOut;
|
||||
|
||||
// check lock state
|
||||
if (Bangle.isLocked()) {
|
||||
// cache options
|
||||
var options = Bangle.getOptions();
|
||||
// set shortened lock and backlight timeout
|
||||
Bangle.setOptions({
|
||||
lockTimeout: tOut,
|
||||
backlightTimeout: tOut
|
||||
});
|
||||
// unlock
|
||||
Bangle.setLocked(false);
|
||||
// set timeout to reset options
|
||||
setTimeout(Bangle.setOptions, tOut + 100, options);
|
||||
|
||||
// clear variable
|
||||
options = undefined;
|
||||
} else {
|
||||
// set timeout to reset backlight
|
||||
setTimeout((w, funct) => {
|
||||
if (!Bangle.isLocked()) funct(w, w.isOn ? w.value : 0);
|
||||
}, tOut, w, setBrightness);
|
||||
}
|
||||
|
||||
// enable backlight
|
||||
setTimeout((w, funct) => {
|
||||
funct(w, w.value < w.minFlash ? w.minFlash : w.value);
|
||||
}, 10, w, setBrightness);
|
||||
|
||||
// clear variable
|
||||
setBrightness = undefined;
|
||||
}
|
||||
|
||||
// clear variable
|
||||
w = undefined;
|
||||
},
|
||||
|
||||
// external access to internal function //
|
||||
// refference to widget function or set backlight and write to storage if not skipped
|
||||
changeValue: function(value, skipWrite) {
|
||||
// check if widgets are loaded
|
||||
if (global.WIDGETS) {
|
||||
// execute inside widget
|
||||
WIDGETS.lightswitch.changeValue(value, skipWrite);
|
||||
} else {
|
||||
// load settings from storage
|
||||
var filename = "lightswitch.json";
|
||||
var storage = require("Storage");
|
||||
var settings = Object.assign({
|
||||
value: 1,
|
||||
isOn: true
|
||||
}, storage.readJSON(filename, true) || {});
|
||||
|
||||
// check value
|
||||
if (value) {
|
||||
// set new value
|
||||
settings.value = value;
|
||||
} else {
|
||||
// switch backlight status
|
||||
settings.isOn = !settings.isOn;
|
||||
}
|
||||
// set brightness
|
||||
Bangle.setLCDBrightness(settings.isOn ? settings.value : 0);
|
||||
// write changes to storage if not skipped
|
||||
if (!skipWrite) storage.writeJSON(filename, settings);
|
||||
|
||||
// clear variables
|
||||
filename = undefined;
|
||||
storage = undefined;
|
||||
settings = undefined;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"id": "lightswitch",
|
||||
"name": "Light Switch Widget",
|
||||
"shortName": "Light Switch",
|
||||
"version": "0.02",
|
||||
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
|
||||
"icon": "images/app.png",
|
||||
"screenshots": [
|
||||
{"url": "images/screenshot_1.png"},
|
||||
{"url": "images/screenshot_2.png"},
|
||||
{"url": "images/screenshot_3.png"},
|
||||
{"url": "images/screenshot_4.png"}
|
||||
],
|
||||
"type": "widget",
|
||||
"tags": "tool,widget,brightness,lock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name": "lightswitch.boot.js", "url": "boot.js"},
|
||||
{"name": "lightswitch.js", "url": "lib.js"},
|
||||
{"name": "lightswitch.settings.js", "url": "settings.js"},
|
||||
{"name": "lightswitch.wid.js", "url": "widget.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name": "lightswitch.json"},
|
||||
{"name": "lightswitch.images.json", "url": "images.json"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
(function(back) {
|
||||
var filename = "lightswitch.json";
|
||||
|
||||
// set Storage and load settings
|
||||
var storage = require("Storage");
|
||||
var settings = Object.assign({
|
||||
colors: "011",
|
||||
image: "default",
|
||||
touchOn: "clock,launch",
|
||||
dragDelay: 500,
|
||||
minValue: 0.1,
|
||||
unlockSide: "",
|
||||
tapSide: "right",
|
||||
tapOn: "always",
|
||||
tOut: 2000,
|
||||
minFlash: 0.2
|
||||
}, storage.readJSON(filename, true) || {});
|
||||
var images = storage.readJSON(filename.replace(".", ".images."), true) || false;
|
||||
|
||||
// write change to storage and widget
|
||||
function writeSetting(key, value, drawWidgets) {
|
||||
// reread settings to only change key
|
||||
settings = Object.assign(settings, storage.readJSON(filename, true) || {});
|
||||
// change the value of key
|
||||
settings[key] = value;
|
||||
// write to storage
|
||||
storage.writeJSON(filename, settings);
|
||||
// check if widgets are loaded
|
||||
if (global.WIDGETS) {
|
||||
// setup shortcut to the widget
|
||||
var w = WIDGETS.lightswitch;
|
||||
// assign changes to widget
|
||||
w = Object.assign(w, settings);
|
||||
// redraw widgets if neccessary
|
||||
if (drawWidgets) Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
// generate entry for circulating values
|
||||
function getEntry(key) {
|
||||
var entry = entries[key];
|
||||
// check for existing titles to decide value type
|
||||
if (entry.value) {
|
||||
// return entry for string value
|
||||
return {
|
||||
value: entry.value.indexOf(settings[key]),
|
||||
format: v => entry.title ? entry.title[v] : entry.value[v],
|
||||
onchange: function(v) {
|
||||
this.value = v = v >= entry.value.length ? 0 : v < 0 ? entry.value.length - 1 : v;
|
||||
writeSetting(key, entry.value[v], entry.drawWidgets);
|
||||
if (entry.exec) entry.exec(entry.value[v]);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// return entry for numerical value
|
||||
return {
|
||||
value: settings[key] * entry.factor,
|
||||
step: entry.step,
|
||||
format: v => v > 0 ? v + entry.unit : "off",
|
||||
onchange: function(v) {
|
||||
this.value = v = v > entry.max ? entry.min : v < entry.min ? entry.max : v;
|
||||
writeSetting(key, v / entry.factor, entry.drawWidgets);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// define menu entries with circulating values
|
||||
var entries = {
|
||||
colors: {
|
||||
title: ["red", "yellow", "green", "cyan", "blue", "magenta"],
|
||||
value: ["100", "110", "010", "011", "001", "101"],
|
||||
drawWidgets: true
|
||||
},
|
||||
image: {
|
||||
title: images ? undefined : ["no found"],
|
||||
value: images ? ["default", "random"].concat(Object.keys(images)) : ["default"],
|
||||
exec: function(value) {
|
||||
// draw selected image in upper right corner
|
||||
var x = 152,
|
||||
y = 26,
|
||||
i = images ? images[value] : false;
|
||||
g.reset();
|
||||
if (!i) g.setColor(g.theme.bg);
|
||||
g.drawImage(atob("Dw+BADAYYDDAY//v////////////////////////3/8A"), x + 4, y);
|
||||
if (i) g.drawImage(atob(i.str), x + i.x, y - 9 + i.y);
|
||||
i = undefined;
|
||||
}
|
||||
},
|
||||
touchOn: {
|
||||
title: ["on def clk", "on all clk", "clk+launch", "clk+setting", "except apps", "always on"],
|
||||
value: ["", "clock", "clock,setting.app.js", "clock,launch", "clock,setting.app.js,launch", "always"],
|
||||
drawWidgets: true
|
||||
},
|
||||
dragDelay: {
|
||||
factor: 1,
|
||||
unit: "ms",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
step: 50
|
||||
},
|
||||
minValue: {
|
||||
factor: 100,
|
||||
unit: "%",
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1
|
||||
},
|
||||
unlockSide: {
|
||||
title: ["off", "left", "right", "top", "bottom", "front", "back"],
|
||||
value: ["", "left", "right", "top", "bottom", "front", "back"]
|
||||
},
|
||||
tapOn: {
|
||||
title: ["on locked", "on unlocked", "always on"],
|
||||
value: ["locked", "unlocked", "always"]
|
||||
},
|
||||
tOut: {
|
||||
factor: 0.001,
|
||||
unit: "s",
|
||||
min: 0.5,
|
||||
max: 10,
|
||||
step: 0.5
|
||||
}
|
||||
};
|
||||
// copy duplicated entries
|
||||
entries.tapSide = entries.unlockSide;
|
||||
entries.minFlash = entries.minValue;
|
||||
|
||||
// show main menu
|
||||
function showMain() {
|
||||
var mainMenu = E.showMenu({
|
||||
"": {
|
||||
title: "Light Switch"
|
||||
},
|
||||
"< Back": () => back(),
|
||||
"-- Widget --------": 0,
|
||||
"Bulb col": getEntry("colors"),
|
||||
"Image": getEntry("image"),
|
||||
"-- Control -------": 0,
|
||||
"Touch": getEntry("touchOn"),
|
||||
"Drag Delay": getEntry("dragDelay"),
|
||||
"Min Value": getEntry("minValue"),
|
||||
"-- Unlock --------": 0,
|
||||
"TapSide": getEntry("unlockSide"),
|
||||
"-- Flash ---------": 0,
|
||||
"TapSide ": getEntry("tapSide"),
|
||||
"Tap": getEntry("tapOn"),
|
||||
"Timeout": getEntry("tOut"),
|
||||
"Min Value ": getEntry("minFlash")
|
||||
});
|
||||
}
|
||||
|
||||
// draw main menu
|
||||
showMain();
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
/*** Available settings for lightswitch ***
|
||||
|
||||
* colors: string // colors used for the bulb
|
||||
// set with g.setColor(val*col[0], val*col[1], val*col[2])
|
||||
"100" -> red
|
||||
"110" -> yellow
|
||||
"010" -> green
|
||||
"011" -> cyan (default)
|
||||
"001" -> blue
|
||||
"101" -> magenta
|
||||
|
||||
* image: string //
|
||||
"default" ->
|
||||
"random" ->
|
||||
|
||||
* touchOn: string // select when widget touch is active
|
||||
"" -> only on default clock
|
||||
"clock" -> on all clocks
|
||||
"clock,launch" -> on all clocks and lanchers (default)
|
||||
"always" -> always
|
||||
|
||||
* dragDelay: int // drag listener reset time in ms
|
||||
// time until a drag is needed to activate backlight changing mode
|
||||
0 -> disabled
|
||||
500 -> (default)
|
||||
|
||||
* minValue: float // minimal brightness level that can be set by dragging
|
||||
0.05 to 1, 0.1 as default
|
||||
|
||||
* unlockSide: string // side of the watch to double tap on to flash backlight
|
||||
0/false/undefined -> backlight flash disabled
|
||||
right/left/up/down/front/back -> side to tap on (default: right)
|
||||
|
||||
* tapSide: string // side of the watch to double tap on to flash backlight
|
||||
0/false/undefined -> backlight flash disabled
|
||||
right/left/up/down/front/back -> side to tap on (default: right)
|
||||
|
||||
* tapOn: string // select when tap to flash backlight is active
|
||||
"locked" -> only when locked
|
||||
"unlocked" -> only when unlocked (default)
|
||||
"always" -> always
|
||||
|
||||
* tOut: int // backlight flash timeout in ms
|
||||
3000 (default)
|
||||
|
||||
* minFlash: float // minimal brightness level when
|
||||
0.05 to 1, 0.2 as default
|
||||
|
||||
*** Cached values ***
|
||||
|
||||
* value: float // active brightness value (0-1)
|
||||
1 (default)
|
||||
* isOn: bool // active backlight status
|
||||
true (default)
|
||||
|
||||
*/
|
||||
{
|
||||
// settings
|
||||
"colors": "011",
|
||||
"image": "default",
|
||||
"touchOn": "clock,launch",
|
||||
"dragDelay": 500,
|
||||
"minValue": 0.1,
|
||||
"unlockSide": "",
|
||||
"tapSide": "right",
|
||||
"tapOn": "always",
|
||||
"tOut": 2000,
|
||||
"minFlash": 0.2,
|
||||
// cached values
|
||||
"value": 1,
|
||||
"isOn": true
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
(function() {
|
||||
// load settings
|
||||
var settings = Object.assign({
|
||||
colors: "011",
|
||||
image: "default",
|
||||
touchOn: "clock,launch",
|
||||
dragDelay: 500,
|
||||
minValue: 0.1,
|
||||
unlockSide: "",
|
||||
tapSide: "right",
|
||||
tapOn: "always",
|
||||
tOut: 3000,
|
||||
value: 1,
|
||||
isOn: true
|
||||
}, require("Storage").readJSON("lightswitch.json", true) || {});
|
||||
|
||||
// write widget with loaded settings
|
||||
WIDGETS.lightswitch = Object.assign(settings, {
|
||||
|
||||
// set area, sortorder, width and dragStatus
|
||||
area: "tr",
|
||||
sortorder: 10,
|
||||
width: 23,
|
||||
dragStatus: "off",
|
||||
|
||||
// internal function //
|
||||
// write settings to storage
|
||||
writeSettings: function(changes) {
|
||||
// define variables
|
||||
var filename = "lightswitch.json";
|
||||
var storage = require("Storage");
|
||||
|
||||
// write changes into json file
|
||||
storage.writeJSON(filename, Object.assign(
|
||||
storage.readJSON(filename, true) || {}, changes
|
||||
));
|
||||
|
||||
// clear variables
|
||||
filename = undefined;
|
||||
storage = undefined;
|
||||
},
|
||||
|
||||
// internal function //
|
||||
// draw inner bulb circle
|
||||
drawInnerBulb: function(value) {
|
||||
// check if active or value is set
|
||||
if (value || this.isOn) {
|
||||
// use set value or load from widget
|
||||
value = value || this.value;
|
||||
// calculate color
|
||||
g.setColor(
|
||||
value * this.colors[0],
|
||||
value * this.colors[1],
|
||||
value * this.colors[2]
|
||||
);
|
||||
} else {
|
||||
// backlight off
|
||||
g.setColor(0);
|
||||
}
|
||||
// draw circle
|
||||
g.drawImage(atob("CwuBAB8H8f9/////////f8fwfAA="), this.x + 6, this.y + 6);
|
||||
},
|
||||
|
||||
// internal function //
|
||||
// draw widget icon
|
||||
drawIcon: function(locked) {
|
||||
// define icons
|
||||
var icons = {
|
||||
bulb: "DxSBAAAAD4BgwYDCAIgAkAEgAkAEgAiAIYDBgwH8A/gH8A/gH8AfABwA",
|
||||
shine: "FxeBAAgQIAgggBBBABAECAAALAABhAAEAAAAAAAAAAAAAAAHAABwAAAAAAAAAAAAAAAQABDAABoAAAgQBABABACACAIACAA=",
|
||||
lock: "DxCBAAAAH8B/wMGBgwMGBgwf/H/8+Pnx8/fn78/fn/8f/A==",
|
||||
image: "DxSBAA/gP+Dg4YDDAYYDDAYYDH/9////////////////////////+//g"
|
||||
};
|
||||
// read images
|
||||
var images = require("Storage").readJSON("lightswitch.images.json", true) || false;
|
||||
|
||||
// select image if images are found
|
||||
var image = (!images || image === "default") ? false :
|
||||
(function(i) {
|
||||
if (i === "random") {
|
||||
i = Object.keys(images);
|
||||
i = i[parseInt(Math.random() * i.length)];
|
||||
}
|
||||
return images[i];
|
||||
})(this.image);
|
||||
|
||||
// clear widget area
|
||||
g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 24);
|
||||
|
||||
// draw shine if backlight is active
|
||||
if (this.isOn) g.drawImage(atob(icons.shine), this.x, this.y);
|
||||
|
||||
// draw icon depending on lock status and image
|
||||
g.drawImage(atob(!locked ? icons.bulb : image ? icons.image : icons.lock), this.x + 4, this.y + 4);
|
||||
|
||||
// draw image on lock
|
||||
if (locked && image) g.drawImage(atob(image.str), this.x + image.x, this.y + image.y);
|
||||
|
||||
// draw bulb color depending on backlight status
|
||||
if (!locked) this.drawInnerBulb();
|
||||
|
||||
// clear variables
|
||||
icons = undefined;
|
||||
images = undefined;
|
||||
image = undefined;
|
||||
},
|
||||
|
||||
// internal function //
|
||||
// change or switch backlight and icon and write to storage if not skipped
|
||||
changeValue: function(value, skipWrite) {
|
||||
// check value
|
||||
if (value) {
|
||||
// set new value
|
||||
this.value = value;
|
||||
// check backlight status
|
||||
if (this.isOn) {
|
||||
// redraw only inner bulb circle
|
||||
this.drawInnerBulb();
|
||||
} else {
|
||||
// activate backlight
|
||||
this.isOn = true;
|
||||
// redraw complete widget icon
|
||||
this.drawIcon(false);
|
||||
}
|
||||
} else {
|
||||
// switch backlight status
|
||||
this.isOn = !this.isOn;
|
||||
// redraw widget icon
|
||||
this.drawIcon(false);
|
||||
}
|
||||
// set brightness
|
||||
Bangle.setLCDBrightness(this.isOn ? this.value : 0);
|
||||
// write changes to storage if not skipped
|
||||
if (!skipWrite) this.writeSettings({
|
||||
isOn: this.isOn,
|
||||
value: this.value
|
||||
});
|
||||
},
|
||||
|
||||
// listener function //
|
||||
// drag listener for brightness change mode
|
||||
dragListener: function(event) {
|
||||
// setup shortcut to this widget
|
||||
var w = WIDGETS.lightswitch;
|
||||
|
||||
// first drag recognised
|
||||
if (event.b && typeof w.dragStatus === "number") {
|
||||
// reset drag timeout
|
||||
clearTimeout(w.dragStatus);
|
||||
// change drag status to indicate ongoing drag action
|
||||
w.dragStatus = "ongoing";
|
||||
// feedback for brightness change mode
|
||||
Bangle.buzz(50);
|
||||
}
|
||||
|
||||
// read y position, pleasant usable area 20-170
|
||||
var y = event.y;
|
||||
y = y < 20 ? 0 : y > 170 ? 150 : y - 20;
|
||||
// calculate brightness respecting minimal value in settings
|
||||
var value = (1 - Math.round(y / 1.5) / 100) * (1 - w.minValue) + w.minValue;
|
||||
|
||||
// change brigthness value, skip write to storage while still touching
|
||||
w.changeValue(value, event.b);
|
||||
|
||||
// on touch release remove drag listener and reset drag status to indicate stopped drag action
|
||||
if (!event.b) {
|
||||
Bangle.removeListener("drag", w.dragListener);
|
||||
w.dragStatus = "off";
|
||||
}
|
||||
|
||||
// clear variables
|
||||
w = undefined;
|
||||
y = undefined;
|
||||
value = undefined;
|
||||
},
|
||||
|
||||
// listener function //
|
||||
// touch listener for light control
|
||||
touchListener: function(button, cursor) {
|
||||
// setup shortcut to this widget
|
||||
var w = WIDGETS.lightswitch;
|
||||
|
||||
// skip all if drag action ongoing
|
||||
if (w.dragStatus === "off") {
|
||||
|
||||
// check if inside widget area
|
||||
if (!(!w || cursor.x < w.x || cursor.x > w.x + w.width ||
|
||||
cursor.y < w.y || cursor.y > w.y + 23)) {
|
||||
// first touch feedback
|
||||
Bangle.buzz(25);
|
||||
// check if drag is disabled
|
||||
if (w.dragDelay) {
|
||||
// add drag listener
|
||||
Bangle.on("drag", w.dragListener);
|
||||
// set drag timeout
|
||||
w.dragStatus = setTimeout((w) => {
|
||||
// remove drag listener
|
||||
Bangle.removeListener("drag", w.dragListener);
|
||||
// clear drag timeout
|
||||
if (typeof w.dragStatus === "number") clearTimeout(w.dragStatus);
|
||||
// reset drag status to indicate stopped drag action
|
||||
w.dragStatus = "off";
|
||||
}, w.dragDelay, w);
|
||||
}
|
||||
// switch backlight
|
||||
w.changeValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// clear variable
|
||||
w = undefined;
|
||||
},
|
||||
|
||||
// main widget function //
|
||||
// display and setup/reset function
|
||||
draw: function(locked) {
|
||||
// setup shortcut to this widget
|
||||
var w = WIDGETS.lightswitch;
|
||||
|
||||
// set lcd brightness on unlocking
|
||||
// all other cases are catched by the boot file
|
||||
if (locked === false) Bangle.setLCDBrightness(w.isOn ? w.value : 0);
|
||||
|
||||
// read lock status
|
||||
locked = Bangle.isLocked();
|
||||
|
||||
// remove listeners to prevent uncertainties
|
||||
Bangle.removeListener("lock", w.draw);
|
||||
Bangle.removeListener("touch", w.touchListener);
|
||||
Bangle.removeListener("tap", require("lightswitch.js").tapListener);
|
||||
|
||||
// draw widget icon
|
||||
w.drawIcon(locked);
|
||||
|
||||
// add lock listener
|
||||
Bangle.on("lock", w.draw);
|
||||
|
||||
// add touch listener to control the light depending on settings
|
||||
if (w.touchOn === "always" || !global.__FILE__ ||
|
||||
w.touchOn.includes(__FILE__) ||
|
||||
w.touchOn.includes(require("Storage").readJSON(__FILE__.replace("app.js", "info")).type))
|
||||
Bangle.on("touch", w.touchListener);
|
||||
|
||||
// add tap listener to unlock and/or flash backlight
|
||||
if (w.unlockSide || w.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
|
||||
|
||||
// clear variables
|
||||
w = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// clear variable
|
||||
settings = undefined;
|
||||
})()
|
|
@ -0,0 +1,3 @@
|
|||
0.01: Launch app.
|
||||
0.02: 12k steps are 360 degrees - improves readability of steps.
|
||||
0.03: Battery improvements through sleep (no minute updates) and partial updates of drawing.
|
|
@ -0,0 +1,33 @@
|
|||
# Not Analog
|
||||
An analog watch face for people (like me) that can not read analog watch faces.
|
||||
It looks like an analog clock, but its not! It shows the time digital - check the
|
||||
4 numbers on the watch face ;)
|
||||
|
||||
The red hand shows the number of steps (12k steps = 360 degrees) and the
|
||||
black one the battery level (100% = 360 degrees).
|
||||
The selected theme is also respected. Note that this watch face is in fullscreen
|
||||
mode, but widgets are still loaded in background.
|
||||
|
||||
## Other features
|
||||
- Set a timer - simply touch top (+5min.) or bottom (-5 min.).
|
||||
- If the weather is available through the weather app, the outside temp. will be shown.
|
||||
- Sleep modus at midnight to save more battery (no minute updates).
|
||||
- Icons for charging and GPS.
|
||||
- If you have done more than 10k steps, the red hand and icon will turn green.
|
||||
- Shows current lock status of your bangle va a colored dot in the middle.
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
# Thanks
|
||||
Thanks to the multiclock from https://github.com/jeffmer/BangleApps/
|
||||
which helped a lot for this development.
|
||||
|
||||
Icons from <a href="https://www.flaticon.com/free-icons" title="icons">by Freepik - Flaticon</a>
|
||||
|
||||
## Contributors
|
||||
- [David Peer](https://github.com/peerdavid).
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "notanalog",
|
||||
"name": "Not Analog",
|
||||
"shortName":"Not Analog",
|
||||
"icon": "notanalog.png",
|
||||
"version":"0.03",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "An analog watch face for people that can not read analog watch faces.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"screenshots": [
|
||||
{"url":"screenshot_1.png"},
|
||||
{"url":"screenshot_2.png"}
|
||||
],
|
||||
"storage": [
|
||||
{"name":"notanalog.app.js","url":"notanalog.app.js"},
|
||||
{"name":"notanalog.img","url":"notanalog.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
/**
|
||||
* NOT ANALOG CLOCK
|
||||
*/
|
||||
|
||||
const locale = require('locale');
|
||||
const storage = require('Storage')
|
||||
const SETTINGS_FILE = "notanalog.setting.json";
|
||||
let settings = {
|
||||
alarm: -1,
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
var state = {
|
||||
color: "#ff0000",
|
||||
steps: 0,
|
||||
maxSteps: 10000,
|
||||
bat: 0,
|
||||
has_weather: false,
|
||||
temp: "-",
|
||||
sleep: false,
|
||||
}
|
||||
|
||||
var chargeImg = {
|
||||
width : 32, height : 32, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("AAAMAAAAHgAAADMAAABjAAAAxgAAD44AAB8cAAA7uAAAcfAMAODgPgDAcHMBgDjjAYAdxgGBj4wBg8cYAYZjsAGGYeABg8DgAQGAYAMAAOAHgAHAB8ADgAzgBwAYc/4AGD/4AAw4AAAOcAAAH8AAADmAAABwAAAA4AAAAMAAAAA="))
|
||||
};
|
||||
|
||||
var alarmImg = {
|
||||
width : 32, height : 32, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("AA/wAAAP8AAAD/AAAAGAAAABgAAAA8AABh/4YAd//uAH+B/gA+AHwAOAAcAHAPDgDgD4cA4A/HAcAP44HAD+OBwA/zgYAP8YGAD/GBj//xgc//84HH/+OBx//jgOP/xwDh/4cAcP8OAHg8HgA8ADwAHwD4AA//8AAD/8AAAP8AA="))
|
||||
};
|
||||
|
||||
var stepsImg = {
|
||||
width : 32, height : 32, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("AcAAAAPwAAAH8AAAB/gAAAf4AAAH/AAAD/wAAAf8AAAH/AfAB/wP4Af8H+AH/B/gB/wf4AP8P+AD+D/gAfg/4AGAP+AAPD/gAPw/4AD+P+AAfj/AAH4/wAB+H8AAPAeAAAAwAAAAPgAAAH8AAAB/AAAAfgAAAH4AAAA8AAAAOAA="))
|
||||
};
|
||||
|
||||
var gpsImg = {
|
||||
width : 32, height : 32, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("AAAMAAAAD4AAAAHAAAAA4AAADjABAA8YAYADmAPAAcwD4DzMB/B8zAf4fAAH/HwAB/74AAf/wAAH/4AAB//AAAP/4AAD//AAA//4AAH//AAA//4AAH//AAA//4ABH/4AAYP4AAHgAAAB/AAAA/4AAAP+AAAD/gAAP//gAD//4AA="))
|
||||
};
|
||||
|
||||
var sleepImg = {
|
||||
width : 128, height : 128, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("ABk//+AB5l///AB5wfDh4kIF4s/8AgIj4ED//wB5E+AYUB//8B5F8AYUD+F+B5H4AYUH8E/Bw8BHIcHwEfMA4PEh4RBQo8DNIYPBIIIPGDAkeEwJGDAAaZEB4MAOAisB+COEngCBOAn///4NAgPCMAgfCZ4gPCaIpWBd4l4QQZtFD4gPCgYPEQw3wRo41FgHxfw5tEB4sHfg7DC8IPDFQb8DB4XgB4ZDDWosD4DNCbAbsEB4zRDB5bRDfghKDB4bRCRwwPBuAFCbISOCgP/EYMPK4kPDgKOCgbiBDIJLDEoIYBRwQPD//DD4hQBbgPgF4QCB84PDBgICCDgJTBEQP/B4QFCwAIDKYIRB/84bQX/x+AD4YPCwF+nguC+B9FMYJuBngPBIgKmCeQoPEg5dBB4ryBB4kPPoMfdohRCB4McSYPAg5dBeQoPCjxOBCIIPBcQYUBL4N4j0B/hQBAATPBV4RnB/EegYFB//AbYYPCgfh+EeZgJNDAYYWBCQUedgN/NoUD/xhDEwUOj67BBQd/IAIFEh8+gZ3CNQMfSQkMBQN8g/wMATKBCQIAEh/4IAMPdoQlCB4vwn7sC/5OBSIQPE8F+KoRoBfIIPFPwP8cASyBQoIPG4JABJQUHAoJwEBAODIAUBAIIlBOAg/BgfgcAMDBYN+A4IPFC4I+BB4U/wKAFh8PwJ5BB4SFBB40fFANggPAg5nBSAsPzwwBDIRGB+F8L4v+NAIZCh8B+E8B4v8RAN4AwMOgH4jwPEY4M+gEwB4d8UA34E4sAn0PA4pHGgEeWApHBfA8HB4vgQ4oPBw4PF8IPGbALQEgfB8IXF4/DB4vD8YHG4LgEEwPDA4oPIA4w3BA4pWBF4poGdAJOEAAQPFQwyoDB4q2GB6VwB5twvAFDhwPIvAPFhwPNjwPTgaSDBwgPBj//wH//6qCnAPI4IPEvgPY4APEngPGjxPOL5KvER4gPFV5IPKZ4gPEZ4oPJd5QPF+APEg+AB5kHB5+HB40B8APFwfBVgIPCgeB8K0CB4fDB4kH4YXCLQfDB4oHBB43B8ZABB4UB4/DKgYPCCwRPDHAIPEKwgPDh+HB434B4yIDQwbGCB4ceB434ngPFnzIDewc+gEwB4MEgF8j4PFA4V4B4MOE4MeB4s8h+AB4QsBG4YADI4PA+APCgfwvgPFj8D8FwB4L2B8BnCAAcPwKQBL4UPEoIPFFwP8B4cfCwQPGvwPDv42BB4oHBn+AB4MB/gXBB4sB/Ef8BPC/B2BB4sADIP8B4M/8CeGAAN+gP/4fB//AWwIAGn5LB/4ABEwIPHj/Aj4OB/BGBB46ZBgYPBKAJ+GOAQZBj4sBEoIPHgP+Aod/Nw4KCDQQUFKAw6Ch5eIKAX/FYP/JxArCPwQSCABM/BwI+KGAYuLEAYeGA="))
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* 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}));
|
||||
};
|
||||
|
||||
// The following font was used:
|
||||
// <link href="https://fonts.googleapis.com/css2?family=Staatliches&display=swap" rel="stylesheet">
|
||||
Graphics.prototype.setTimeFont = function(scale) {
|
||||
// Actual height 26 (26 - 1)
|
||||
this.setFontCustom(atob("AAAAAAAAAD4AAAAD4AAAAD4AAAAD4AAAAB4AAAAAAAAAAAAAAAAD4AAAD/4AAD//4AD///4Af///gAf//gAAf/gAAAfwAAAAQAAAAAAAAAAAAAAAAAB//+AAH///gAP///wAP///wAfwAP4AfAAD4AfAAD4AfAAD4AfAAD4AfAAD4AfgAH4AP///wAP///wAH///gAB//+AAAP/wAAAAAAAAAAAAAAf///4Af///4Af///4Af///4Af///4AAAAAAAAAAAAAAAAAAAB+AD4AH+AP4AP+Af4AP+B/4AfwD/4AfAH/4AfAf74AfA/z4AfD/j4Af/+D4AP/8D4AH/wD4AD/gD4AB+AD4AAAAAAAB8B8AAD8B/AAH8B/gAP8B/wAf8A/4AfAAD4AfB8D4AfB8D4AfD8D4Af3/P4AP///wAP///wAH///gAB/H+AAAAAAAAAAAAAAAAB+AAAAP+AAAA/+AAAD/+AAAP/+AAA/8+AAD/w+AAf/A+AAf///4Af///4Af///4Af///4AD///4AAAA+AAAAA+AAAAAAAAAAAYAAf/8/AAf/8/gAf/8/wAf/8/wAfD4H4AfD4D4AfD4D4AfD4D4AfD8H4AfD//4AfB//wAfA//gAeAf/AAAAH8AAAAAAAAAAAAAAB//+AAD///AAH///gAP///wAf///4AfD4D4AfD4D4AfD4D4AfD4D4Afz+P4AP5//wAP5//wAH4//gAB4P+AAAAAAAAAAAAAAfAAAAAfAAAAAfAAAIAfAAB4AfAAP4AfAB/4AfAP/4AfA//4AfH//AAf//4AAf/+AAAf/wAAAf+AAAAfwAAAAAAAAAAAAAAAAB8P+AAH///gAP///wAP///wAf/+P4AfH4D4AfD4D4AfD4D4Afn8D4Af///4AP///wAH///gAD/f/AAA4P+AAAAAAAAAAAQAAB/w+AAH/4/gAP/8/wAP/+/wAfh+P4AfA/D4AfAfD4AfAfD4AfA+H4Af///4AP///wAH///gAD///AAA//8AAAAAAAAAAAAAAAB8D4AAB8D4AAB8D4AAB8D4AAA8B4AAAAAAA=="), 46, atob("BwsSCBAQEBAQEBAQBw=="), 36+(scale<<8)+(1<<16));
|
||||
return this;
|
||||
};
|
||||
|
||||
Graphics.prototype.setNormalFont = function(scale) {
|
||||
// Actual height 19 (18 - 0)
|
||||
this.setFontCustom(atob("AAAAAAAAAAAAAAD/5wP/3A/+cAAAAPwAA/AADAAAAgAA/AAD8AAIAAAAAAAxgADGAA/+AD/4ADGAAMYAD/4AP/gAMYAAxgAB84AP7wD3jwPHPA+f8A+/AB54AAAAB+AAP8BAwwcDnHwP8/AfPwAD8AA/AAPxwB+fgPh/A4GMCAfwAA+AAAAA+/AH/+A//8DjhwOOHA4/8Dj/wAP/AA4AADgAAAAAAAAD8AAPwAAwAAAAAAB/4Af/4D//wPAPA4AcDgBwAAAAAAADgBwOAHA//8B//gD/8AD/AAoAAGwAA/gAD+AAHwAAbAAAAAAAAAAAAAABgAAGAAAYAA/+AD/4AAYAABgAAGAAAYAAAByAAH4AAfAAAAAGAAA4AADgAAOAAA4AAAAAAAAAAAAAABwAAHAAAcAAAAAA/AD/8D//gP+AA8AAAAAAD/8Af/4D//wOAHA4AcDgBwPAPA//8B//gB/4AAAAAAAAP//A//8D//wAAAADAMA8DwHw/A+H8Dg/wOP3A/8cB/hwD4HAAAAAYEAHw8AfD4D4HwOOHA44cD//wH/+APvwAAAAAB4AAfgAH+AB/4AfjgD//wP//A//8AAOAAA4AAAAD/vAP++A/58DnBwOcHA5/8Dj/gOH8AAHAA//AH/+A//8DnhwOcHA548D7/wHv+AOPgAAAAOAAA4AADgBwOA/A4f8Dv/AP/gA/wAD4AAAAAAACAA9/AH/+A//8DnBwOcHA//8B//gD38AAHAA/HAH++A/98DhzwOHHA4c8D//wH/+AP/wAAAAAAAAA4cADhwAOHAA4cgDh+AOHwAAAABgAAPAAB8AAH4AA5wAHDgAYGAAAQAAAAAGYAAZgABmAAGYAAZgABmAAGYAAZgABmAAAAAAAQAGDgAcOAA5wAB+AAHwAAPAAAYAAAAADwAAfgAD8AAOD3A4fcDz9wP+AAfwAAcAAAAAAP/wB//gP//A4A8Dn5wOf3A5/cD/9wH/3AP+cAABwAAAAAH8AP/wP//A/84D/zgP//AH/8AAfwAABAAAAD//wP//A//8DjhwOOHA488D//wH/+APngA//AH/+A//8DgBwOAHA8A8D8PwHw+AHDgAAAAAAAA//8D//wP//A4AcDgBwPAPA//8B//gD/4AAAAD//wP//A//8DjhwOOHA44cDjhwOOHAAAAD//wP//A//8DjgAOOAA44ADjgAOOAAP/wB//gP//A4AcDhxwOHPA/f8B9/gDn4AAAAAAAAP//A//8D//wAOAAA4AADgAP//A//8D//wAAAA//8D//wP//AAAAAAPAAA+AAD8AABwAAHAAA8D//wP/+A//gAAAAAAAA//8D//wP//AD8AA/8AH/8A+H8DgHwIAHAAAAD//wP//A//8AABwAAHAAAcAABwAAHAAAAD//wP//A//8D8AAD4AAPgAB8AAP//A//8D//wAAAAAAAD//wP//Af/8Af4AAf4A//4D//wP//AAAAA//AH/+A//8DgBwOAHA4A8D//wH/+AP/wAAAAAAAA//8D//wP//A4cADhwAOPAA/8AB/gAD4AAP/wB//gP//A4AcDgBwPAPA//8B//4D//gAAEAAAAP//A//8D//wOOAA44ADjwAP//Af/8A+fwAAAAPjwB/PgP+/A48cDhxwPn/Afv4B+fgBw4AAAADgAAOAAA4AAD//wP//A//8DgAAOAAA4AAD//AP/+A//8AABwAAHAAA8D//wP/+A//wAAAAPAAA/4AD//gA//AAP8Af/wP/8A/4ADgAAAAAA4AAD/gAP//AH/8AB/wP//A//AD//gB//AAP8D//wP/4A/gACAAAOAHA/D8D//wB/4AH/gD//wPx/A4AcAAAAMAAA+AAD/AAD//AB/8B//wP8AA+AADAAAOAfA4H8Dh/wOf3A/8cD/BwPwHA8AcAAAAP//A//8D//wOAHA4AcDgBwAAAAAAAD8AAP/wAf/8AD/wAAPAAAAAAAAOAHA4AcDgBwP//A//8D//wA=="), 32, atob("AwQJCggPCwUHBwgKBAcEBwsFCgoKCgoKCgoEBAkKCQoMCQoKCgkJCgoFCgoJDAoKCgoLCgkKCg4JCQkHBwc="), 22+(scale<<8)+(1<<16));
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
|
||||
function getSteps() {
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom.getSteps();
|
||||
}
|
||||
} catch(ex) {
|
||||
// In case we failed, we can only show 0 steps.
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
function drawBackground() {
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setNormalFont();
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
for (let a=0;a<360;a+=6){
|
||||
if (a % 30 == 0 || (a > 345 || a < 15) || (a > 90-15 && a < 90+15) || (a > 180-15 && a < 180+15) || (a > 270-15 && a < 270+15)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var theta=a*Math.PI/180;
|
||||
g.drawLine(cx,cy,cx+125*Math.sin(theta),cy-125*Math.cos(theta));
|
||||
}
|
||||
|
||||
g.clearRect(10,10,W-10,H-10);
|
||||
for (let a=0;a<360;a+=30){
|
||||
if(a == 0 || a == 90 || a == 180 || a == 270){
|
||||
continue;
|
||||
}
|
||||
g.drawRotRect(6,R-80,125,a);
|
||||
}
|
||||
|
||||
g.clearRect(16,16,W-16,H-16);
|
||||
}
|
||||
|
||||
|
||||
function drawState(){
|
||||
g.setFontAlign(1,0,0);
|
||||
|
||||
// Draw alarm
|
||||
var highPrioImg = isAlarmEnabled() ? alarmImg :
|
||||
Bangle.isCharging() ? chargeImg :
|
||||
Bangle.isGPSOn() ? gpsImg :
|
||||
undefined;
|
||||
|
||||
var imgColor = isAlarmEnabled() ? state.color :
|
||||
Bangle.isCharging() ? g.theme.fg :
|
||||
Bangle.isGPSOn() ? g.theme.fg :
|
||||
state.color;
|
||||
|
||||
// As default, we draw weather if available, otherwise the steps symbol is shown.
|
||||
if(!highPrioImg && state.has_weather){
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(state.temp, cx+cx/2+15, cy+cy/2+10);
|
||||
} else {
|
||||
g.setColor(imgColor);
|
||||
var img = highPrioImg ? highPrioImg : stepsImg;
|
||||
g.drawImage(img, cx+cx/2 - img.width/2 + 5, cy+cy/2 - img.height/2+5);
|
||||
}
|
||||
}
|
||||
|
||||
function drawData() {
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setNormalFont();
|
||||
|
||||
// Set hand functions
|
||||
var drawBatteryHand = g.drawRotRect.bind(g,6,12,R-38);
|
||||
var drawDataHand = g.drawRotRect.bind(g,5,12,R-24);
|
||||
|
||||
// Draw battery hand
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontAlign(0,0,0);
|
||||
drawBatteryHand(parseInt(state.bat*360/100));
|
||||
|
||||
// Draw data hand - depending on state
|
||||
g.setColor(state.color);
|
||||
if(isAlarmEnabled()){
|
||||
var alrm = getAlarmMinutes();
|
||||
drawDataHand(parseInt(alrm*360/60));
|
||||
return;
|
||||
}
|
||||
|
||||
// Default are the steps
|
||||
drawDataHand(parseInt(state.steps*360/12000));
|
||||
}
|
||||
|
||||
function drawTextCleared(s, x, y){
|
||||
g.clearRect(x-15, y-22, x+15, y+15);
|
||||
g.drawString(s, x, y);
|
||||
}
|
||||
|
||||
|
||||
function drawTime(){
|
||||
g.setTimeFont();
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
var posX = 14;
|
||||
var posY = 14;
|
||||
|
||||
// Hour
|
||||
var h = state.currentDate.getHours();
|
||||
var h1 = parseInt(h / 10);
|
||||
var h2 = h < 10 ? h : h - h1*10;
|
||||
drawTextCleared(h1, cx, posY+8);
|
||||
drawTextCleared(h2, W-posX, cy+5);
|
||||
|
||||
// Minutes
|
||||
var m = state.currentDate.getMinutes();
|
||||
var m1 = parseInt(m / 10);
|
||||
var m2 = m < 10 ? m : m - m1*10;
|
||||
drawTextCleared(m2, cx, H-posY);
|
||||
drawTextCleared(m1, posX-1, cy+5);
|
||||
}
|
||||
|
||||
|
||||
function drawDate(){
|
||||
// Date
|
||||
g.setFontAlign(-1,0,0);
|
||||
g.setNormalFont();
|
||||
g.setColor(g.theme.fg);
|
||||
var dayStr = locale.dow(state.currentDate, true).toUpperCase();
|
||||
g.drawString(dayStr, cx/2-15, cy/2-5);
|
||||
g.drawString(state.currentDate.getDate(), cx/2-15, cy/2+17);
|
||||
}
|
||||
|
||||
|
||||
function drawLock(){
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(cx, cy, 7);
|
||||
|
||||
var c = Bangle.isLocked() ? state.color : g.theme.bg;
|
||||
g.setColor(c);
|
||||
g.fillCircle(cx, cy, 4);
|
||||
}
|
||||
|
||||
|
||||
function handleState(fastUpdate){
|
||||
state.currentDate = new Date();
|
||||
|
||||
/*
|
||||
* Sleep modus
|
||||
*/
|
||||
var minutes = state.currentDate.getMinutes();
|
||||
var hours = state.currentDate.getHours();
|
||||
if(!isAlarmEnabled() && fastUpdate && hours == 00 && minutes == 01){
|
||||
state.sleep = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set steps
|
||||
state.steps = getSteps();
|
||||
|
||||
// Color based on state
|
||||
state.color = isAlarmEnabled() ? "#FF6A00" :
|
||||
state.steps > state.maxSteps ? "#00ff00" :
|
||||
"#ff0000";
|
||||
|
||||
/*
|
||||
* 5 Minute updates
|
||||
*/
|
||||
if(minutes % 5 == 0 && fastUpdate){
|
||||
return;
|
||||
}
|
||||
|
||||
// Set battery
|
||||
state.bat = E.getBattery();
|
||||
|
||||
// Set weather
|
||||
state.has_weather = true;
|
||||
try {
|
||||
weather = require('weather').get();
|
||||
if (weather === undefined){
|
||||
state.has_weather = false;
|
||||
state.temp = "-";
|
||||
} else {
|
||||
state.temp = locale.temp(Math.round(weather.temp-273.15));
|
||||
}
|
||||
} catch(ex) {
|
||||
state.has_weather = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawSleep(){
|
||||
g.reset();
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
drawBackground();
|
||||
|
||||
g.setColor(1,1,1);
|
||||
g.drawImage(sleepImg, cx - sleepImg.width/2, cy- sleepImg.height/2);
|
||||
}
|
||||
|
||||
|
||||
function draw(fastUpdate){
|
||||
// Execute handlers
|
||||
handleState(fastUpdate);
|
||||
handleAlarm();
|
||||
|
||||
if(state.sleep){
|
||||
drawSleep();
|
||||
// We don't queue draw again - so its sleeping until
|
||||
// the user presses the btn again.
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear watch face
|
||||
if(fastUpdate){
|
||||
var innerRect = 20;
|
||||
g.clearRect(innerRect, innerRect, g.getWidth()-innerRect, g.getHeight()-innerRect);
|
||||
} else {
|
||||
g.reset();
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
// Draw again
|
||||
g.setColor(1,1,1);
|
||||
|
||||
if(!fastUpdate){
|
||||
drawBackground();
|
||||
}
|
||||
|
||||
drawDate();
|
||||
drawLock();
|
||||
drawState();
|
||||
drawTime();
|
||||
drawData();
|
||||
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Listeners
|
||||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(true);
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('charging',function(charging) {
|
||||
draw(true);
|
||||
});
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if(state.sleep){
|
||||
state.sleep=false;
|
||||
draw(false);
|
||||
} else {
|
||||
drawLock();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('touch', function(btn, e){
|
||||
var upper = parseInt(g.getHeight() * 0.2);
|
||||
var lower = g.getHeight() - upper;
|
||||
|
||||
var is_upper = e.y < upper;
|
||||
var is_lower = e.y > lower;
|
||||
|
||||
if(is_upper){
|
||||
feedback();
|
||||
increaseAlarm();
|
||||
draw(true);
|
||||
}
|
||||
|
||||
if(is_lower){
|
||||
feedback();
|
||||
decreaseAlarm();
|
||||
draw(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Some helpers
|
||||
*/
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw(true);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle alarm
|
||||
*/
|
||||
function getCurrentTimeInMinutes(){
|
||||
return Math.floor(Date.now() / (1000*60));
|
||||
}
|
||||
|
||||
function isAlarmEnabled(){
|
||||
return settings.alarm >= 0;
|
||||
}
|
||||
|
||||
function getAlarmMinutes(){
|
||||
var currentTime = getCurrentTimeInMinutes();
|
||||
return settings.alarm - currentTime;
|
||||
}
|
||||
|
||||
function handleAlarm(){
|
||||
if(!isAlarmEnabled()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(getAlarmMinutes() > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
// Alarm
|
||||
var t = 300;
|
||||
Bangle.buzz(t, 1)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
|
||||
.then(() => {
|
||||
// Update alarm state to disabled
|
||||
settings.alarm = -1;
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function increaseAlarm(){
|
||||
if(isAlarmEnabled()){
|
||||
settings.alarm += 5;
|
||||
} else {
|
||||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||
}
|
||||
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
|
||||
function decreaseAlarm(){
|
||||
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
||||
settings.alarm -= 5;
|
||||
} else {
|
||||
settings.alarm = -1;
|
||||
}
|
||||
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
function feedback(){
|
||||
Bangle.buzz(40, 0.6);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(false);
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
// Bangle.drawWidgets();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkE/4AM+cikMRkU/CZoWDkMAgMQgESDB4WBgMSkcykMQDB8xgMjAwcyiETFxoPHD4IwMBxAgBG4gLFCYMgHxExgQXI+USEoMvBhBIJ+URmQMJERQKBiI8BmQZHKRJTBgETmURC48xC5PxgERaBPxga9KgDnJ+KQJKYJFIQoQXKOhAvK+cRgBeBC5ZfF+QVBAAacKBQgWGAALNIX4iJCAA0Bd5kwCw4ABWw3ygJrC+YWJAAJeGRwboBIQhMFj5GFLwcgCAoeFW4kxIwf/IAoXGgARCmQuEUgwXHiczCwMCFwfwC5sBfIMRYwilGC5MSkaTEagwXImbbGC54WGRwwXIbIwXh+YXVh6YHC453GN4IwFO5AXGJAIwFgQXHHwwwHgYXH+AXGGAxnBAAyfHGAwdBAAyfHCQaOKAAMgGBEhOxRIKGYoAJC5YWKVJClLbRjsJAAvyC48vC5v/mJ0RYgyiCiU/CyAASA=="))
|
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -1,3 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Set pace format to mm:ss, time format to h:mm:ss,
|
||||
added settings to opt out of GPS and HRM
|
||||
0.03: Fixed distance calculation, tested against Garmin Etrex, Amazfit GTS 2
|
||||
0.04: Use the exstats module, and make what is displayed configurable
|
||||
|
|
|
@ -28,7 +28,24 @@ so if you have no GPS lock you just need to wait.
|
|||
However you can just install the `Recorder` app, turn recording on in
|
||||
that, and then start the `Run` app.
|
||||
|
||||
## Settings
|
||||
|
||||
Under `Settings` -> `App` -> `Run` you can change settings for this app.
|
||||
|
||||
* `Pace` is the distance that pace should be shown over - 1km, 1 mile, 1/2 Marathon or 1 Marathon
|
||||
* `Box 1/2/3/4/5/6` are what should be shown in each of the 6 boxes on the display. From the top left, down.
|
||||
If you set it to `-` nothing will be displayed, so you can display only 4 boxes of information
|
||||
if you wish by setting the last 2 boxes to `-`.
|
||||
|
||||
## TODO
|
||||
|
||||
* Allow this app to trigger the `Recorder` app on and off directly.
|
||||
* Keep a log of each run's stats (distance/steps/etc)
|
||||
|
||||
## Development
|
||||
|
||||
This app uses the [`exstats` module](/modules/exstats.js). When uploaded via the
|
||||
app loader, the module is automatically included in the app's source. However
|
||||
when developing via the IDE the module won't get pulled in by default.
|
||||
|
||||
There are some options to fix this easily - please check out the [modules README.md file](/modules/README.md)
|
||||
|
|
182
apps/run/app.js
|
@ -1,67 +1,37 @@
|
|||
var ExStats = require("exstats");
|
||||
var B2 = process.env.HWVERSION==2;
|
||||
var Layout = require("Layout");
|
||||
var locale = require("locale");
|
||||
var fontHeading = "6x8:2";
|
||||
var fontValue = B2 ? "6x15:2" : "6x8:3";
|
||||
var headingCol = "#888";
|
||||
var running = false;
|
||||
var startTime;
|
||||
var startSteps;
|
||||
// This & previous GPS readings
|
||||
var lastGPS, thisGPS;
|
||||
var distance = 0; ///< distance in meters
|
||||
var startSteps = Bangle.getStepCount(); ///< number of steps when we started
|
||||
var lastStepCount = startSteps; // last time 'step' was called
|
||||
var stepHistory = new Uint8Array(60); // steps each second for the last minute (0 = current minute)
|
||||
var fixCount = 0;
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// ---------------------------
|
||||
|
||||
function formatTime(ms) {
|
||||
let hrs = Math.floor(ms/3600000).toString();
|
||||
let mins = (Math.floor(ms/60000)%60).toString();
|
||||
let secs = (Math.floor(ms/1000)%60).toString();
|
||||
|
||||
if (hrs === '0')
|
||||
return mins.padStart(2,0)+":"+secs.padStart(2,0);
|
||||
else
|
||||
return hrs+":"+mins.padStart(2,0)+":"+secs.padStart(2,0); // dont pad hours
|
||||
}
|
||||
|
||||
// Format speed in meters/second
|
||||
function formatPace(speed) {
|
||||
if (speed < 0.1667) {
|
||||
return `__:__`;
|
||||
}
|
||||
const pace = Math.round(1000 / speed); // seconds for 1km
|
||||
const min = Math.floor(pace / 60); // minutes for 1km
|
||||
const sec = pace % 60;
|
||||
return ('0' + min).substr(-2) + `:` + ('0' + sec).substr(-2);
|
||||
}
|
||||
|
||||
let settings = Object.assign({
|
||||
B1 : "dist",
|
||||
B2 : "time",
|
||||
B3 : "pacea",
|
||||
B4 : "bpm",
|
||||
B5 : "step",
|
||||
B6 : "caden",
|
||||
paceLength : 1000
|
||||
}, require("Storage").readJSON("run.json", 1) || {});
|
||||
var statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!="");
|
||||
var exs = ExStats.getStats(statIDs, settings);
|
||||
// ---------------------------
|
||||
|
||||
function clearState() {
|
||||
distance = 0;
|
||||
startSteps = Bangle.getStepCount();
|
||||
stepHistory.fill(0);
|
||||
layout.dist.label=locale.distance(distance);
|
||||
layout.time.label="00:00";
|
||||
layout.pace.label=formatPace(0);
|
||||
layout.hrm.label="--";
|
||||
layout.steps.label=0;
|
||||
layout.cadence.label= "0";
|
||||
layout.status.bgCol = "#f00";
|
||||
}
|
||||
|
||||
// Called to start/stop running
|
||||
function onStartStop() {
|
||||
running = !running;
|
||||
var running = !exs.state.active;
|
||||
if (running) {
|
||||
clearState();
|
||||
startTime = Date.now();
|
||||
exs.start();
|
||||
} else {
|
||||
exs.stop();
|
||||
}
|
||||
layout.button.label = running ? "STOP" : "START";
|
||||
layout.status.label = running ? "RUN" : "STOP";
|
||||
|
@ -71,94 +41,44 @@ function onStartStop() {
|
|||
layout.render();
|
||||
}
|
||||
|
||||
var lc = [];
|
||||
// Load stats in pair by pair
|
||||
for (var i=0;i<statIDs.length;i+=2) {
|
||||
var sa = exs.stats[statIDs[i+0]];
|
||||
var sb = exs.stats[statIDs[i+1]];
|
||||
lc.push({ type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol },
|
||||
{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontValue, label:sa.getString(), id:sa.id, fillx:1 },
|
||||
{type:"txt", font:fontValue, label:sb.getString(), id:sb.id, fillx:1 }
|
||||
]});
|
||||
sa.on('changed', e=>layout[e.id].label = e.getString());
|
||||
sb.on('changed', e=>layout[e.id].label = e.getString());
|
||||
}
|
||||
// At the bottom put time/GPS state/etc
|
||||
lc.push({ type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" },
|
||||
{type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg },
|
||||
{type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 }
|
||||
]});
|
||||
// Now calculate the layout
|
||||
var layout = new Layout( {
|
||||
type:"v", c: [
|
||||
{ type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontHeading, label:"DIST", fillx:1, col:headingCol },
|
||||
{type:"txt", font:fontHeading, label:"TIME", fillx:1, col:headingCol }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontValue, label:"0.00", id:"dist", fillx:1 },
|
||||
{type:"txt", font:fontValue, label:"00:00", id:"time", fillx:1 }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontHeading, label:"PACE", fillx:1, col:headingCol },
|
||||
{type:"txt", font:fontHeading, label:"HEART", fillx:1, col:headingCol }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontValue, label:`__'__"`, id:"pace", fillx:1 },
|
||||
{type:"txt", font:fontValue, label:"--", id:"hrm", fillx:1 }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontHeading, label:"STEPS", fillx:1, col:headingCol },
|
||||
{type:"txt", font:fontHeading, label:"CADENCE", fillx:1, col:headingCol }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontValue, label:"0", id:"steps", fillx:1 },
|
||||
{type:"txt", font:fontValue, label:"0", id:"cadence", fillx:1 }
|
||||
]}, { type:"h", filly:1, c:[
|
||||
{type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" },
|
||||
{type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg },
|
||||
{type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 }
|
||||
]},
|
||||
|
||||
]
|
||||
type:"v", c: lc
|
||||
},{lazy:true, btns:[{ label:"START", cb: onStartStop, id:"button"}]});
|
||||
clearState();
|
||||
delete lc;
|
||||
layout.render();
|
||||
|
||||
|
||||
|
||||
function onTimer() {
|
||||
layout.clock.label = locale.time(new Date(),1);
|
||||
if (!running) {
|
||||
layout.render();
|
||||
return;
|
||||
}
|
||||
// called once a second
|
||||
var duration = Date.now() - startTime; // in ms
|
||||
// set cadence based on steps over last minute
|
||||
var stepsInMinute = E.sum(stepHistory);
|
||||
var cadence = 60000 * stepsInMinute / Math.min(duration,60000);
|
||||
// update layout
|
||||
layout.time.label = formatTime(duration);
|
||||
layout.steps.label = Bangle.getStepCount()-startSteps;
|
||||
layout.cadence.label = Math.round(cadence);
|
||||
layout.render();
|
||||
// move step history onwards
|
||||
stepHistory.set(stepHistory,1);
|
||||
stepHistory[0]=0;
|
||||
}
|
||||
|
||||
// Handle GPS state change for icon
|
||||
Bangle.on("GPS", function(fix) {
|
||||
layout.gps.bgCol = fix.fix ? "#0f0" : "#f00";
|
||||
lastGPS = thisGPS;
|
||||
thisGPS = fix;
|
||||
if (running && fix.fix && lastGPS.fix) {
|
||||
// work out distance - moving from a to b
|
||||
var a = Bangle.project(lastGPS);
|
||||
var b = Bangle.project(thisGPS);
|
||||
var dx = a.x-b.x, dy = a.y-b.y;
|
||||
var d = Math.sqrt(dx*dx+dy*dy); // this should be the distance in meters
|
||||
distance += d;
|
||||
layout.dist.label=locale.distance(distance);
|
||||
var duration = Date.now() - startTime; // in ms
|
||||
var speed = distance * 1000 / duration; // meters/sec
|
||||
layout.pace.label = formatPace(speed);
|
||||
if (!fix.fix) return; // only process actual fixes
|
||||
if (fixCount++ == 0) {
|
||||
Bangle.buzz(); // first fix, does not need to respect quiet mode
|
||||
}
|
||||
});
|
||||
Bangle.on("HRM", function(h) {
|
||||
layout.hrm.label = h.bpm;
|
||||
});
|
||||
Bangle.on("step", function(steps) {
|
||||
if (running) {
|
||||
layout.steps.label = steps-Bangle.getStepCount();
|
||||
stepHistory[0] += steps-lastStepCount;
|
||||
}
|
||||
lastStepCount = steps;
|
||||
});
|
||||
|
||||
let settings = require("Storage").readJSON('run.json',1)||{"use_gps":true,"use_hrm":true};
|
||||
|
||||
// We always call ourselves once a second, if only to update the time
|
||||
setInterval(onTimer, 1000);
|
||||
|
||||
/* Turn GPS and HRM on right at the start to ensure
|
||||
we get the highest chance of a lock. */
|
||||
if (settings.use_hrm) Bangle.setHRMPower(true,"app");
|
||||
if (settings.use_gps) Bangle.setGPSPower(true,"app");
|
||||
// We always call ourselves once a second to update
|
||||
setInterval(function() {
|
||||
layout.clock.label = locale.time(new Date(),1);
|
||||
layout.render();
|
||||
}, 1000);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "run",
|
||||
"name": "Run",
|
||||
"version":"0.02",
|
||||
"version":"0.04",
|
||||
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors,gps",
|
||||
|
|
|
@ -1,44 +1,50 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "run.json";
|
||||
|
||||
// initialize with default settings...
|
||||
let s = {
|
||||
'use_gps': true,
|
||||
'use_hrm': true
|
||||
}
|
||||
var ExStats = require("exstats");
|
||||
var statsList = ExStats.getList();
|
||||
statsList.unshift({name:"-",id:""}); // add blank menu item
|
||||
var statsIDs = statsList.map(s=>s.id);
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||
const saved = settings || {}
|
||||
for (const key in saved) {
|
||||
s[key] = saved[key]
|
||||
}
|
||||
|
||||
function save() {
|
||||
settings = s
|
||||
let settings = Object.assign({
|
||||
B1 : "dist",
|
||||
B2 : "time",
|
||||
B3 : "pacea",
|
||||
B4 : "bpm",
|
||||
B5 : "step",
|
||||
B6 : "caden",
|
||||
paceLength : 1000
|
||||
}, storage.readJSON(SETTINGS_FILE, 1) || {});
|
||||
function saveSettings() {
|
||||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Run' },
|
||||
'< Back': back,
|
||||
'Use GPS': {
|
||||
value: s.use_gps,
|
||||
format: () => (s.use_gps ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.use_gps = !s.use_gps;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Use HRM': {
|
||||
value: s.use_hrm,
|
||||
format: () => (s.use_hrm ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.use_hrm = !s.use_hrm;
|
||||
save();
|
||||
function getBoxChooser(boxID) {
|
||||
return {
|
||||
min :0, max: statsIDs.length-1,
|
||||
value: Math.max(statsIDs.indexOf(settings[boxID]),0),
|
||||
format: v => statsList[v].name,
|
||||
onchange: v => {
|
||||
settings[boxID] = statsIDs[v];
|
||||
saveSettings();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var menu = {
|
||||
'': { 'title': 'Run' },
|
||||
'< Back': back
|
||||
};
|
||||
ExStats.appendMenuItems(menu, settings, saveSettings);
|
||||
Object.assign(menu,{
|
||||
'Box 1': getBoxChooser("B1"),
|
||||
'Box 2': getBoxChooser("B2"),
|
||||
'Box 3': getBoxChooser("B3"),
|
||||
'Box 4': getBoxChooser("B4"),
|
||||
'Box 5': getBoxChooser("B5"),
|
||||
'Box 6': getBoxChooser("B6"),
|
||||
});
|
||||
E.showMenu(menu);
|
||||
})
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
0.02: Corrected variable initialisation
|
||||
0.03: Advertise app name, added screenshots
|
||||
0.04: Advertise bar, GPS, HRM and mag services
|
||||
0.05: Refactored for efficiency, corrected sensor value inaccuracies
|
||||
0.05: Refactored for efficiency, corrected sensor value inaccuracies
|
||||
0.06: User settings are written to persistent storage, loaded on app start
|
|
@ -5,7 +5,9 @@ Collect all the sensor data from the Bangle.js 2, display the live readings in m
|
|||
|
||||
## Usage
|
||||
|
||||
The advertising packets will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the standard packet types. Also convenient for testing individual sensors of the Bangle.js 2 via the menu interface.
|
||||
The advertising packets will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the standard packet types. See our [Bangle.js Development Guide](https://reelyactive.github.io/diy/banglejs-dev/) for details. Also convenient for testing individual sensors of the Bangle.js 2 via the menu interface.
|
||||
|
||||

|
||||
|
||||
|
||||
## Features
|
||||
|
@ -22,7 +24,7 @@ in the menu display, and broadcasts all sensor data readings _except_ accelerati
|
|||
|
||||
## Controls
|
||||
|
||||
Browse and control sensors using the standard Espruino menu interface.
|
||||
Browse and control sensors using the standard Espruino menu interface. By default, all sensors _except_ the accelerometer are disabled. Sensors can be individually enabled/disabled via the menu. These settings are written to persistent storage (flash) and will be applied each time the SensiBLE app is loaded.
|
||||
|
||||
|
||||
## Requests
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
{
|
||||
"id": "sensible",
|
||||
"name": "SensiBLE",
|
||||
"shortName": "SensiBLE",
|
||||
"version": "0.05",
|
||||
"description": "Collect, display and advertise real-time sensor data.",
|
||||
"icon": "sensible.png",
|
||||
"screenshots": [
|
||||
{ "url": "screenshot-top.png" },
|
||||
{ "url": "screenshot-acc.png" },
|
||||
{ "url": "screenshot-bar.png" },
|
||||
{ "url": "screenshot-gps.png" },
|
||||
{ "url": "screenshot-hrm.png" },
|
||||
{ "url": "screenshot-mag.png" }
|
||||
],
|
||||
"type": "app",
|
||||
"tags": "tool,sensors",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "sensible.app.js", "url": "sensible.js" },
|
||||
{ "name": "sensible.img", "url": "sensible-icon.js", "evaluate": true }
|
||||
]
|
||||
"id": "sensible",
|
||||
"name": "SensiBLE",
|
||||
"shortName": "SensiBLE",
|
||||
"version": "0.06",
|
||||
"description": "Collect, display and advertise real-time sensor data.",
|
||||
"icon": "sensible.png",
|
||||
"screenshots": [
|
||||
{ "url": "screenshot-top.png" },
|
||||
{ "url": "screenshot-acc.png" },
|
||||
{ "url": "screenshot-bar.png" },
|
||||
{ "url": "screenshot-gps.png" },
|
||||
{ "url": "screenshot-hrm.png" },
|
||||
{ "url": "screenshot-mag.png" }
|
||||
],
|
||||
"type": "app",
|
||||
"tags": "tool,sensors,bluetooth",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "sensible.app.js", "url": "sensible.js" },
|
||||
{ "name": "sensible.img", "url": "sensible-icon.js", "evaluate": true }
|
||||
],
|
||||
"data": [
|
||||
{ "name": "sensible.data.json", "url": "settings.json", "storageFile": true }
|
||||
]
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 45 KiB |
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright reelyActive 2021
|
||||
* Copyright reelyActive 2021-2022
|
||||
* We believe in an open Internet of Things
|
||||
*/
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
|||
// Non-user-configurable constants
|
||||
const APP_ID = 'sensible';
|
||||
const ESPRUINO_COMPANY_CODE = 0x0590;
|
||||
const SETTINGS_FILENAME = 'sensible.data.json';
|
||||
const UPDATE_MILLISECONDS = 1000;
|
||||
const APP_ADVERTISING_DATA = [ 0x12, 0xff, 0x90, 0x05, 0x7b, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x3a, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x62,
|
||||
0x6c, 0x65, 0x7d ];
|
||||
|
@ -19,16 +21,12 @@ let isBarMenu = false;
|
|||
let isGpsMenu = false;
|
||||
let isHrmMenu = false;
|
||||
let isMagMenu = false;
|
||||
let isBarEnabled = true;
|
||||
let isGpsEnabled = true;
|
||||
let isHrmEnabled = true;
|
||||
let isMagEnabled = true;
|
||||
let isNewAccData = false;
|
||||
let isNewBarData = false;
|
||||
let isNewGpsData = false;
|
||||
let isNewHrmData = false;
|
||||
let isNewMagData = false;
|
||||
|
||||
let settings = require('Storage').readJSON(SETTINGS_FILENAME);
|
||||
|
||||
|
||||
// Menus
|
||||
|
@ -51,9 +49,9 @@ let accMenu = {
|
|||
let barMenu = {
|
||||
"": { "title" : "- Barometer -" },
|
||||
"State": {
|
||||
value: isBarEnabled,
|
||||
value: settings.isBarEnabled,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => { isBarEnabled = v; Bangle.setBarometerPower(v, APP_ID); }
|
||||
onchange: v => { updateSetting('isBarEnabled', v); }
|
||||
},
|
||||
"Altitude": { value: null },
|
||||
"Press": { value: null },
|
||||
|
@ -63,9 +61,9 @@ let barMenu = {
|
|||
let gpsMenu = {
|
||||
"": { "title" : "- GPS -" },
|
||||
"State": {
|
||||
value: isGpsEnabled,
|
||||
value: settings.isGpsEnabled,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => { isGpsEnabled = v; Bangle.setGPSPower(v, APP_ID); }
|
||||
onchange: v => { updateSetting('isGpsEnabled', v); }
|
||||
},
|
||||
"Lat": { value: null },
|
||||
"Lon": { value: null },
|
||||
|
@ -77,9 +75,9 @@ let gpsMenu = {
|
|||
let hrmMenu = {
|
||||
"": { "title" : "- Heart Rate -" },
|
||||
"State": {
|
||||
value: isHrmEnabled,
|
||||
value: settings.isHrmEnabled,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => { isHrmEnabled = v; Bangle.setHRMPower(v, APP_ID); }
|
||||
onchange: v => { updateSetting('isHrmEnabled', v); }
|
||||
},
|
||||
"BPM": { value: null },
|
||||
"Confidence": { value: null },
|
||||
|
@ -88,9 +86,9 @@ let hrmMenu = {
|
|||
let magMenu = {
|
||||
"": { "title" : "- Magnetometer -" },
|
||||
"State": {
|
||||
value: isMagEnabled,
|
||||
value: settings.isMagEnabled,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => { isMagEnabled = v; Bangle.setCompassPower(v, APP_ID); }
|
||||
onchange: v => { updateSetting('isMagEnabled', v); }
|
||||
},
|
||||
"x": { value: null },
|
||||
"y": { value: null },
|
||||
|
@ -124,7 +122,7 @@ function transmitUpdatedSensorData() {
|
|||
isNewMagData = false;
|
||||
}
|
||||
|
||||
let interval = 1000 / data.length;
|
||||
let interval = UPDATE_MILLISECONDS / data.length;
|
||||
NRF.setAdvertising(data, { showName: false, interval: interval });
|
||||
}
|
||||
|
||||
|
@ -190,6 +188,23 @@ function toByteArray(value, numberOfBytes, isSigned) {
|
|||
}
|
||||
|
||||
|
||||
// Enable the sensors as per the current settings
|
||||
function enableSensors() {
|
||||
Bangle.setBarometerPower(settings.isBarEnabled, APP_ID);
|
||||
Bangle.setGPSPower(settings.isGpsEnabled, APP_ID);
|
||||
Bangle.setHRMPower(settings.isHrmEnabled, APP_ID);
|
||||
Bangle.setCompassPower(settings.isMagEnabled, APP_ID);
|
||||
}
|
||||
|
||||
|
||||
// Update the given setting and write to persistent storage
|
||||
function updateSetting(name, value) {
|
||||
settings[name] = value;
|
||||
require('Storage').writeJSON(SETTINGS_FILENAME, settings);
|
||||
enableSensors();
|
||||
}
|
||||
|
||||
|
||||
// Update acceleration
|
||||
Bangle.on('accel', function(newAcc) {
|
||||
acc = newAcc;
|
||||
|
@ -260,9 +275,6 @@ Bangle.on('mag', function(newMag) {
|
|||
|
||||
// On start: enable sensors and display main menu
|
||||
g.clear();
|
||||
Bangle.setBarometerPower(isBarEnabled, APP_ID);
|
||||
Bangle.setGPSPower(isGpsEnabled, APP_ID);
|
||||
Bangle.setHRMPower(isHrmEnabled, APP_ID);
|
||||
Bangle.setCompassPower(isMagEnabled, APP_ID);
|
||||
enableSensors();
|
||||
E.showMenu(mainMenu);
|
||||
setInterval(transmitUpdatedSensorData, 1000);
|
||||
setInterval(transmitUpdatedSensorData, UPDATE_MILLISECONDS);
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"isBarEnabled": false,
|
||||
"isGpsEnabled": false,
|
||||
"isHrmEnabled": false,
|
||||
"isMagEnabled": false
|
||||
}
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Use queueDraw(), update every minute, respect theme, use Lato font
|
||||
0.05: Decided against custom font as it inceases the code size
|
||||
minimalism is useful when narrowing down issues
|
||||
0.06: renamed some files
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "simplest",
|
||||
"name": "Simplest Clock",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "The simplest working clock, acts as a tutorial piece",
|
||||
"icon": "simplest.png",
|
||||
"screenshots": [{"url":"screenshot_simplest.png"}],
|
||||
|
@ -9,7 +9,7 @@
|
|||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"simplest.app.js","url":"app.js"},
|
||||
{"name":"simplest.img","url":"icon.js","evaluate":true}
|
||||
{"name":"simplest.app.js","url":"simplest.app.js"},
|
||||
{"name":"simplest.img","url":"simplest.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ function draw() {
|
|||
g.setFontAlign(0, 0);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(timeStr, w/2, h/2);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
@ -42,13 +41,12 @@ Bangle.on('lcdPower',on=>{
|
|||
g.clear();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
//Bangle.setUI("clock");
|
||||
// Bangle.setUI("clock");
|
||||
// use clockupdown as it tests for issue #1249
|
||||
Bangle.setUI("clockupdown", btn=> {
|
||||
draw();
|
||||
});
|
||||
|
||||
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|