1
0
Fork 0

Merge branch 'espruino:master' into master

master
jeonlab 2024-05-21 16:14:14 -05:00 committed by GitHub
commit 2e241d8505
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
223 changed files with 5522 additions and 2090 deletions

View File

@ -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 = {
"env": {
@ -195,9 +217,32 @@ module.exports = {
"no-control-regex" : "off"
},
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}]) => ({
files: [filePath],
rules: Object.fromEntries(rules.map(rule => [rule, "off"])),
})),
],
ignorePatterns: findGeneratedJS(["apps/", "modules/"]),
}

View File

@ -6,6 +6,8 @@ Bangle.js App Loader (and 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/)
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
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.

View File

@ -1,16 +1,17 @@
{
// timeout used to update every minute
let drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
let queueDraw = function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
};
function draw() {
let draw = function() {
// queue next draw in one minute
queueDraw();
// Work out where to draw...
@ -30,7 +31,7 @@ function draw() {
g.setFontAlign(0,0).setFont("6x8");
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
g.drawString(dateStr,x,y);
}
};
// Clear the screen once, at startup
g.clear();
@ -38,7 +39,10 @@ g.clear();
draw();
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.setUI({mode:"clock", remove:function() {
// free any memory we allocated to allow fast loading
}});
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
}

View File

@ -4,6 +4,7 @@
"version":"0.01",
"description": "A detailed description of my clock",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1 @@
0.01: Initial release.

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

55
apps/accelsender/boot.js Normal file
View File

@ -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
}
})();

1
apps/accelsender/boot.min.js vendored Normal file
View File

@ -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))})();

View File

@ -0,0 +1,4 @@
{
"enabled": false,
"interval": 10000
}

View File

@ -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"
}
]
}

View File

@ -6,7 +6,7 @@
"version":"0.12",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",
"tags": "tool,activity,health",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [

View File

@ -1,2 +1,3 @@
0.01: New Clock!
0.02: Fix fastloading memory leak and clockinfo overwritten by hands
0.03: Use new clockinfo lib with function to render images wirh borders

View File

@ -102,6 +102,7 @@
// Show launcher when middle button pressed
Bangle.setUI({
mode: "clock",
redraw : draw,
remove: function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
@ -116,9 +117,6 @@
Bangle.loadWidgets();
require("widget_utils").hide();
// used for clockinfo image rendering
let clockInfoG = Graphics.createArrayBuffer(28, 28, 2, {msb:true});
clockInfoG.transparent = 3;
// render clockinfos
let clockInfoDraw = function(itm, info, options) {
// itm: the item containing name/hasRange/etc
@ -139,18 +137,8 @@
fg = g.toColor("#f00");
}
if (info.img) {
//g.drawImage(info.img, left ? 2 : W - 27, top ? 18 : H - 41); // draw the image
// 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);
}
if (info.img)
require("clock_info").drawBorderedImage(info.img,imgx,imgy);
g.setFont("6x8:2").setFontAlign(left ? -1 : 1, -1);
g.setColor(bg).drawString(info.text, textx-2, texty). // draw the text background

View File

@ -1,12 +1,12 @@
{ "id": "analogquadclk",
"name": "Analog 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",
"icon": "icon.png",
"screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ],
"type": "clock",
"tags": "clock,clkinfo,analog",
"tags": "clock,clkinfo,analog,clockbg",
"supports" : ["BANGLEJS2"],
"dependencies" : { "clock_info":"module", "clockbg":"module" },
"storage": [

View File

@ -1,4 +1,7 @@
0.01: Release
0.02: Rename app
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

View File

@ -1,10 +1,16 @@
# Analog Clock
# Dark Analog Clock
## Features
* second hand
* second hand (only on unlocked screen)
* date
* battery percantage
* no widgets
* battery percentage (showing charge status with color)
* turned off or swipeable widgets (choose in settings)
![logo](andark_screen.png)
## 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,5 +1,30 @@
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};
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;
function zeiger(len,dia,tim){
@ -11,12 +36,8 @@ function zeiger(len,dia,tim){
}
function draw(){
const d=new Date();
function drawHands(d) {
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);
if(h>12){
@ -29,15 +50,8 @@ function draw(){
m=2*Math.PI/60*(m)-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);
const hz = zeiger(100,5,h);
const hz = zeiger(settings.shortHrHand?88:100,5,h);
g.fillPoly(hz,true);
//g.setColor(1,1,1);
const minz = zeiger(150,5,m);
@ -47,12 +61,53 @@ function draw(){
g.fillPoly(sekz,true);
}
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
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++){
const win=i*2*Math.PI/60;
let d=2;
@ -64,40 +119,22 @@ function drawScale(){
}
}
//draws the numbers on the screen
//// main running sequence ////
function drawlet(){
g.setFont("Vector",20);
for(let i = 0;i<12;i++){
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]);
}
}
//calcultes the Position of the numbers when app starts and saves them in an array
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();
// Show launcher when middle button pressed, and widgets that we're clock
Bangle.setUI("clock");
// Load widgets if needed, and make them show swipeable
if (settings.loadWidgets) {
Bangle.loadWidgets();
require("widget_utils").swipeOn();
} else if (global.WIDGETS) require("widget_utils").hide();
// Clear the screen once, at startup
g.setBgColor(0,0,0);
g.clear();
drawScale();
draw();
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=>{
if (secondInterval) clearInterval(secondInterval);
secondInterval = undefined;
@ -107,18 +144,9 @@ Bangle.on('lcdPower',on=>{
}
});
Bangle.on('lock',on=>{
unlock = !on;
if (secondInterval) clearInterval(secondInterval);
secondInterval = undefined;
if (!on) {
secondInterval = setInterval(draw, 1000);
unlock = true;
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
draw(); // draw immediately
}else{
secondInterval = setInterval(draw, 60000);
unlock = false;
draw();
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.on('charging',on=>{draw();});

View File

@ -1,15 +1,18 @@
{ "id": "andark",
"name": "Analog Dark",
"shortName":"AnDark",
"version":"0.04",
"version":"0.06",
"description": "analog clock face without disturbing widgets",
"icon": "andark_icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"andark_screen.png"}],
"readme": "README.md",
"storage": [
{"name":"andark.app.js","url":"app.js"},
{"name":"andark.settings.js","url":"settings.js"},
{"name":"andark.img","url":"app_icon.js","evaluate":true}
]
],
"data": [{"name":"andark.json"}]
}

28
apps/andark/settings.js Normal file
View File

@ -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);
});

View File

@ -34,3 +34,4 @@
0.32: Added support for loyalty cards from gadgetbridge
0.33: Fix alarms created in Gadgetbridge not repeating
0.34: Implement API for activity tracks fetching (Recorder app logs).
0.35: Implement API to enable/disable acceleration data tracking.

View File

@ -1,3 +1,4 @@
/* global GB */
(function() {
function gbSend(message) {
Bluetooth.println("");
@ -292,6 +293,10 @@
// we receive all, just override what we have
if (Array.isArray(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];

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"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.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -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);

99
apps/android/test.json Normal file
View File

@ -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"}
]
}]
}

15
apps/antonclk/test.json Normal file
View File

@ -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"}
]
}]
}

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Add black- and whitelist for apps. Configure the timout time.

