Merge branch 'espruino:master' into master
|
@ -1,4 +1,26 @@
|
||||||
const lintExemptions = require("./lint_exemptions.js");
|
const lintExemptions = require("./apps/lint_exemptions.js");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
function findGeneratedJS(roots) {
|
||||||
|
function* listFiles(dir, allow) {
|
||||||
|
for (const f of fs.readdirSync(dir)) {
|
||||||
|
const filepath = path.join(dir, f);
|
||||||
|
const stat = fs.statSync(filepath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
yield* listFiles(filepath, allow);
|
||||||
|
} else if(allow(filepath)) {
|
||||||
|
yield filepath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return roots.flatMap(root =>
|
||||||
|
[...listFiles(root, f => f.endsWith(".ts"))]
|
||||||
|
.map(f => f.replace(/\.ts$/, ".js"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"env": {
|
"env": {
|
||||||
|
@ -195,9 +217,32 @@ module.exports = {
|
||||||
"no-control-regex" : "off"
|
"no-control-regex" : "off"
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.ts"],
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
rules: {
|
||||||
|
"no-delete-var": "off",
|
||||||
|
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||||
|
"no-prototype-builtins": "off",
|
||||||
|
"prefer-const": "off",
|
||||||
|
"prefer-rest-params": "off",
|
||||||
|
"no-control-regex" : "off",
|
||||||
|
"@typescript-eslint/no-delete-var": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-this-alias": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
}
|
||||||
|
},
|
||||||
...Object.entries(lintExemptions).map(([filePath, {rules}]) => ({
|
...Object.entries(lintExemptions).map(([filePath, {rules}]) => ({
|
||||||
files: [filePath],
|
files: [filePath],
|
||||||
rules: Object.fromEntries(rules.map(rule => [rule, "off"])),
|
rules: Object.fromEntries(rules.map(rule => [rule, "off"])),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
|
ignorePatterns: findGeneratedJS(["apps/", "modules/"]),
|
||||||
}
|
}
|
|
@ -6,6 +6,8 @@ Bangle.js App Loader (and Apps)
|
||||||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||||
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
||||||
|
|
||||||
|
The release version is manually refreshed with regular intervals while the development version is continuously updated as new code is committed to this repository.
|
||||||
|
|
||||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||||
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||||
and that it is not licensed in another way that would make this impossible.
|
and that it is not licensed in another way that would make this impossible.
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
// timeout used to update every minute
|
{
|
||||||
let drawTimeout;
|
// timeout used to update every minute
|
||||||
|
let drawTimeout;
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next minute
|
||||||
function queueDraw() {
|
let queueDraw = function() {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = setTimeout(function() {
|
drawTimeout = setTimeout(function() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
draw();
|
draw();
|
||||||
}, 60000 - (Date.now() % 60000));
|
}, 60000 - (Date.now() % 60000));
|
||||||
}
|
};
|
||||||
|
|
||||||
function draw() {
|
let draw = function() {
|
||||||
// queue next draw in one minute
|
// queue next draw in one minute
|
||||||
queueDraw();
|
queueDraw();
|
||||||
// Work out where to draw...
|
// Work out where to draw...
|
||||||
|
@ -30,15 +31,18 @@ function draw() {
|
||||||
g.setFontAlign(0,0).setFont("6x8");
|
g.setFontAlign(0,0).setFont("6x8");
|
||||||
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
|
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
|
||||||
g.drawString(dateStr,x,y);
|
g.drawString(dateStr,x,y);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
g.clear();
|
||||||
|
// draw immediately at first, queue update
|
||||||
|
draw();
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI({mode:"clock", remove:function() {
|
||||||
|
// free any memory we allocated to allow fast loading
|
||||||
|
}});
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the screen once, at startup
|
|
||||||
g.clear();
|
|
||||||
// draw immediately at first, queue update
|
|
||||||
draw();
|
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI("clock");
|
|
||||||
// Load widgets
|
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
|
|
@ -4,6 +4,7 @@
|
||||||
"version":"0.01",
|
"version":"0.01",
|
||||||
"description": "A detailed description of my clock",
|
"description": "A detailed description of my clock",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
|
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
0.01: Initial release.
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Accerleration Data Provider
|
||||||
|
|
||||||
|
This app provides acceleration data via Bluetooth, which can be used in Gadgetbridge.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This boot code runs in the background and has no user interface.
|
||||||
|
Currently this app is used to enable Sleep as Android tracking for your Banglejs using Gadgetbridge.
|
||||||
|
|
||||||
|
**Please Note**: This app only listens to "accel" events and sends them to your phone using Bluetooth.
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
[Another Stranger](https://github.com/anotherstranger)
|
||||||
|
|
||||||
|
## Aknowledgements
|
||||||
|
|
||||||
|
Special thanks to [José Rebelo](https://github.com/joserebelo) and [Rob Pilling](https://github.com/bobrippling)
|
||||||
|
for their Code Reviews and guidance.
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,55 @@
|
||||||
|
(() => {
|
||||||
|
/**
|
||||||
|
* Sends a message to the gadgetbridge via Bluetooth.
|
||||||
|
* @param {Object} message - The message to be sent.
|
||||||
|
*/
|
||||||
|
function gbSend(message) {
|
||||||
|
try {
|
||||||
|
Bluetooth.println("");
|
||||||
|
Bluetooth.println(JSON.stringify(message));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to send message via Bluetooth:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var max_acceleration = { x: 0, y: 0, z: 0, diff: 0, td: 0, mag: 0 };
|
||||||
|
var hasData = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the maximum acceleration if the current acceleration is greater.
|
||||||
|
* @param {Object} accel - The current acceleration object with x, y, z, and mag properties.
|
||||||
|
*/
|
||||||
|
function updateAcceleration(accel) {
|
||||||
|
hasData = true;
|
||||||
|
var current_max_raw = accel.mag;
|
||||||
|
var max_raw = max_acceleration.mag;
|
||||||
|
|
||||||
|
if (current_max_raw > max_raw) {
|
||||||
|
max_acceleration = accel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the acceleration data and sends it to gadgetbridge.
|
||||||
|
* Resets the maximum acceleration.
|
||||||
|
* Note: If your interval setting is too short, the last value gets sent again.
|
||||||
|
*/
|
||||||
|
function sendAccelerationData() {
|
||||||
|
var accel = hasData ? max_acceleration : Bangle.getAccel();
|
||||||
|
|
||||||
|
var update_data = {
|
||||||
|
t: "accel", accel: accel
|
||||||
|
};
|
||||||
|
gbSend(update_data);
|
||||||
|
|
||||||
|
max_acceleration = { x: 0, y: 0, z: 0, mag: 0, diff: 0, td: 0 };
|
||||||
|
hasData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = require("Storage").readJSON("accelsender.json") || {};
|
||||||
|
if (config.enabled) { // Gadgetbridge needs to enable and disable tracking by writing {enabled: true} to "accelsender.json" and reloading
|
||||||
|
setInterval(sendAccelerationData, config.interval);
|
||||||
|
Bangle.on("accel", updateAcceleration); // Log all acceleration events
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1 @@
|
||||||
|
(()=>{function e(a){c=!0;a.mag>b.mag&&(b=a)}function f(){var a={t:"accel",accel:c?b:Bangle.getAccel()};try{Bluetooth.println(""),Bluetooth.println(JSON.stringify(a))}catch(g){console.error("Failed to send message via Bluetooth:",g)}b={x:0,y:0,z:0,mag:0,diff:0,td:0};c=!1}var b={x:0,y:0,z:0,diff:0,td:0,mag:0},c=!1,d=require("Storage").readJSON("accelsender.json")||{};d.enabled&&(setInterval(f,d.interval),Bangle.on("accel",e))})();
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"interval": 10000
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"id": "accelsender",
|
||||||
|
"name": "Acceleration Data Provider",
|
||||||
|
"shortName": "Accel Data Provider",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "This app sends accelerometer and heart rate data from your Bangle.js via Bluetooth.",
|
||||||
|
"icon": "bluetooth.png",
|
||||||
|
"type": "bootloader",
|
||||||
|
"tags": "accel",
|
||||||
|
"supports": [
|
||||||
|
"BANGLEJS",
|
||||||
|
"BANGLEJS2"
|
||||||
|
],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"name": "accelsender.boot.js",
|
||||||
|
"url": "boot.min.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "accelsender.json",
|
||||||
|
"url": "config.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
"version":"0.12",
|
"version":"0.12",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool,activity",
|
"tags": "tool,activity,health",
|
||||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New Clock!
|
0.01: New Clock!
|
||||||
0.02: Fix fastloading memory leak and clockinfo overwritten by hands
|
0.02: Fix fastloading memory leak and clockinfo overwritten by hands
|
||||||
|
0.03: Use new clockinfo lib with function to render images wirh borders
|
|
@ -102,6 +102,7 @@
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
Bangle.setUI({
|
Bangle.setUI({
|
||||||
mode: "clock",
|
mode: "clock",
|
||||||
|
redraw : draw,
|
||||||
remove: function() {
|
remove: function() {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
@ -116,9 +117,6 @@
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
require("widget_utils").hide();
|
require("widget_utils").hide();
|
||||||
|
|
||||||
// used for clockinfo image rendering
|
|
||||||
let clockInfoG = Graphics.createArrayBuffer(28, 28, 2, {msb:true});
|
|
||||||
clockInfoG.transparent = 3;
|
|
||||||
// render clockinfos
|
// render clockinfos
|
||||||
let clockInfoDraw = function(itm, info, options) {
|
let clockInfoDraw = function(itm, info, options) {
|
||||||
// itm: the item containing name/hasRange/etc
|
// itm: the item containing name/hasRange/etc
|
||||||
|
@ -139,18 +137,8 @@
|
||||||
fg = g.toColor("#f00");
|
fg = g.toColor("#f00");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.img) {
|
if (info.img)
|
||||||
//g.drawImage(info.img, left ? 2 : W - 27, top ? 18 : H - 41); // draw the image
|
require("clock_info").drawBorderedImage(info.img,imgx,imgy);
|
||||||
// fiddle around colouring the border and inside of the image
|
|
||||||
clockInfoG.clear(1);
|
|
||||||
// do a border - images need to be transparent for this
|
|
||||||
clockInfoG.setColor(2).drawImage(info.img, 1,1).drawImage(info.img, 3,1).
|
|
||||||
drawImage(info.img, 1,3).drawImage(info.img, 3,3);
|
|
||||||
clockInfoG.setColor(1).drawImage(info.img, 2,2); // main image
|
|
||||||
clockInfoG.floodFill(27,27,3); // flood fill edge to transparent
|
|
||||||
clockInfoG.palette = new Uint16Array([bg,fg,bg/*border*/, g.toColor("#888")]);
|
|
||||||
g.drawImage(clockInfoG, imgx-1, imgy-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
g.setFont("6x8:2").setFontAlign(left ? -1 : 1, -1);
|
g.setFont("6x8:2").setFontAlign(left ? -1 : 1, -1);
|
||||||
g.setColor(bg).drawString(info.text, textx-2, texty). // draw the text background
|
g.setColor(bg).drawString(info.text, textx-2, texty). // draw the text background
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{ "id": "analogquadclk",
|
{ "id": "analogquadclk",
|
||||||
"name": "Analog Quad Clock",
|
"name": "Analog Quad Clock",
|
||||||
"shortName":"Quad Clock",
|
"shortName":"Quad Clock",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "An analog clock with clockinfos in each of the 4 corners, allowing 4 different data types to be rendered at once",
|
"description": "An analog clock with clockinfos in each of the 4 corners, allowing 4 different data types to be rendered at once",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ],
|
"screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock,clkinfo,analog",
|
"tags": "clock,clkinfo,analog,clockbg",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"dependencies" : { "clock_info":"module", "clockbg":"module" },
|
"dependencies" : { "clock_info":"module", "clockbg":"module" },
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
0.01: Release
|
0.01: Release
|
||||||
0.02: Rename app
|
0.02: Rename app
|
||||||
0.03: Add type "clock"
|
0.03: Add type "clock"
|
||||||
0.04: changed update cylce, when locked
|
0.04: Changed update cylce, when locked
|
||||||
|
0.05: Fix support for dark theme + support widgets +
|
||||||
|
add settings for widgets, order of drawing and hour hand length
|
||||||
|
0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
# Analog Clock
|
# Dark Analog Clock
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* second hand
|
* second hand (only on unlocked screen)
|
||||||
* date
|
* date
|
||||||
* battery percantage
|
* battery percentage (showing charge status with color)
|
||||||
* no widgets
|
* turned off or swipeable widgets (choose in settings)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
* whether to load widgets, or not; if widgets are loaded, they are swipeable from the top; if not, NO ACTIONS of widgets are available
|
||||||
|
* date and battery can be printed both below hands (as if hands were physical) and above (more readable)
|
||||||
|
* hour hand can be made slighly shorter to improve readability when minute hand is behind a number
|
||||||
|
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,22 +1,43 @@
|
||||||
|
const defaultSettings = {
|
||||||
|
loadWidgets : false,
|
||||||
|
textAboveHands : false,
|
||||||
|
shortHrHand : false
|
||||||
|
};
|
||||||
|
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
|
||||||
|
|
||||||
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
||||||
let zahlpos=[];
|
|
||||||
|
const zahlpos=(function() {
|
||||||
|
let z=[];
|
||||||
|
let sk=1;
|
||||||
|
for(let i=-10;i<50;i+=5){
|
||||||
|
let win=i*2*Math.PI/60;
|
||||||
|
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||||
|
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||||
|
if(sk==3){xsk-=10;}
|
||||||
|
if(sk==6){ysk-=10;}
|
||||||
|
if(sk==9){xsk+=10;}
|
||||||
|
if(sk==12){ysk+=10;}
|
||||||
|
if(sk==10){xsk+=3;}
|
||||||
|
z.push([sk,xsk,ysk]);
|
||||||
|
sk+=1;
|
||||||
|
}
|
||||||
|
return z;
|
||||||
|
})();
|
||||||
|
|
||||||
let unlock = false;
|
let unlock = false;
|
||||||
|
|
||||||
function zeiger(len,dia,tim){
|
function zeiger(len,dia,tim){
|
||||||
const x =c.x+ Math.cos(tim)*len/2,
|
const x=c.x+ Math.cos(tim)*len/2,
|
||||||
y =c.y + Math.sin(tim)*len/2,
|
y=c.y + Math.sin(tim)*len/2,
|
||||||
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
||||||
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
||||||
return pol;
|
return pol;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw(){
|
function drawHands(d) {
|
||||||
const d=new Date();
|
|
||||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||||
//draw black rectangle in the middle to clear screen from scale and hands
|
|
||||||
g.setColor(0,0,0);
|
|
||||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
|
|
||||||
if(h>12){
|
if(h>12){
|
||||||
|
@ -29,17 +50,10 @@ function draw(){
|
||||||
m=2*Math.PI/60*(m)-Math.PI/2;
|
m=2*Math.PI/60*(m)-Math.PI/2;
|
||||||
|
|
||||||
s=2*Math.PI/60*s-Math.PI/2;
|
s=2*Math.PI/60*s-Math.PI/2;
|
||||||
g.setFontAlign(0,0);
|
|
||||||
g.setFont("Vector",10);
|
|
||||||
let dateStr = " "+require("locale").date(d)+" ";
|
|
||||||
g.drawString(dateStr, c.x, c.y+20, true);
|
|
||||||
// g.drawString(d.getDate(),1.4*c.x,c.y,true);
|
|
||||||
g.drawString(Math.round(E.getBattery()/5)*5+"%",c.x,c.y+40,true);
|
|
||||||
drawlet();
|
|
||||||
//g.setColor(1,0,0);
|
//g.setColor(1,0,0);
|
||||||
const hz = zeiger(100,5,h);
|
const hz = zeiger(settings.shortHrHand?88:100,5,h);
|
||||||
g.fillPoly(hz,true);
|
g.fillPoly(hz,true);
|
||||||
// g.setColor(1,1,1);
|
//g.setColor(1,1,1);
|
||||||
const minz = zeiger(150,5,m);
|
const minz = zeiger(150,5,m);
|
||||||
g.fillPoly(minz,true);
|
g.fillPoly(minz,true);
|
||||||
if (unlock){
|
if (unlock){
|
||||||
|
@ -47,12 +61,53 @@ function draw(){
|
||||||
g.fillPoly(sekz,true);
|
g.fillPoly(sekz,true);
|
||||||
}
|
}
|
||||||
g.fillCircle(c.x,c.y,4);
|
g.fillCircle(c.x,c.y,4);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawText(d) {
|
||||||
|
g.setFont("Vector",10);
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
let dateStr = require("locale").date(d);
|
||||||
|
g.drawString(dateStr, c.x, c.y+20, true);
|
||||||
|
let batStr = Math.round(E.getBattery()/5)*5+"%";
|
||||||
|
if (Bangle.isCharging()) {
|
||||||
|
g.setBgColor(1,0,0);
|
||||||
|
}
|
||||||
|
g.drawString(batStr, c.x, c.y+40, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawNumbers() {
|
||||||
|
//draws the numbers on the screen
|
||||||
|
g.setFont("Vector",20);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
for(let i = 0;i<12;i++){
|
||||||
|
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw(){
|
||||||
|
// draw black rectangle in the middle to clear screen from scale and hands
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||||
|
// prepare for drawing the text
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
// do drawing
|
||||||
|
drawNumbers();
|
||||||
|
const d=new Date();
|
||||||
|
if (settings.textAboveHands) {
|
||||||
|
drawHands(d); drawText(d);
|
||||||
|
} else {
|
||||||
|
drawText(d); drawHands(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//draws the scale once the app is startet
|
//draws the scale once the app is startet
|
||||||
function drawScale(){
|
function drawScale(){
|
||||||
|
// clear the screen
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
g.clear();
|
||||||
|
// draw the ticks of the scale
|
||||||
for(let i=-14;i<47;i++){
|
for(let i=-14;i<47;i++){
|
||||||
const win=i*2*Math.PI/60;
|
const win=i*2*Math.PI/60;
|
||||||
let d=2;
|
let d=2;
|
||||||
|
@ -64,40 +119,22 @@ function drawScale(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//draws the numbers on the screen
|
//// main running sequence ////
|
||||||
|
|
||||||
function drawlet(){
|
// Show launcher when middle button pressed, and widgets that we're clock
|
||||||
g.setFont("Vector",20);
|
Bangle.setUI("clock");
|
||||||
for(let i = 0;i<12;i++){
|
// Load widgets if needed, and make them show swipeable
|
||||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]);
|
if (settings.loadWidgets) {
|
||||||
}
|
Bangle.loadWidgets();
|
||||||
}
|
require("widget_utils").swipeOn();
|
||||||
//calcultes the Position of the numbers when app starts and saves them in an array
|
} else if (global.WIDGETS) require("widget_utils").hide();
|
||||||
function setlet(){
|
|
||||||
let sk=1;
|
|
||||||
for(let i=-10;i<50;i+=5){
|
|
||||||
let win=i*2*Math.PI/60;
|
|
||||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
|
||||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
|
||||||
if(sk==3){xsk-=10;}
|
|
||||||
if(sk==6){ysk-=10;}
|
|
||||||
if(sk==9){xsk+=10;}
|
|
||||||
if(sk==12){ysk+=10;}
|
|
||||||
if(sk==10){xsk+=3;}
|
|
||||||
zahlpos.push([sk,xsk,ysk]);
|
|
||||||
sk+=1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setlet();
|
|
||||||
// Clear the screen once, at startup
|
// Clear the screen once, at startup
|
||||||
g.setBgColor(0,0,0);
|
|
||||||
g.clear();
|
|
||||||
drawScale();
|
drawScale();
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
let secondInterval= setInterval(draw, 1000);
|
let secondInterval = setInterval(draw, 1000);
|
||||||
// Stop updates when LCD is off, restart when on
|
|
||||||
|
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
if (secondInterval) clearInterval(secondInterval);
|
if (secondInterval) clearInterval(secondInterval);
|
||||||
secondInterval = undefined;
|
secondInterval = undefined;
|
||||||
|
@ -107,18 +144,9 @@ Bangle.on('lcdPower',on=>{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Bangle.on('lock',on=>{
|
Bangle.on('lock',on=>{
|
||||||
|
unlock = !on;
|
||||||
if (secondInterval) clearInterval(secondInterval);
|
if (secondInterval) clearInterval(secondInterval);
|
||||||
secondInterval = undefined;
|
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
|
||||||
if (!on) {
|
|
||||||
secondInterval = setInterval(draw, 1000);
|
|
||||||
unlock = true;
|
|
||||||
draw(); // draw immediately
|
draw(); // draw immediately
|
||||||
}else{
|
});
|
||||||
secondInterval = setInterval(draw, 60000);
|
Bangle.on('charging',on=>{draw();});
|
||||||
unlock = false;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI("clock");
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
{ "id": "andark",
|
{ "id": "andark",
|
||||||
"name": "Analog Dark",
|
"name": "Analog Dark",
|
||||||
"shortName":"AnDark",
|
"shortName":"AnDark",
|
||||||
"version":"0.04",
|
"version":"0.06",
|
||||||
"description": "analog clock face without disturbing widgets",
|
"description": "analog clock face without disturbing widgets",
|
||||||
"icon": "andark_icon.png",
|
"icon": "andark_icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"screenshots": [{"url":"andark_screen.png"}],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"andark.app.js","url":"app.js"},
|
{"name":"andark.app.js","url":"app.js"},
|
||||||
|
{"name":"andark.settings.js","url":"settings.js"},
|
||||||
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
||||||
]
|
],
|
||||||
|
"data": [{"name":"andark.json"}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
(function(back) {
|
||||||
|
const defaultSettings = {
|
||||||
|
loadWidgets : false,
|
||||||
|
textAboveHands : false,
|
||||||
|
shortHrHand : false
|
||||||
|
}
|
||||||
|
let settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
|
||||||
|
|
||||||
|
const save = () => require('Storage').write('andark.json', settings);
|
||||||
|
|
||||||
|
const appMenu = {
|
||||||
|
'': {title: 'andark'}, '< Back': back,
|
||||||
|
/*LANG*/'Load widgets': {
|
||||||
|
value : !!settings.loadWidgets,
|
||||||
|
onchange : v => { settings.loadWidgets=v; save();}
|
||||||
|
},
|
||||||
|
/*LANG*/'Text above hands': {
|
||||||
|
value : !!settings.textAboveHands,
|
||||||
|
onchange : v => { settings.textAboveHands=v; save();}
|
||||||
|
},
|
||||||
|
/*LANG*/'Short hour hand': {
|
||||||
|
value : !!settings.shortHrHand,
|
||||||
|
onchange : v => { settings.shortHrHand=v; save();}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
E.showMenu(appMenu);
|
||||||
|
});
|
|
@ -34,3 +34,4 @@
|
||||||
0.32: Added support for loyalty cards from gadgetbridge
|
0.32: Added support for loyalty cards from gadgetbridge
|
||||||
0.33: Fix alarms created in Gadgetbridge not repeating
|
0.33: Fix alarms created in Gadgetbridge not repeating
|
||||||
0.34: Implement API for activity tracks fetching (Recorder app logs).
|
0.34: Implement API for activity tracks fetching (Recorder app logs).
|
||||||
|
0.35: Implement API to enable/disable acceleration data tracking.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* global GB */
|
||||||
(function() {
|
(function() {
|
||||||
function gbSend(message) {
|
function gbSend(message) {
|
||||||
Bluetooth.println("");
|
Bluetooth.println("");
|
||||||
|
@ -292,6 +293,10 @@
|
||||||
// we receive all, just override what we have
|
// we receive all, just override what we have
|
||||||
if (Array.isArray(event.d))
|
if (Array.isArray(event.d))
|
||||||
require("Storage").writeJSON("android.cards.json", event.d);
|
require("Storage").writeJSON("android.cards.json", event.d);
|
||||||
|
},
|
||||||
|
"accelsender": function () {
|
||||||
|
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
|
||||||
|
load();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var h = HANDLERS[event.t];
|
var h = HANDLERS[event.t];
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"shortName": "Android",
|
||||||
"version": "0.34",
|
"version": "0.35",
|
||||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
let result = true;
|
|
||||||
|
|
||||||
function assertTrue(condition, text) {
|
|
||||||
if (!condition) {
|
|
||||||
result = false;
|
|
||||||
print("FAILURE: " + text);
|
|
||||||
} else print("OK: " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertFalse(condition, text) {
|
|
||||||
assertTrue(!condition, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertUndefinedOrEmpty(array, text) {
|
|
||||||
assertTrue(!array || array.length == 0, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertNotEmpty(array, text) {
|
|
||||||
assertTrue(array && array.length > 0, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
let internalOn = () => {
|
|
||||||
return getPinMode((process.env.HWVERSION==2)?D30:D26) == "input";
|
|
||||||
};
|
|
||||||
|
|
||||||
let sec = {
|
|
||||||
connected: false
|
|
||||||
};
|
|
||||||
|
|
||||||
NRF.getSecurityStatus = () => sec;
|
|
||||||
// add an empty starting point to make the asserts work
|
|
||||||
Bangle._PWR={};
|
|
||||||
|
|
||||||
let teststeps = [];
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Not connected, should use internal GPS");
|
|
||||||
assertTrue(!NRF.getSecurityStatus().connected, "Not connected");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
|
|
||||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Connected, should use GB GPS");
|
|
||||||
sec.connected = true;
|
|
||||||
|
|
||||||
assertTrue(NRF.getSecurityStatus().connected, "Connected");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
|
|
||||||
|
|
||||||
print("Internal GPS stays on until the first GadgetBridge event arrives");
|
|
||||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
print("Send minimal GadgetBridge GPS event to trigger switch");
|
|
||||||
GB({t:"gps"});
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("GPS should be on, internal off");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Switching GPS off turns both GadgetBridge as well as internal off");
|
|
||||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Wait for all timeouts to run out");
|
|
||||||
return 12000;
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Check auto switch when no GPS event arrives");
|
|
||||||
|
|
||||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
print("Send minimal GadgetBridge GPS event to trigger switch");
|
|
||||||
GB({t:"gps"});
|
|
||||||
|
|
||||||
print("Internal should be switched off now");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
|
|
||||||
//wait on next test
|
|
||||||
return 12000;
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Check state and disable GPS, internal should be on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Result Overall is " + (result ? "OK" : "FAIL"));
|
|
||||||
});
|
|
||||||
|
|
||||||
let wrap = (functions) => {
|
|
||||||
if (functions.length > 0) {
|
|
||||||
setTimeout(()=>{
|
|
||||||
let waitingTime = functions.shift()();
|
|
||||||
if (waitingTime){
|
|
||||||
print("WAITING: ", waitingTime);
|
|
||||||
setTimeout(()=>{wrap(functions);}, waitingTime);
|
|
||||||
} else
|
|
||||||
wrap(functions);
|
|
||||||
},0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(()=>{
|
|
||||||
wrap(teststeps);
|
|
||||||
}, 5000);
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
"app" : "android",
|
||||||
|
"setup" : [{
|
||||||
|
"id": "default",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"cmd", "js": "Bangle.setGPSPower=(isOn, appID)=>{if (!appID) appID='?';if (!Bangle._PWR) Bangle._PWR={};if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);return Bangle._PWR.GPS.length>0;};", "text": "Fake the setGPSPower"},
|
||||||
|
{"t":"wrap", "fn": "Bangle.setGPSPower", "id": "gpspower"},
|
||||||
|
{"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port println"},
|
||||||
|
{"t":"cmd", "js": "Bluetooth.println = () => { }", "text": "Fake the Bluetooth println"},
|
||||||
|
{"t":"cmd", "js": "Bangle._PWR={}", "text": "Prepare an empty _PWR for following asserts"},
|
||||||
|
{"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"},
|
||||||
|
{"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"id": "connected",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"cmd", "js": "NRF.getSecurityStatus = () => { return { connected: true };}", "text": "Control the security status to be connected"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"id": "disconnected",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"cmd", "js": "NRF.getSecurityStatus = () => { return { connected: false };}", "text": "Control the security status to be disconnected"}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
"tests" : [{
|
||||||
|
"description": "Check setGPSPower is replaced",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port"},
|
||||||
|
{"t":"cmd", "js": "Bluetooth.println = () => { }", "text": "Fake the Bluetooth println"},
|
||||||
|
{"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"},
|
||||||
|
{"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"},
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower.toString().includes('native')", "is":"false", "text": "setGPSPower has been replaced"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"description": "Test switching hardware GPS on and off",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"setup", "id": "default"},
|
||||||
|
{"t":"setup", "id": "connected"},
|
||||||
|
{"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"},
|
||||||
|
{"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"},
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"},
|
||||||
|
{"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"},
|
||||||
|
{"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn shows GPS as on"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "count": 1, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "internal GPS switched on"},
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"falsy", "text": "setGPSPower returns falsy when switching off"},
|
||||||
|
{"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"},
|
||||||
|
{"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "count": 2, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ] , "text": "internal GPS switched off"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"description": "Test switching when GB GPS is available, internal GPS active until GB GPS event arrives",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"setup", "id": "default"},
|
||||||
|
{"t":"setup", "id": "connected"},
|
||||||
|
{"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"},
|
||||||
|
{"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"},
|
||||||
|
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"},
|
||||||
|
{"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"},
|
||||||
|
{"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn shows GPS as on"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "count": 1, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ], "text": "internal GPS switched on"},
|
||||||
|
|
||||||
|
{"t":"gb", "obj":{"t":"gps"}},
|
||||||
|
{"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients still there"},
|
||||||
|
{"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn still shows GPS as on"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "count": 2, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS switched off"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"description": "Test switching when GB GPS is available, internal stays off",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"setup", "id": "default"},
|
||||||
|
{"t":"setup", "id": "connected"},
|
||||||
|
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"},
|
||||||
|
|
||||||
|
{"t":"gb", "obj":{"t":"gps"}},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS switched off"},
|
||||||
|
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"falsy", "text": "setGPSPower returns truthy when switching on"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS still switched off"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"description": "Test switching when GB GPS is available, but no event arrives",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"setup", "id": "default"},
|
||||||
|
{"t":"setup", "id": "connected"},
|
||||||
|
|
||||||
|
{"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"},
|
||||||
|
|
||||||
|
{"t":"resetCall", "id": "gpspower"},
|
||||||
|
{"t":"gb", "obj":{"t":"gps"}, "text": "trigger switch"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS switched off"},
|
||||||
|
|
||||||
|
{"t":"resetCall", "id": "gpspower"},
|
||||||
|
{"t":"advanceTimers", "ms":"12000", "text": "wait for fallback"},
|
||||||
|
{"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ], "text": "internal GPS switched on caused by missing GB event"}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"app" : "antonclk",
|
||||||
|
"tests" : [{
|
||||||
|
"description": "Check memory usage after setUI",
|
||||||
|
"steps" : [
|
||||||
|
{"t":"cmd", "js": "Bangle.loadWidgets()"},
|
||||||
|
{"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"},
|
||||||
|
{"t":"cmd", "js": "Bangle.setUI()"},
|
||||||
|
{"t":"saveMemoryUsage"},
|
||||||
|
{"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"},
|
||||||
|
{"t":"cmd", "js":"Bangle.setUI()"},
|
||||||
|
{"t":"checkMemoryUsage"}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Add black- and whitelist for apps. Configure the timout time.
|
||||||
|
|
|
@ -6,11 +6,11 @@ Sets a timeout to load the clock face. The timeout is stopped and started again
|
||||||
|
|
||||||
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
|
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
|
||||||
|
|
||||||
|
Through the settings apps can be black-/whitelisted and the timeout length can be configured.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Add settings page
|
- per app specific timeout lengths?
|
||||||
- set how many minutes the timeout should count down.
|
|
||||||
- whitelist/blacklist for apps.
|
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
{
|
{
|
||||||
|
const DEFAULTS = {
|
||||||
|
mode: 0,
|
||||||
|
apps: [],
|
||||||
|
timeout: 10
|
||||||
|
};
|
||||||
|
const settings = require("Storage").readJSON("autoreset.json", 1) || DEFAULTS;
|
||||||
|
|
||||||
|
// Check if the back button should be enabled for the current app.
|
||||||
|
// app is the src file of the app.
|
||||||
|
// Derivative of the backswipe app's logic.
|
||||||
|
function enabledForApp(app) {
|
||||||
|
if (Bangle.CLOCK==1) return false;
|
||||||
|
if (!settings) return true;
|
||||||
|
let isListed = settings.apps.filter((a) => a.files.includes(app)).length > 0;
|
||||||
|
return settings.mode===0?!isListed:isListed;
|
||||||
|
}
|
||||||
|
|
||||||
let timeoutAutoreset;
|
let timeoutAutoreset;
|
||||||
let resetTimeoutAutoreset = (force)=>{
|
const resetTimeoutAutoreset = (force)=>{
|
||||||
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
|
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
|
||||||
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
|
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
|
||||||
if (Bangle.CLOCK!=1) { // Only add timeout if not already on clock face.
|
if (enabledForApp(global.__FILE__)) {
|
||||||
timeoutAutoreset = setTimeout(()=>{
|
timeoutAutoreset = setTimeout(()=>{
|
||||||
if (Bangle.CLOCK!=1) Bangle.showClock();
|
if (Bangle.CLOCK!=1) Bangle.showClock();
|
||||||
}, 10*60*1000);
|
}, settings.timeout*60*1000);
|
||||||
}
|
}
|
||||||
},200);
|
},200);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "autoreset",
|
{ "id": "autoreset",
|
||||||
"name": "Auto Reset",
|
"name": "Auto Reset",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
|
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
@ -8,6 +8,10 @@
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"autoreset.boot.js","url":"boot.js"}
|
{"name":"autoreset.boot.js","url":"boot.js"},
|
||||||
|
{"name":"autoreset.settings.js","url":"settings.js"}
|
||||||
|
],
|
||||||
|
"data":[
|
||||||
|
{"name":"autoreset.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
(function(back) {
|
||||||
|
var FILE = 'autoreset.json';
|
||||||
|
// Mode can be 'blacklist' or 'whitelist'
|
||||||
|
// Apps is an array of app info objects, where all the apps that are there are either blocked or allowed, depending on the mode
|
||||||
|
var DEFAULTS = {
|
||||||
|
'mode': 0,
|
||||||
|
'apps': [],
|
||||||
|
'timeout': 10
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = {};
|
||||||
|
|
||||||
|
var loadSettings = function() {
|
||||||
|
settings = require('Storage').readJSON(FILE, 1) || DEFAULTS;
|
||||||
|
};
|
||||||
|
|
||||||
|
var saveSettings = function(settings) {
|
||||||
|
require('Storage').write(FILE, settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all app info files
|
||||||
|
var getApps = function() {
|
||||||
|
var apps = require('Storage').list(/\.info$/).map(appInfoFileName => {
|
||||||
|
var appInfo = require('Storage').readJSON(appInfoFileName, 1);
|
||||||
|
return appInfo && {
|
||||||
|
'name': appInfo.name,
|
||||||
|
'sortorder': appInfo.sortorder,
|
||||||
|
'src': appInfo.src,
|
||||||
|
'files': appInfo.files
|
||||||
|
};
|
||||||
|
}).filter(app => app && !!app.src);
|
||||||
|
apps.sort((a, b) => {
|
||||||
|
var n = (0 | a.sortorder) - (0 | b.sortorder);
|
||||||
|
if (n) return n; // do sortorder first
|
||||||
|
if (a.name < b.name) return -1;
|
||||||
|
if (a.name > b.name) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
|
var showMenu = function() {
|
||||||
|
var menu = {
|
||||||
|
'': { 'title': 'Auto Reset' },
|
||||||
|
/*LANG*/'< Back': () => {
|
||||||
|
back();
|
||||||
|
},
|
||||||
|
/*LANG*/'Mode': {
|
||||||
|
value: settings.mode,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
format: v => ["Blacklist", "Whitelist"][v],
|
||||||
|
onchange: v => {
|
||||||
|
settings.mode = v;
|
||||||
|
saveSettings(settings);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/*LANG*/'App List': () => {
|
||||||
|
showAppSubMenu();
|
||||||
|
},
|
||||||
|
/*LANG*/'Timeout [min]': {
|
||||||
|
value: settings.timeout,
|
||||||
|
min: 0.25, max: 30, step : 0.25,
|
||||||
|
format: v => v,
|
||||||
|
onchange: v => {
|
||||||
|
settings.timeout = v;
|
||||||
|
saveSettings(settings);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
E.showMenu(menu);
|
||||||
|
};
|
||||||
|
|
||||||
|
var showAppSubMenu = function() {
|
||||||
|
var menu = {
|
||||||
|
'': { 'title': 'Auto Reset' },
|
||||||
|
'< Back': () => {
|
||||||
|
showMenu();
|
||||||
|
},
|
||||||
|
'Add App': () => {
|
||||||
|
showAppList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
settings.apps.forEach(app => {
|
||||||
|
menu[app.name] = () => {
|
||||||
|
settings.apps.splice(settings.apps.indexOf(app), 1);
|
||||||
|
saveSettings(settings);
|
||||||
|
showAppSubMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
var showAppList = function() {
|
||||||
|
var apps = getApps();
|
||||||
|
var menu = {
|
||||||
|
'': { 'title': 'Auto Reset' },
|
||||||
|
/*LANG*/'< Back': () => {
|
||||||
|
showMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apps.forEach(app => {
|
||||||
|
menu[app.name] = () => {
|
||||||
|
settings.apps.push(app);
|
||||||
|
saveSettings(settings);
|
||||||
|
showAppSubMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
showMenu();
|
||||||
|
})
|
|
@ -1,3 +1,6 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Don't fire if the app uses swipes already.
|
0.02: Don't fire if the app uses swipes already.
|
||||||
0.03: Only count defined handlers in the handler array.
|
0.03: Only count defined handlers in the handler array.
|
||||||
|
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs
|
||||||
|
a refresh by deselecting and reselecting the "Messages" app throught Back Swipe
|
||||||
|
settings.
|
||||||
|
|
|
@ -47,9 +47,9 @@
|
||||||
function enabledForApp(app) {
|
function enabledForApp(app) {
|
||||||
if (!settings) return true;
|
if (!settings) return true;
|
||||||
if (settings.mode === 0) {
|
if (settings.mode === 0) {
|
||||||
return !(settings.apps.filter((a) => a.src === app).length > 0);
|
return !(settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0); // The `a.src===app` and `a.files&&...` checks are for backwards compatibility. Otherwise only `a.files.includes(app)` is needed.
|
||||||
} else if (settings.mode === 1) {
|
} else if (settings.mode === 1) {
|
||||||
return settings.apps.filter((a) => a.src === app).length > 0;
|
return settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0;
|
||||||
} else {
|
} else {
|
||||||
return settings.mode === 2 ? true : false;
|
return settings.mode === 2 ? true : false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "backswipe",
|
{ "id": "backswipe",
|
||||||
"name": "Back Swipe",
|
"name": "Back Swipe",
|
||||||
"shortName":"BackSwipe",
|
"shortName":"BackSwipe",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "back,gesture,swipe",
|
"tags": "back,gesture,swipe",
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
return appInfo && {
|
return appInfo && {
|
||||||
'name': appInfo.name,
|
'name': appInfo.name,
|
||||||
'sortorder': appInfo.sortorder,
|
'sortorder': appInfo.sortorder,
|
||||||
'src': appInfo.src
|
'src': appInfo.src,
|
||||||
|
'files': appInfo.files
|
||||||
};
|
};
|
||||||
}).filter(app => app && !!app.src);
|
}).filter(app => app && !!app.src);
|
||||||
apps.sort((a, b) => {
|
apps.sort((a, b) => {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
0.01: Initial version
|
||||||
|
0.02: Minor code improvements
|
||||||
|
0.03: Moved from cycling app, fixed connection issues and cadence
|
|
@ -0,0 +1,32 @@
|
||||||
|
# BLE Cycling Speed Sencor (CSC)
|
||||||
|
|
||||||
|
Displays data from a BLE Cycling Speed and Cadence sensor.
|
||||||
|
|
||||||
|
Other than in the original version of the app, total distance is not stored on the Bangle, but instead is calculated from the CWR (cumulative wheel revolutions) reported by the sensor. This metric is, according to the BLE spec, an absolute value that persists throughout the lifetime of the sensor and never rolls over.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Accessible from `Settings -> Apps -> BLE CSC`
|
||||||
|
|
||||||
|
Here you can set the wheel diameter
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```
|
||||||
|
var csc = require("blecsc").getInstance();
|
||||||
|
csc.on("status", txt => {
|
||||||
|
print("##", txt);
|
||||||
|
E.showMessage(txt);
|
||||||
|
});
|
||||||
|
csc.on("data", e => print(e));
|
||||||
|
csc.start();
|
||||||
|
```
|
||||||
|
|
||||||
|
The `data` event contains:
|
||||||
|
|
||||||
|
* cwr/ccr => wheel/crank cumulative revs
|
||||||
|
* lwet/lcet => wheel/crank last event time in 1/1024s
|
||||||
|
* wrps/crps => calculated wheel/crank revs per second
|
||||||
|
* wdt/cdt => time period in seconds between events
|
||||||
|
* wr => wheel revs
|
||||||
|
* kph => kilometers per hour
|
|
@ -0,0 +1,216 @@
|
||||||
|
/**
|
||||||
|
* This library communicates with a Bluetooth CSC peripherial using the Espruino NRF library.
|
||||||
|
*
|
||||||
|
* ## Usage:
|
||||||
|
* 1. Register event handlers using the \`on(eventName, handlerFunction)\` method
|
||||||
|
* You can subscribe to the \`wheelEvent\` and \`crankEvent\` events or you can
|
||||||
|
* have raw characteristic values passed through using the \`value\` event.
|
||||||
|
* 2. Search and connect to a BLE CSC peripherial by calling the \`connect()\` method
|
||||||
|
* 3. To tear down the connection, call the \`disconnect()\` method
|
||||||
|
*
|
||||||
|
* ## Events
|
||||||
|
* - \`status\` - string containing connection status
|
||||||
|
* - \`data\` - the peripheral sends a notification containing wheel/crank event data
|
||||||
|
* - \`disconnect\` - the peripheral ends the connection or the connection is lost
|
||||||
|
*
|
||||||
|
* cwr/ccr => wheel/crank cumulative revs
|
||||||
|
* lwet/lcet => wheel/crank last event time in 1/1024s
|
||||||
|
* wrps/crps => calculated wheel/crank revs per second
|
||||||
|
* wdt/cdt => time period in seconds between events
|
||||||
|
* wr => wheel revs
|
||||||
|
* kph => kilometers per hour
|
||||||
|
*/
|
||||||
|
class BLECSC {
|
||||||
|
constructor() {
|
||||||
|
this.reconnect = false; // set when start called
|
||||||
|
this.device = undefined; // set when device found
|
||||||
|
this.gatt = undefined; // set when connected
|
||||||
|
// .on("status", => string
|
||||||
|
// .on("data"
|
||||||
|
// .on("disconnect"
|
||||||
|
this.resetStats();
|
||||||
|
// Set default values and merge with stored values
|
||||||
|
this.settings = Object.assign({
|
||||||
|
circum: 2068 // circumference in mm
|
||||||
|
}, (require('Storage').readJSON('blecsc.json', true) || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStats() {
|
||||||
|
this.cwr = undefined;
|
||||||
|
this.ccr = undefined;
|
||||||
|
this.lwet = undefined;
|
||||||
|
this.lcet = undefined;
|
||||||
|
this.lastCwr = undefined;
|
||||||
|
this.lastCcr = undefined;
|
||||||
|
this.lastLwet = undefined;
|
||||||
|
this.lastLcet = undefined;
|
||||||
|
this.kph = undefined;
|
||||||
|
this.wrps = 0; // wheel revs per second
|
||||||
|
this.crps = 0; // crank revs per second
|
||||||
|
//this.batteryLevel = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeviceAddress() {
|
||||||
|
if (!this.device || !this.device.id)
|
||||||
|
return '00:00:00:00:00:00';
|
||||||
|
return this.device.id.split(" ")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
status(txt) {
|
||||||
|
this.emit("status", txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and connect to a device which exposes the CSC service.
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
connect() {
|
||||||
|
this.status("Scanning");
|
||||||
|
// Find a device, then get the CSC Service and subscribe to
|
||||||
|
// notifications on the CSC Measurement characteristic.
|
||||||
|
// NRF.setLowPowerConnection(true);
|
||||||
|
var reconnect = this.reconnect; // auto-reconnect
|
||||||
|
return NRF.requestDevice({
|
||||||
|
timeout: 5000,
|
||||||
|
filters: [{
|
||||||
|
services: ["1816"]
|
||||||
|
}],
|
||||||
|
}).then(device => {
|
||||||
|
this.status("Connecting");
|
||||||
|
this.device = device;
|
||||||
|
this.device.on('gattserverdisconnected', event => {
|
||||||
|
this.device = undefined;
|
||||||
|
this.gatt = undefined;
|
||||||
|
this.resetStats();
|
||||||
|
this.status("Disconnected");
|
||||||
|
this.emit("disconnect", event);
|
||||||
|
if (reconnect) {// auto-reconnect
|
||||||
|
reconnect = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.reconnect) this.connect().then(() => {}, () => {});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise(resolve => setTimeout(resolve, 150)); // On CooSpo we get a 'Connection Timeout' if we try and connect too soon
|
||||||
|
}).then(() => {
|
||||||
|
return this.device.gatt.connect();
|
||||||
|
}).then(gatt => {
|
||||||
|
this.status("Connected");
|
||||||
|
this.gatt = gatt;
|
||||||
|
return gatt.getPrimaryService("1816");
|
||||||
|
}).then(service => {
|
||||||
|
return service.getCharacteristic("2a5b"); // UUID of the CSC measurement characteristic
|
||||||
|
}).then(characteristic => {
|
||||||
|
// register for changes on 2a5b
|
||||||
|
characteristic.on('characteristicvaluechanged', event => {
|
||||||
|
const flags = event.target.value.getUint8(0);
|
||||||
|
var offs = 0;
|
||||||
|
var data = {};
|
||||||
|
if (flags & 1) { // FLAGS_WREV_BM
|
||||||
|
this.lastCwr = this.cwr;
|
||||||
|
this.lastLwet = this.lwet;
|
||||||
|
this.cwr = event.target.value.getUint32(1, true);
|
||||||
|
this.lwet = event.target.value.getUint16(5, true);
|
||||||
|
if (this.lastCwr === undefined) this.lastCwr = this.cwr;
|
||||||
|
if (this.lastLwet === undefined) this.lastLwet = this.lwet;
|
||||||
|
if (this.lwet < this.lastLwet) this.lastLwet -= 65536;
|
||||||
|
let secs = (this.lwet - this.lastLwet) / 1024;
|
||||||
|
this.wrps = (this.cwr - this.lastCwr) / (secs?secs:1);
|
||||||
|
this.kph = this.wrps * this.settings.circum / 3600;
|
||||||
|
Object.assign(data, { // Notify the 'wheelEvent' handler
|
||||||
|
cwr: this.cwr, // cumulative wheel revolutions
|
||||||
|
lwet: this.lwet, // last wheel event time
|
||||||
|
wrps: this.wrps, // wheel revs per second
|
||||||
|
wr: this.cwr - this.lastCwr, // wheel revs
|
||||||
|
wdt : secs, // time period
|
||||||
|
kph : this.kph
|
||||||
|
});
|
||||||
|
offs += 6;
|
||||||
|
}
|
||||||
|
if (flags & 2) { // FLAGS_CREV_BM
|
||||||
|
this.lastCcr = this.ccr;
|
||||||
|
this.lastLcet = this.lcet;
|
||||||
|
this.ccr = event.target.value.getUint16(offs + 1, true);
|
||||||
|
this.lcet = event.target.value.getUint16(offs + 3, true);
|
||||||
|
if (this.lastCcr === undefined) this.lastCcr = this.ccr;
|
||||||
|
if (this.lastLcet === undefined) this.lastLcet = this.lcet;
|
||||||
|
if (this.lcet < this.lastLcet) this.lastLcet -= 65536;
|
||||||
|
let secs = (this.lcet - this.lastLcet) / 1024;
|
||||||
|
this.crps = (this.ccr - this.lastCcr) / (secs?secs:1);
|
||||||
|
Object.assign(data, { // Notify the 'crankEvent' handler
|
||||||
|
ccr: this.ccr, // cumulative crank revolutions
|
||||||
|
lcet: this.lcet, // last crank event time
|
||||||
|
crps: this.crps, // crank revs per second
|
||||||
|
cdt : secs, // time period
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.emit("data",data);
|
||||||
|
});
|
||||||
|
return characteristic.startNotifications();
|
||||||
|
/* }).then(() => {
|
||||||
|
return this.gatt.getPrimaryService("180f");
|
||||||
|
}).then(service => {
|
||||||
|
return service.getCharacteristic("2a19");
|
||||||
|
}).then(characteristic => {
|
||||||
|
characteristic.on('characteristicvaluechanged', (event)=>{
|
||||||
|
this.batteryLevel = event.target.value.getUint8(0);
|
||||||
|
});
|
||||||
|
return characteristic.startNotifications();*/
|
||||||
|
}).then(() => {
|
||||||
|
this.status("Ready");
|
||||||
|
}, err => {
|
||||||
|
this.status("Error: " + err);
|
||||||
|
if (reconnect) { // auto-reconnect
|
||||||
|
reconnect = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.reconnect) this.connect().then(() => {}, () => {});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect the device.
|
||||||
|
*/
|
||||||
|
disconnect() {
|
||||||
|
if (!this.gatt) return;
|
||||||
|
this.gatt.disconnect();
|
||||||
|
this.gatt = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start trying to connect - will keep searching and attempting to connect*/
|
||||||
|
start() {
|
||||||
|
this.reconnect = true;
|
||||||
|
if (!this.device)
|
||||||
|
this.connect().then(() => {}, () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop trying to connect, and disconnect */
|
||||||
|
stop() {
|
||||||
|
this.reconnect = false;
|
||||||
|
this.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an instance of BLECSC or create one if it doesn't exist
|
||||||
|
BLECSC.getInstance = function() {
|
||||||
|
if (!BLECSC.instance) {
|
||||||
|
BLECSC.instance = new BLECSC();
|
||||||
|
}
|
||||||
|
return BLECSC.instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports = BLECSC;
|
||||||
|
|
||||||
|
/*
|
||||||
|
var csc = require("blecsc").getInstance();
|
||||||
|
csc.on("status", txt => {
|
||||||
|
print("##", txt);
|
||||||
|
E.showMessage(txt);
|
||||||
|
});
|
||||||
|
csc.on("data", e => print(e));
|
||||||
|
csc.start();
|
||||||
|
*/
|
|
@ -0,0 +1,74 @@
|
||||||
|
(function() {
|
||||||
|
var csc = require("blecsc").getInstance();
|
||||||
|
//csc.on("status", txt => { print("CSC",txt); });
|
||||||
|
csc.on("data", e => {
|
||||||
|
ci.items.forEach(it => { if (it._visible) it.emit('redraw'); });
|
||||||
|
});
|
||||||
|
csc.on("disconnect", e => {
|
||||||
|
// redraw all with no info
|
||||||
|
ci.items.forEach(it => { if (it._visible) it.emit('redraw'); });
|
||||||
|
});
|
||||||
|
var uses = 0;
|
||||||
|
var ci = {
|
||||||
|
name: "CSC",
|
||||||
|
items: [
|
||||||
|
{ name : "Speed",
|
||||||
|
get : () => {
|
||||||
|
return {
|
||||||
|
text : (csc.kph === undefined) ? "--" : require("locale").speed(csc.kph),
|
||||||
|
img : atob("GBiBAAAAAAAAAAAAAAABwAABwAeBgAMBgAH/gAH/wAPDwA/DcD9m/Ge35sW9o8//M8/7E8CBA2GBhn8A/h4AeAAAAAAAAAAAAAAAAA==")
|
||||||
|
};
|
||||||
|
},
|
||||||
|
show : function() {
|
||||||
|
uses++;
|
||||||
|
if (uses==1) csc.start();
|
||||||
|
this._visible = true;
|
||||||
|
},
|
||||||
|
hide : function() {
|
||||||
|
this._visible = false;
|
||||||
|
uses--;
|
||||||
|
if (uses==0) csc.stop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name : "Distance",
|
||||||
|
get : () => {
|
||||||
|
return {
|
||||||
|
text : (csc.kph === undefined) ? "--" : require("locale").distance(csc.cwr * csc.settings.circum / 1000),
|
||||||
|
img : atob("GBiBAAAAAB8AADuAAGDAAGTAAGRAAEBAAGBAAGDAADCAADGAIB8B+A/BjAfjBgAyJgAyIgAyAj/jBnADBmABjGAA2HAA8D//4AAAAA==")
|
||||||
|
};
|
||||||
|
},
|
||||||
|
show : function() {
|
||||||
|
uses++;
|
||||||
|
if (uses==1) csc.start();
|
||||||
|
this._visible = true;
|
||||||
|
},
|
||||||
|
hide : function() {
|
||||||
|
this._visible = false;
|
||||||
|
uses--;
|
||||||
|
if (uses==0) csc.stop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name : "Cadence",
|
||||||
|
get : () => {
|
||||||
|
return {
|
||||||
|
text : (csc.crps === undefined) ? "--" : Math.round(csc.crps*60),
|
||||||
|
img : atob("GBiBAAAAAAAAAAB+EAH/sAeB8A4A8AwB8BgAABgAADAAADAAADAAADAADDAADDAAABgAABgAGAwAEA4AAAeAwAH8gAB8AAAAAAAAAA==")
|
||||||
|
};
|
||||||
|
},
|
||||||
|
show : function() {
|
||||||
|
uses++;
|
||||||
|
if (uses==1) csc.start();
|
||||||
|
this._visible = true;
|
||||||
|
},
|
||||||
|
hide : function() {
|
||||||
|
this._visible = false;
|
||||||
|
uses--;
|
||||||
|
if (uses==0) csc.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return ci;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"id": "blecsc",
|
||||||
|
"name": "BLE Cycling Speed Sensor Library",
|
||||||
|
"shortName": "BLE CSC",
|
||||||
|
"version": "0.03",
|
||||||
|
"description": "Module to get live values from a BLE Cycle Speed (CSC) sensor. Includes recorder and clockinfo plugins",
|
||||||
|
"icon": "icons8-cycling-48.png",
|
||||||
|
"tags": "outdoors,exercise,ble,bluetooth,clkinfo",
|
||||||
|
"type":"module",
|
||||||
|
"provides_modules" : ["blecsc"],
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"blecsc","url":"blecsc.js"},
|
||||||
|
{"name":"blecsc.settings.js","url":"settings.js"},
|
||||||
|
{"name":"blecsc.recorder.js","url":"recorder.js"},
|
||||||
|
{"name":"blecsc.clkinfo.js","url":"clkinfo.js"}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"blecsc.json"}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
(function(recorders) {
|
||||||
|
recorders.blecsc = function() {
|
||||||
|
var csc = require("blecsc").getInstance();
|
||||||
|
var speed, cadence;
|
||||||
|
csc.on("data", e => {
|
||||||
|
speed = e.kph; // speed in KPH
|
||||||
|
cadence = (e.crps===undefined)?"":Math.round(e.crps*60); // crank rotations per minute
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
name : "CSC",
|
||||||
|
fields : ["Speed (kph)","Cadence (rpm)"],
|
||||||
|
getValues : () => {
|
||||||
|
var r = [speed,cadence];
|
||||||
|
speed = "";
|
||||||
|
cadence = "";
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
start : () => {
|
||||||
|
csc.start();
|
||||||
|
},
|
||||||
|
stop : () => {
|
||||||
|
csc.stop();
|
||||||
|
},
|
||||||
|
draw : (x,y) => g.setColor(csc.device?"#0f0":"#8f8").drawImage(atob("Dw+BAAAAAAABgOIA5gHcBxw9fpfTPqYRC8HgAAAAAAAA"),x,y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
(function(back) {
|
||||||
|
const storage = require('Storage')
|
||||||
|
const SETTINGS_FILE = 'blecsc.json'
|
||||||
|
|
||||||
|
// Set default values and merge with stored values
|
||||||
|
let settings = Object.assign({
|
||||||
|
circum: 2068 // circumference in mm
|
||||||
|
}, (storage.readJSON(SETTINGS_FILE, true) || {}));
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
storage.writeJSON(SETTINGS_FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
function circumMenu() {
|
||||||
|
var v = 0|settings.circum;
|
||||||
|
var cm = 0|(v/10);
|
||||||
|
var mm = v-(cm*10);
|
||||||
|
E.showMenu({
|
||||||
|
'': { title: /*LANG*/"Circumference", back: mainMenu },
|
||||||
|
'cm': {
|
||||||
|
value: cm,
|
||||||
|
min: 80, max: 240, step: 1,
|
||||||
|
onchange: (v) => {
|
||||||
|
cm = v;
|
||||||
|
settings.circum = (cm*10)+mm;
|
||||||
|
saveSettings();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'+ mm': {
|
||||||
|
value: mm,
|
||||||
|
min: 0, max: 9, step: 1,
|
||||||
|
onchange: (v) => {
|
||||||
|
mm = v;
|
||||||
|
settings.circum = (cm*10)+mm;
|
||||||
|
saveSettings();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/*LANG*/'Std Wheels': function() {
|
||||||
|
// https://support.wahoofitness.com/hc/en-us/articles/115000738484-Tire-Size-Wheel-Circumference-Chart
|
||||||
|
E.showMenu({
|
||||||
|
'': { title: /*LANG*/'Std Wheels', back: circumMenu },
|
||||||
|
'650x38 wheel' : function() {
|
||||||
|
settings.circum = 1995;
|
||||||
|
saveSettings();
|
||||||
|
mainMenu();
|
||||||
|
},
|
||||||
|
'700x32c wheel' : function() {
|
||||||
|
settings.circum = 2152;
|
||||||
|
saveSettings();
|
||||||
|
mainMenu();
|
||||||
|
},
|
||||||
|
'24"x1.75 wheel' : function() {
|
||||||
|
settings.circum = 1890;
|
||||||
|
saveSettings();
|
||||||
|
mainMenu();
|
||||||
|
},
|
||||||
|
'26"x1.5 wheel' : function() {
|
||||||
|
settings.circum = 2010;
|
||||||
|
saveSettings();
|
||||||
|
mainMenu();
|
||||||
|
},
|
||||||
|
'27.5"x1.5 wheel' : function() {
|
||||||
|
settings.circum = 2079;
|
||||||
|
saveSettings();
|
||||||
|
mainMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mainMenu() {
|
||||||
|
E.showMenu({
|
||||||
|
'': { 'title': 'BLE CSC' },
|
||||||
|
'< Back': back,
|
||||||
|
/*LANG*/'Circumference': {
|
||||||
|
value: settings.circum+"mm",
|
||||||
|
onchange: circumMenu
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mainMenu();
|
||||||
|
})
|
|
@ -69,3 +69,4 @@
|
||||||
0.58: "Make Connectable" temporarily bypasses the whitelist
|
0.58: "Make Connectable" temporarily bypasses the whitelist
|
||||||
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
||||||
0.60: Minor code improvements
|
0.60: Minor code improvements
|
||||||
|
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
|
|
@ -14,15 +14,15 @@ if (DEBUG) {
|
||||||
}
|
}
|
||||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||||
} else {
|
} else {
|
||||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||||
}
|
}
|
||||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
boot += `{eval(require('Storage').read('bootupdate.js'));print("Storage Updated!")}else{\n`;
|
||||||
boot += `E.setFlags({pretokenise:1});\n`;
|
boot += `E.setFlags({pretokenise:1});\n`;
|
||||||
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
||||||
bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
|
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
|
||||||
if (s.ble!==false) {
|
if (s.ble!==false) {
|
||||||
if (s.HID) { // Human interface device
|
if (s.HID) { // Human interface device
|
||||||
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
||||||
|
@ -122,6 +122,7 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||||
return a==b ? 0 : (a>b ? 1 : -1);
|
return a==b ? 0 : (a>b ? 1 : -1);
|
||||||
});
|
});
|
||||||
// precalculate file size
|
// precalculate file size
|
||||||
|
bootPost += "}";
|
||||||
let fileSize = boot.length + bootPost.length;
|
let fileSize = boot.length + bootPost.length;
|
||||||
bootFiles.forEach(bootFile=>{
|
bootFiles.forEach(bootFile=>{
|
||||||
// match the size of data we're adding below in bootFiles.forEach
|
// match the size of data we're adding below in bootFiles.forEach
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.60",
|
"version": "0.61",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
// @ts-ignore helper
|
// @ts-expect-error helper
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const __assign = Object.assign;
|
const __assign = Object.assign;
|
||||||
|
|
||||||
const Layout = require("Layout");
|
const Layout = require("Layout");
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
0.04: Added notification
|
0.04: Added notification
|
||||||
0.05: Fixed boot
|
0.05: Fixed boot
|
||||||
0.06: Allow tap to silence notification/buzzing
|
0.06: Allow tap to silence notification/buzzing
|
||||||
|
0.07: Fix notification-tap silencing and notification length
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
lim = sum / cnt;
|
lim = sum / cnt;
|
||||||
require('Storage').writeJSON('chargent.json', {limit: lim});
|
require('Storage').writeJSON('chargent.json', {limit: lim});
|
||||||
}
|
}
|
||||||
const onHide = () => { id = clearInterval(id) };
|
const onHide = () => { if(id) id = clearInterval(id) };
|
||||||
require('notify').show({id: 'chargent', title: 'Fully charged', onHide });
|
require('notify').show({id: 'chargent', title: 'Charged', onHide });
|
||||||
// TODO ? customizable
|
// TODO ? customizable
|
||||||
Bangle.buzz(500);
|
Bangle.buzz(500);
|
||||||
setTimeout(() => Bangle.buzz(500), 1000);
|
setTimeout(() => Bangle.buzz(500), 1000);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "chargent",
|
{ "id": "chargent",
|
||||||
"name": "Charge Gently",
|
"name": "Charge Gently",
|
||||||
"version": "0.06",
|
"version": "0.07",
|
||||||
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: added settings options to change date format
|
0.02: added settings options to change date format
|
||||||
0.03: Remove un-needed font requirement, now outputs transparent image
|
0.03: Remove un-needed font requirement, now outputs transparent image
|
||||||
|
0.04: Fix image after 0.03 regression
|
|
@ -20,7 +20,7 @@
|
||||||
get : () => {
|
get : () => {
|
||||||
let d = new Date();
|
let d = new Date();
|
||||||
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
||||||
g.transparent = 1;
|
g.transparent = 0;
|
||||||
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
||||||
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "clkinfocal",
|
{ "id": "clkinfocal",
|
||||||
"name": "Calendar Clockinfo",
|
"name": "Calendar Clockinfo",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text",
|
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
var info = {
|
var info = {
|
||||||
name: "Gps",
|
name: "GPS",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: "gridref",
|
name: "gridref",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New Clock Info!
|
|
@ -0,0 +1,27 @@
|
||||||
|
(function() {
|
||||||
|
var speed;
|
||||||
|
function gpsHandler(e) {
|
||||||
|
speed = e.speed;
|
||||||
|
ci.items[0].emit('redraw');
|
||||||
|
}
|
||||||
|
var ci = {
|
||||||
|
name: "GPS",
|
||||||
|
items: [
|
||||||
|
{ name : "Speed",
|
||||||
|
get : function() { return { text : isFinite(speed) ? require("locale").speed(speed) : "--",
|
||||||
|
v : 0, min : isFinite(speed) ? speed : 0, max : 150,
|
||||||
|
img : atob("GBiBAAAAAAAAAAAAAAAAAAD/AAHDgAMYwAbDYAwAMAoA0BgDmBgfGB4ceBgYGBgAGBoAWAwAMAwAMAf/4AP/wAAAAAAAAAAAAAAAAA==") }},
|
||||||
|
show : function() {
|
||||||
|
Bangle.setGPSPower(1, "clkinfogpsspeed");
|
||||||
|
Bangle.on("GPS", gpsHandler);
|
||||||
|
},
|
||||||
|
hide : function() {
|
||||||
|
Bangle.removeListener("GPS", gpsHandler);
|
||||||
|
Bangle.setGPSPower(0, "clkinfogpsspeed");
|
||||||
|
}
|
||||||
|
// run : function() {} optional (called when tapped)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return ci;
|
||||||
|
}) // must not have a semi-colon!
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
{ "id": "clkinfogpsspeed",
|
||||||
|
"name": "GPS Speed Clockinfo",
|
||||||
|
"shortName":"GPS Speed",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A Clockinfo that displays your current speed according to the GPS",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "clkinfo",
|
||||||
|
"tags": "clkinfo",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"clkinfogpsspeed.clkinfo.js","url":"clkinfo.js"}
|
||||||
|
]
|
||||||
|
}
|
|
@ -10,3 +10,4 @@
|
||||||
0.09: Save clkinfo settings on kill and remove
|
0.09: Save clkinfo settings on kill and remove
|
||||||
0.10: Fix focus bug when changing focus between two clock infos
|
0.10: Fix focus bug when changing focus between two clock infos
|
||||||
0.11: Prepend swipe listener if possible
|
0.11: Prepend swipe listener if possible
|
||||||
|
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle
|
|
@ -371,6 +371,46 @@ exports.addInteractive = function(menu, options) {
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* clockinfos usually return a 24x24 image. This draws that image but
|
||||||
|
recolors it such that it is transparent, with the middle of the image as background
|
||||||
|
and the image itself as foreground. options is passed to g.drawImage */
|
||||||
|
exports.drawFilledImage = function(img,x,y,options) {
|
||||||
|
if (!img) return;
|
||||||
|
if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options);
|
||||||
|
let gfx = exports.imgGfx;
|
||||||
|
if (!gfx) {
|
||||||
|
gfx = exports.imgGfx = Graphics.createArrayBuffer(26, 26, 2, {msb:true});
|
||||||
|
gfx.transparent = 3;
|
||||||
|
gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor("#888"), g.toColor("#888")]);
|
||||||
|
}
|
||||||
|
/* img is (usually) a black and white transparent image. But we really would like the bits in
|
||||||
|
the middle of it to be white. So what we do is we draw a slightly bigger rectangle in white,
|
||||||
|
draw the image, and then flood-fill the rectangle back to the background color. floodFill
|
||||||
|
was only added in 2v18 so we have to check for it and fallback if not. */
|
||||||
|
gfx.clear(1).setColor(1).drawImage(img, 1,1).floodFill(0,0,3);
|
||||||
|
var scale = (options && options.scale) || 1;
|
||||||
|
return g.drawImage(gfx, x-scale,y-scale,options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* clockinfos usually return a 24x24 image. This creates a 26x26 gfx of the image but
|
||||||
|
recolors it such that it is transparent, with the middle and border of the image as background
|
||||||
|
and the image itself as foreground. options is passed to g.drawImage */
|
||||||
|
exports.drawBorderedImage = function(img,x,y,options) {
|
||||||
|
if (!img) return;
|
||||||
|
if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options);
|
||||||
|
let gfx = exports.imgGfxB;
|
||||||
|
if (!gfx) {
|
||||||
|
gfx = exports.imgGfxB = Graphics.createArrayBuffer(28, 28, 2, {msb:true});
|
||||||
|
gfx.transparent = 3;
|
||||||
|
gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.theme.bg/*border*/, g.toColor("#888")]);
|
||||||
|
}
|
||||||
|
gfx.clear(1).setColor(2).drawImage(img, 1,1).drawImage(img, 3,1).drawImage(img, 1,3).drawImage(img, 3,3); // border
|
||||||
|
gfx.setColor(1).drawImage(img, 2,2); // main image
|
||||||
|
gfx.floodFill(27,27,3); // flood fill edge to transparent
|
||||||
|
var o = ((options && options.scale) || 1)*2;
|
||||||
|
return g.drawImage(gfx, x-o,y-o,options);
|
||||||
|
};
|
||||||
|
|
||||||
// Code for testing (plots all elements from first list)
|
// Code for testing (plots all elements from first list)
|
||||||
/*
|
/*
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{ "id": "clock_info",
|
{ "id": "clock_info",
|
||||||
"name": "Clock Info Module",
|
"name": "Clock Info Module",
|
||||||
"shortName": "Clock Info",
|
"shortName": "Clock Info",
|
||||||
"version":"0.11",
|
"version":"0.12",
|
||||||
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"tags": "clkinfo",
|
"tags": "clkinfo,clockinfo",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"provides_modules" : ["clock_info"],
|
"provides_modules" : ["clock_info"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Moved settings into 'Settings->Apps'
|
0.02: Moved settings into 'Settings->Apps'
|
||||||
|
0.03: Add 'Squares' option for random squares background
|
|
@ -9,8 +9,12 @@ By default the app provides just a red/green/blue background but it can easily b
|
||||||
|
|
||||||
You can either:
|
You can either:
|
||||||
|
|
||||||
* Go to [the Clock Backgrounds app](https://banglejs.com/apps/?id=clockbg) in the App Loader and upload backgrounds
|
* Go to [the Clock Backgrounds app](https://banglejs.com/apps/?id=clockbg) in the App Loader and use pre-made image backgrounds (or upload your own)
|
||||||
* Go to the `Backgrounds` app on the Bangle itself, and choose between solid color, random colors, or any uploaded images.
|
* Go to the `Backgrounds` app on the Bangle itself, and choose between:
|
||||||
|
* `Solid Color` - one color that never changes
|
||||||
|
* `Random Color` - a new color every time the clock starts
|
||||||
|
* `Image` - choose from a previously uploaded image
|
||||||
|
* `Squares` - a randomly generated pattern of squares in the selected color palette
|
||||||
|
|
||||||
|
|
||||||
## Usage in code
|
## Usage in code
|
||||||
|
@ -33,8 +37,10 @@ ensure that the clock background library is automatically loaded.
|
||||||
|
|
||||||
## Features to be added
|
## Features to be added
|
||||||
|
|
||||||
This library/app is still pretty basic right now, but a few features could be added that would really improve functionality:
|
A few features could be added that would really improve functionality:
|
||||||
|
|
||||||
* Support for >1 image, and choosing randomly between them
|
* When 'fast loading', 'random' backgrounds don't update at the moment
|
||||||
|
* Support for >1 image to be uploaded (requires some image management in `interface.html`), and choose randomly between them
|
||||||
* Support for gradients (random colors)
|
* Support for gradients (random colors)
|
||||||
|
* More types of auto-generated pattern (as long as they can be generated quickly or in the background)
|
||||||
* Storing 'clear' areas of uploaded images so clocks can easily position themselves
|
* Storing 'clear' areas of uploaded images so clocks can easily position themselves
|
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 597 B |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 474 B |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 597 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 597 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 684 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 477 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 451 B |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 58 KiB |
|
@ -2,27 +2,29 @@
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
<style>
|
<style>
|
||||||
.flag {
|
.thumbnail {
|
||||||
width : 100px;
|
width : 100px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#preview {
|
#preview {
|
||||||
width : 176px;
|
width : 178px; /* include the border */
|
||||||
height: 176px;
|
height: 178px;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Upload an image:</p>
|
<p>Upload an image: <input class="form-input" id="customfile" type="file"></p>
|
||||||
<div id="flaglist"></div>
|
<div id="thumbnaillist"></div>
|
||||||
<p>If you'd like to contribute images you can <a href="https://github.com/espruino/BangleApps/tree/master/apps/patriotclk/img" target="_blank">add them on GitHub</a>!</p>
|
<p>If you'd like to contribute images you can <a href="https://github.com/espruino/BangleApps/tree/master/apps/patriotclk/img" target="_blank">add them on GitHub</a>!</p>
|
||||||
<div style="float:right">Preview:<br/><canvas width="176" height="176" id="preview"></canvas></div>
|
<div style="float:right">Preview:<br/><canvas width="176" height="176" id="preview"></canvas></div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-switch">
|
<select class="form-select" id="box_zoom" style="width:inherit">
|
||||||
<input type="checkbox" id="box_zoom">
|
<option value="0">No Zoom</option>
|
||||||
<i class="form-icon"></i> Zoom
|
<option value="1">10% Zoom</option>
|
||||||
</label>
|
<option value="1.5">15% Zoom</option>
|
||||||
|
<option value="2">20% Zoom</option>
|
||||||
|
</select><br/>
|
||||||
<label class="form-switch">
|
<label class="form-switch">
|
||||||
<input type="checkbox" id="box_mirror">
|
<input type="checkbox" id="box_mirror">
|
||||||
<i class="form-icon"></i> Mirror
|
<i class="form-icon"></i> Mirror
|
||||||
|
@ -55,6 +57,8 @@
|
||||||
{"path":"img/ai_robot.jpeg","dither":true},
|
{"path":"img/ai_robot.jpeg","dither":true},
|
||||||
{"path":"img/ai_eye.jpeg","dither":true},
|
{"path":"img/ai_eye.jpeg","dither":true},
|
||||||
{"path":"img/ai_hero.jpeg","dither":true},
|
{"path":"img/ai_hero.jpeg","dither":true},
|
||||||
|
{"path":"img/ai_flow.jpeg","dither":true},
|
||||||
|
{"path":"img/ai_horse.jpeg","dither":true},
|
||||||
{"path":"img/icons8-australia-480.png","dither":false},
|
{"path":"img/icons8-australia-480.png","dither":false},
|
||||||
{"path":"img/icons8-austria-480.png","dither":false},
|
{"path":"img/icons8-austria-480.png","dither":false},
|
||||||
{"path":"img/icons8-belgium-480.png","dither":false},
|
{"path":"img/icons8-belgium-480.png","dither":false},
|
||||||
|
@ -85,22 +89,21 @@
|
||||||
var selectedImage;
|
var selectedImage;
|
||||||
var bgImageData;
|
var bgImageData;
|
||||||
|
|
||||||
document.getElementById("flaglist").innerHTML =
|
document.getElementById("thumbnaillist").innerHTML = `<img class="thumbnail" id="customimage" src="" face="custom" style="display:none"/>\n`+
|
||||||
IMAGES.map(f => `<img class="flag" src="${f.path}" data-file="${f.path}"/>`).join("\n");
|
IMAGES.map(f => `<img class="thumbnail" src="${f.path}" data-file="${f.path}"/>`).join("\n");
|
||||||
var elements = document.querySelectorAll(".flag");
|
var elements = document.querySelectorAll(".thumbnail");
|
||||||
for (var i=0;i<elements.length;i++)
|
for (var i=0;i<elements.length;i++)
|
||||||
elements[i].addEventListener("click", function(e) {
|
elements[i].addEventListener("click", function(e) {
|
||||||
selectedImage = e.target;
|
selectedImage = e.target;
|
||||||
drawPreview();
|
drawPreview();
|
||||||
document.getElementById("upload").classList.remove("disabled")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function drawPreview() {
|
function drawPreview() {
|
||||||
if (!selectedImage) return;
|
if (!selectedImage) return;
|
||||||
var imgPath = selectedImage.getAttribute("data-file");
|
var imgPath = selectedImage.getAttribute("data-file");
|
||||||
var img = IMAGES.find(img => img.path == imgPath);
|
var img = IMAGES.find(img => img.path == imgPath) || {"dither":true}; // No IMAGES entry for custom images
|
||||||
var zoom = document.getElementById("box_zoom").checked;
|
var zoom = document.getElementById("box_zoom").value;
|
||||||
var dither = document.getElementById("box_dither").value;
|
var dither = document.getElementById("box_dither").value;
|
||||||
if (dither=="" && img.dither) dither="bayer2";
|
if (dither=="" && img.dither) dither="bayer2";
|
||||||
if (dither=="no" || dither=="") dither=undefined;
|
if (dither=="no" || dither=="") dither=undefined;
|
||||||
|
@ -115,8 +118,13 @@
|
||||||
let imgW = selectedImage.naturalWidth;
|
let imgW = selectedImage.naturalWidth;
|
||||||
let imgH = selectedImage.naturalHeight;
|
let imgH = selectedImage.naturalHeight;
|
||||||
let border = 0;
|
let border = 0;
|
||||||
if (imgW > 400) border = 20;
|
let imgMin = Math.min(imgW,imgH);
|
||||||
if (zoom) border = (border*5) >> 1;
|
switch (zoom) {
|
||||||
|
case "0": border = 0; break;
|
||||||
|
case "1": border = Math.round(imgMin*0.1); break;
|
||||||
|
case "1.5": border = Math.round(imgMin*0.15); break;
|
||||||
|
case "2": border = Math.round(imgMin*0.2); break;
|
||||||
|
}
|
||||||
ctx.save(); // Save the current state
|
ctx.save(); // Save the current state
|
||||||
if (mirror) {
|
if (mirror) {
|
||||||
ctx.translate(canvas.width, 0);
|
ctx.translate(canvas.width, 0);
|
||||||
|
@ -135,6 +143,7 @@
|
||||||
brightness:64*brightness
|
brightness:64*brightness
|
||||||
};
|
};
|
||||||
bgImageData = imageconverter.canvastoString(canvas, options);
|
bgImageData = imageconverter.canvastoString(canvas, options);
|
||||||
|
document.getElementById("upload").classList.remove("disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If options changed
|
// If options changed
|
||||||
|
@ -163,6 +172,20 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// Custom image upload
|
||||||
|
document.getElementById('customfile').onchange = function (evt) {
|
||||||
|
var tgt = evt.target || window.event.srcElement, files = tgt.files;
|
||||||
|
if (FileReader && files && files.length) {
|
||||||
|
var fr = new FileReader();
|
||||||
|
fr.onload = function () {
|
||||||
|
document.getElementById("customimage").src = fr.result;
|
||||||
|
document.getElementById("customimage").style.display = "inline-block";
|
||||||
|
selectedImage = document.getElementById("customimage");
|
||||||
|
drawPreview();
|
||||||
|
}
|
||||||
|
fr.readAsDataURL(files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onInit() {
|
function onInit() {
|
||||||
Util.readStorageJSON("clockbg.json", function(data) {
|
Util.readStorageJSON("clockbg.json", function(data) {
|
||||||
|
|
|
@ -4,10 +4,21 @@ let settings = Object.assign({
|
||||||
},require("Storage").readJSON("clockbg.json")||{});
|
},require("Storage").readJSON("clockbg.json")||{});
|
||||||
if (settings.style=="image")
|
if (settings.style=="image")
|
||||||
settings.img = require("Storage").read(settings.fn);
|
settings.img = require("Storage").read(settings.fn);
|
||||||
if (settings.style=="randomcolor") {
|
else if (settings.style=="randomcolor") {
|
||||||
settings.style = "color";
|
settings.style = "color";
|
||||||
var n = (0|(Math.random()*settings.colors.length)) % settings.colors.length;
|
let n = (0|(Math.random()*settings.colors.length)) % settings.colors.length;
|
||||||
settings.color = settings.colors[n];
|
settings.color = settings.colors[n];
|
||||||
|
delete settings.colors;
|
||||||
|
} else if (settings.style=="squares") {
|
||||||
|
settings.style = "image";
|
||||||
|
let bpp = (settings.colors.length>4)?4:2;
|
||||||
|
let bg = Graphics.createArrayBuffer(11,11,bpp,{msb:true});
|
||||||
|
E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256); // random pixels
|
||||||
|
bg.palette = new Uint16Array(1<<bpp);
|
||||||
|
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
|
||||||
|
settings.img = bg.asImage("string");
|
||||||
|
settings.imgOpt = {scale:16};
|
||||||
|
delete settings.colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill a rectangle with the current background style, rect = {x,y,w,h}
|
// Fill a rectangle with the current background style, rect = {x,y,w,h}
|
||||||
|
@ -16,7 +27,7 @@ if (settings.style=="randomcolor") {
|
||||||
exports.fillRect = function(rect,y,x2,y2) {
|
exports.fillRect = function(rect,y,x2,y2) {
|
||||||
if ("object"!=typeof rect) rect = {x:rect,y:y,w:1+x2-rect,h:1+y2-y};
|
if ("object"!=typeof rect) rect = {x:rect,y:y,w:1+x2-rect,h:1+y2-y};
|
||||||
if (settings.img) {
|
if (settings.img) {
|
||||||
g.setClipRect(rect.x, rect.y, rect.x+rect.w-1, rect.y+rect.h-1).drawImage(settings.img).setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
g.setClipRect(rect.x, rect.y, rect.x+rect.w-1, rect.y+rect.h-1).drawImage(settings.img,0,0,settings.imgOpt).setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||||
} else if (settings.style == "color") {
|
} else if (settings.style == "color") {
|
||||||
g.setBgColor(settings.color).clearRect(rect);
|
g.setBgColor(settings.color).clearRect(rect);
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,4 +35,3 @@ exports.fillRect = function(rect,y,x2,y2) {
|
||||||
g.setBgColor(g.theme.bg).clearRect(rect);
|
g.setBgColor(g.theme.bg).clearRect(rect);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{ "id": "clockbg",
|
{ "id": "clockbg",
|
||||||
"name": "Clock Backgrounds",
|
"name": "Clock Backgrounds",
|
||||||
"shortName":"Backgrounds",
|
"shortName":"Backgrounds",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Library that allows clocks to include a custom background, from a library or uploaded.",
|
"description": "Library that allows clocks to include a custom background (generated on demand or uploaded).",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"provides_modules" : ["clockbg"],
|
"provides_modules" : ["clockbg"],
|
||||||
|
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -9,7 +9,7 @@ function saveSettings() {
|
||||||
delete settings.fn;
|
delete settings.fn;
|
||||||
if (settings.style!="color")
|
if (settings.style!="color")
|
||||||
delete settings.color;
|
delete settings.color;
|
||||||
if (settings.style!="randomcolor")
|
if (settings.style!="randomcolor" && settings.style!="squares")
|
||||||
delete settings.colors;
|
delete settings.colors;
|
||||||
require("Storage").writeJSON("clockbg.json", settings);
|
require("Storage").writeJSON("clockbg.json", settings);
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,11 @@ function getColorsImage(cols) {
|
||||||
var bpp = 1;
|
var bpp = 1;
|
||||||
if (cols.length>4) bpp=4;
|
if (cols.length>4) bpp=4;
|
||||||
else if (cols.length>2) bpp=2;
|
else if (cols.length>2) bpp=2;
|
||||||
var b = Graphics.createArrayBuffer(16*cols.length,16,bpp);
|
var w = (cols.length>8)?8:16;
|
||||||
|
var b = Graphics.createArrayBuffer(w*cols.length,16,bpp);
|
||||||
b.palette = new Uint16Array(1<<bpp);
|
b.palette = new Uint16Array(1<<bpp);
|
||||||
cols.forEach((c,i)=>{
|
cols.forEach((c,i)=>{
|
||||||
b.setColor(i).fillRect(i*16,0,i*16+15,15);
|
b.setColor(i).fillRect(i*w,0,i*w+w-1,15);
|
||||||
b.palette[i] = g.toColor(c);
|
b.palette[i] = g.toColor(c);
|
||||||
});
|
});
|
||||||
return "\0"+b.asImage("string");
|
return "\0"+b.asImage("string");
|
||||||
|
@ -49,6 +50,7 @@ function showModeMenu() {
|
||||||
var cols = [
|
var cols = [
|
||||||
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
|
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
|
||||||
["#F00","#0F0","#00F"],
|
["#F00","#0F0","#00F"],
|
||||||
|
// Please add some more!
|
||||||
];
|
];
|
||||||
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
|
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
|
||||||
cols.forEach(col => {
|
cols.forEach(col => {
|
||||||
|
@ -78,6 +80,31 @@ function showModeMenu() {
|
||||||
E.showAlert("Please use App Loader to upload images").then(showModeMenu);
|
E.showAlert("Please use App Loader to upload images").then(showModeMenu);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/*LANG*/"Squares" : function() {
|
||||||
|
/*
|
||||||
|
a = new Array(16);
|
||||||
|
a.fill(0);
|
||||||
|
print(a.map((n,i)=>E.HSBtoRGB(0 + i/16,1,1,24).toString(16).padStart(6,0).replace(/(.).(.).(.)./,"\"#$1$2$3\"")).join(","))
|
||||||
|
*/
|
||||||
|
var cols = [ // list of color palettes used as possible square colours - either 4 or 16 entries
|
||||||
|
["#00f","#05f","#0bf","#0fd","#0f7","#0f1","#3f0","#9f0","#ff0","#f90","#f30","#f01","#f07","#f0d","#b0f","#50f"],
|
||||||
|
["#0FF","#0CC","#088","#044"],
|
||||||
|
["#FFF","#FBB","#F66","#F44"],
|
||||||
|
["#FFF","#BBB","#666","#000"]
|
||||||
|
// Please add some more!
|
||||||
|
];
|
||||||
|
var menu = {"":{title:/*LANG*/"Squares", back:showModeMenu}};
|
||||||
|
cols.forEach(col => {
|
||||||
|
menu[getColorsImage(col)] = () => {
|
||||||
|
settings.style = "squares";
|
||||||
|
settings.colors = col;
|
||||||
|
console.log(settings);
|
||||||
|
saveSettings();
|
||||||
|
showMainMenu();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
|
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
|
||||||
0.02: Minor code improvements
|
0.02: Minor code improvements
|
||||||
|
0.03: Minor code improvements
|
||||||
|
|
|
@ -35,22 +35,22 @@ var v_model=process.env.BOARD;
|
||||||
var v_color_text='#FB0E01';
|
var v_color_text='#FB0E01';
|
||||||
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
|
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
|
||||||
//RGB565 requires only 16 (5+6+5) bits/2 bytes
|
//RGB565 requires only 16 (5+6+5) bits/2 bytes
|
||||||
var a_colors_str= Array('White RGB565 0x','Orange','DarkGreen','Yellow',
|
var a_colors_str= ['White RGB565 0x','Orange','DarkGreen','Yellow',
|
||||||
'Maroon','Blue','green','Purple',
|
'Maroon','Blue','green','Purple',
|
||||||
'cyan','olive','DarkCyan','DarkGrey',
|
'cyan','olive','DarkCyan','DarkGrey',
|
||||||
'Navy','Red','Magenta','GreenYellow',
|
'Navy','Red','Magenta','GreenYellow',
|
||||||
'Blush RGB888','pure red','Orange','Grey green',
|
'Blush RGB888','pure red','Orange','Grey green',
|
||||||
'D. grey','Almond','Amber','Bone',
|
'D. grey','Almond','Amber','Bone',
|
||||||
'Canary','Aero blue','Camel','Baby pink',
|
'Canary','Aero blue','Camel','Baby pink',
|
||||||
'Y.Corn','Cultured','Eigengrau','Citrine');
|
'Y.Corn','Cultured','Eigengrau','Citrine'];
|
||||||
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,
|
var a_colors= [0xFFFF,0xFD20,0x03E0,0xFFE0,
|
||||||
0x7800,0x001F,0x07E0,0x780F,
|
0x7800,0x001F,0x07E0,0x780F,
|
||||||
0x07FF,0x7BE0,0x03EF,0x7BEF,
|
0x07FF,0x7BE0,0x03EF,0x7BEF,
|
||||||
0x000F,0xF800,0xF81F,0xAFE5,
|
0x000F,0xF800,0xF81F,0xAFE5,
|
||||||
'#DE5D83','#FB0E01','#E56E06','#7E795C',
|
'#DE5D83','#FB0E01','#E56E06','#7E795C',
|
||||||
'#404040','#EFDECD','#FFBF00','#E3DAC9',
|
'#404040','#EFDECD','#FFBF00','#E3DAC9',
|
||||||
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
|
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
|
||||||
'#FBEC5D','#F5F5F5','#16161D','#E4D00A');
|
'#FBEC5D','#F5F5F5','#16161D','#E4D00A'];
|
||||||
var v_color_lines=0xFFFF; //White hex format
|
var v_color_lines=0xFFFF; //White hex format
|
||||||
|
|
||||||
|
|
||||||
|
|