View File

@ -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.
Through the settings apps can be black-/whitelisted and the timeout length can be configured.
## TODO
- Add settings page
- set how many minutes the timeout should count down.
- whitelist/blacklist for apps.
- per app specific timeout lengths?
## Requests

View File

@ -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 resetTimeoutAutoreset = (force)=>{
const resetTimeoutAutoreset = (force)=>{
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
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(()=>{
if (Bangle.CLOCK!=1) Bangle.showClock();
}, 10*60*1000);
}, settings.timeout*60*1000);
}
},200);
};

View File

@ -1,6 +1,6 @@
{ "id": "autoreset",
"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.",
"icon": "app.png",
"type": "bootloader",
@ -8,6 +8,10 @@
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"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"}
]
}

115
apps/autoreset/settings.js Normal file
View File

@ -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();
})

View File

@ -1,3 +1,6 @@
0.01: New App!
0.02: Don't fire if the app uses swipes already.
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.

View File

@ -47,9 +47,9 @@
function enabledForApp(app) {
if (!settings) return true;
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) {
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 {
return settings.mode === 2 ? true : false;
}

View File

@ -1,7 +1,7 @@
{ "id": "backswipe",
"name": "Back Swipe",
"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",
"icon": "app.png",
"tags": "back,gesture,swipe",

View File

@ -26,7 +26,8 @@
return appInfo && {
'name': appInfo.name,
'sortorder': appInfo.sortorder,
'src': appInfo.src
'src': appInfo.src,
'files': appInfo.files
};
}).filter(app => app && !!app.src);
apps.sort((a, b) => {

3
apps/blecsc/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: Initial version
0.02: Minor code improvements
0.03: Moved from cycling app, fixed connection issues and cadence

32
apps/blecsc/README.md Normal file
View File

@ -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

216
apps/blecsc/blecsc.js Normal file
View File

@ -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();
*/

74
apps/blecsc/clkinfo.js Normal file
View File

@ -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;
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

22
apps/blecsc/metadata.json Normal file
View File

@ -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"}
]
}

28
apps/blecsc/recorder.js Normal file
View File

@ -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)
};
}
})

85
apps/blecsc/settings.js Normal file
View File

@ -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();
})

View File

@ -69,3 +69,4 @@
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.60: Minor code improvements
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')

View File

@ -19,7 +19,7 @@ if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't chang
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 += ` { 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 += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
@ -122,6 +122,7 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
return a==b ? 0 : (a>b ? 1 : -1);
});
// precalculate file size
bootPost += "}";
let fileSize = boot.length + bootPost.length;
bootFiles.forEach(bootFile=>{
// match the size of data we're adding below in bootFiles.forEach

View File

@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.60",
"version": "0.61",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",

View File

@ -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 Layout = require("Layout");

View File

@ -4,3 +4,4 @@
0.04: Added notification
0.05: Fixed boot
0.06: Allow tap to silence notification/buzzing
0.07: Fix notification-tap silencing and notification length

View File

@ -24,8 +24,8 @@
lim = sum / cnt;
require('Storage').writeJSON('chargent.json', {limit: lim});
}
const onHide = () => { id = clearInterval(id) };
require('notify').show({id: 'chargent', title: 'Fully charged', onHide });
const onHide = () => { if(id) id = clearInterval(id) };
require('notify').show({id: 'chargent', title: 'Charged', onHide });
// TODO ? customizable
Bangle.buzz(500);
setTimeout(() => Bangle.buzz(500), 1000);

View File

@ -1,6 +1,6 @@
{ "id": "chargent",
"name": "Charge Gently",
"version": "0.06",
"version": "0.07",
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
"icon": "icon.png",
"type": "bootloader",

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: added settings options to change date format
0.03: Remove un-needed font requirement, now outputs transparent image
0.04: Fix image after 0.03 regression

View File

@ -20,7 +20,7 @@
get : () => {
let d = new Date();
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
g.transparent = 1;
g.transparent = 0;
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
return {

View File

@ -1,6 +1,6 @@
{ "id": "clkinfocal",
"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",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -91,7 +91,7 @@
};
var info = {
name: "Gps",
name: "GPS",
items: [
{
name: "gridref",

View File

@ -0,0 +1 @@
0.01: New Clock Info!

View File

@ -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!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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"}
]
}

View File

@ -10,3 +10,4 @@
0.09: Save clkinfo settings on kill and remove
0.10: Fix focus bug when changing focus between two clock infos
0.11: Prepend swipe listener if possible
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle

View File

@ -371,6 +371,46 @@ exports.addInteractive = function(menu, 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)
/*
g.clear();

View File

@ -1,11 +1,11 @@
{ "id": "clock_info",
"name": "Clock Info Module",
"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)",
"icon": "app.png",
"type": "module",
"tags": "clkinfo",
"tags": "clkinfo,clockinfo",
"supports" : ["BANGLEJS2"],
"provides_modules" : ["clock_info"],
"readme": "README.md",

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Moved settings into 'Settings->Apps'
0.03: Add 'Squares' option for random squares background

View File

@ -9,8 +9,12 @@ By default the app provides just a red/green/blue background but it can easily b
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 `Backgrounds` app on the Bangle itself, and choose between solid color, random colors, or any uploaded images.
* 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` - 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
@ -33,8 +37,10 @@ ensure that the clock background library is automatically loaded.
## 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)
* 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -2,27 +2,29 @@
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<style>
.flag {
.thumbnail {
width : 100px;
cursor: pointer;
}
#preview {
width : 176px;
height: 176px;
width : 178px; /* include the border */
height: 178px;
border: 1px solid black;
}
</style>
</head>
<body>
<p>Upload an image:</p>
<div id="flaglist"></div>
<p>Upload an image: <input class="form-input" id="customfile" type="file"></p>
<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>
<div style="float:right">Preview:<br/><canvas width="176" height="176" id="preview"></canvas></div>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" id="box_zoom">
<i class="form-icon"></i> Zoom
</label>
<select class="form-select" id="box_zoom" style="width:inherit">
<option value="0">No Zoom</option>
<option value="1">10% Zoom</option>
<option value="1.5">15% Zoom</option>
<option value="2">20% Zoom</option>
</select><br/>
<label class="form-switch">
<input type="checkbox" id="box_mirror">
<i class="form-icon"></i> Mirror
@ -55,6 +57,8 @@
{"path":"img/ai_robot.jpeg","dither":true},
{"path":"img/ai_eye.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-austria-480.png","dither":false},
{"path":"img/icons8-belgium-480.png","dither":false},
@ -85,22 +89,21 @@
var selectedImage;
var bgImageData;
document.getElementById("flaglist").innerHTML =
IMAGES.map(f => `<img class="flag" src="${f.path}" data-file="${f.path}"/>`).join("\n");
var elements = document.querySelectorAll(".flag");
document.getElementById("thumbnaillist").innerHTML = `<img class="thumbnail" id="customimage" src="" face="custom" style="display:none"/>\n`+
IMAGES.map(f => `<img class="thumbnail" src="${f.path}" data-file="${f.path}"/>`).join("\n");
var elements = document.querySelectorAll(".thumbnail");
for (var i=0;i<elements.length;i++)
elements[i].addEventListener("click", function(e) {
selectedImage = e.target;
drawPreview();
document.getElementById("upload").classList.remove("disabled")
});
function drawPreview() {
if (!selectedImage) return;
var imgPath = selectedImage.getAttribute("data-file");
var img = IMAGES.find(img => img.path == imgPath);
var zoom = document.getElementById("box_zoom").checked;
var img = IMAGES.find(img => img.path == imgPath) || {"dither":true}; // No IMAGES entry for custom images
var zoom = document.getElementById("box_zoom").value;
var dither = document.getElementById("box_dither").value;
if (dither=="" && img.dither) dither="bayer2";
if (dither=="no" || dither=="") dither=undefined;
@ -115,8 +118,13 @@
let imgW = selectedImage.naturalWidth;
let imgH = selectedImage.naturalHeight;
let border = 0;
if (imgW > 400) border = 20;
if (zoom) border = (border*5) >> 1;
let imgMin = Math.min(imgW,imgH);
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
if (mirror) {
ctx.translate(canvas.width, 0);
@ -135,6 +143,7 @@
brightness:64*brightness
};
bgImageData = imageconverter.canvastoString(canvas, options);
document.getElementById("upload").classList.remove("disabled")
}
// 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() {
Util.readStorageJSON("clockbg.json", function(data) {

View File

@ -4,10 +4,21 @@ let settings = Object.assign({
},require("Storage").readJSON("clockbg.json")||{});
if (settings.style=="image")
settings.img = require("Storage").read(settings.fn);
if (settings.style=="randomcolor") {
else if (settings.style=="randomcolor") {
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];
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}
@ -16,7 +27,7 @@ if (settings.style=="randomcolor") {
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 (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") {
g.setBgColor(settings.color).clearRect(rect);
} else {
@ -24,4 +35,3 @@ exports.fillRect = function(rect,y,x2,y2) {
g.setBgColor(g.theme.bg).clearRect(rect);
}
};

View File

@ -1,10 +1,10 @@
{ "id": "clockbg",
"name": "Clock Backgrounds",
"shortName":"Backgrounds",
"version": "0.02",
"description": "Library that allows clocks to include a custom background, from a library or uploaded.",
"version": "0.03",
"description": "Library that allows clocks to include a custom background (generated on demand or uploaded).",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
"type": "module",
"readme": "README.md",
"provides_modules" : ["clockbg"],

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -9,7 +9,7 @@ function saveSettings() {
delete settings.fn;
if (settings.style!="color")
delete settings.color;
if (settings.style!="randomcolor")
if (settings.style!="randomcolor" && settings.style!="squares")
delete settings.colors;
require("Storage").writeJSON("clockbg.json", settings);
}
@ -18,10 +18,11 @@ function getColorsImage(cols) {
var bpp = 1;
if (cols.length>4) bpp=4;
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);
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);
});
return "\0"+b.asImage("string");
@ -49,6 +50,7 @@ function showModeMenu() {
var cols = [
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
["#F00","#0F0","#00F"],
// Please add some more!
];
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
cols.forEach(col => {
@ -78,6 +80,31 @@ function 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);
}
});
}

View File

@ -1,2 +1,3 @@
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
0.02: Minor code improvements
0.03: Minor code improvements

View File

@ -35,22 +35,22 @@ var v_model=process.env.BOARD;
var v_color_text='#FB0E01';
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
//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',
'cyan','olive','DarkCyan','DarkGrey',
'Navy','Red','Magenta','GreenYellow',
'Blush RGB888','pure red','Orange','Grey green',
'D. grey','Almond','Amber','Bone',
'Canary','Aero blue','Camel','Baby pink',
'Y.Corn','Cultured','Eigengrau','Citrine');
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,
'Y.Corn','Cultured','Eigengrau','Citrine'];
var a_colors= [0xFFFF,0xFD20,0x03E0,0xFFE0,
0x7800,0x001F,0x07E0,0x780F,
0x07FF,0x7BE0,0x03EF,0x7BEF,
0x000F,0xF800,0xF81F,0xAFE5,
'#DE5D83','#FB0E01','#E56E06','#7E795C',
'#404040','#EFDECD','#FFBF00','#E3DAC9',
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
'#FBEC5D','#F5F5F5','#16161D','#E4D00A');
'#FBEC5D','#F5F5F5','#16161D','#E4D00A'];
var v_color_lines=0xFFFF; //White hex format

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