Merge branch 'espruino:master' into master

pull/1340/head
Peer David 2022-01-24 17:45:27 +01:00 committed by GitHub
commit c6c1b118ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 905 additions and 46 deletions

View File

@ -1,4 +1,5 @@
(() => {
BANGLEJS2 = process.env.HWVERSION==2;
Bangle.setLCDTimeout(0);
let intervalID;
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
@ -6,7 +7,9 @@
// density, elasticity of bounces, "drag coefficient"
const rho = 100, e = 0.3, C = 0.01;
// screen width & height in pixels
const sW = 240, sH = 160;
const sW = g.getWidth();
const sH = g.getHeight()*2/3;
const bgColour ="#f00"; // only for Bangle.js 2
// gravity constant (lowercase was already taken)
const G = 9.80665;
@ -17,14 +20,16 @@
// The play area is 240x160, sizes are the ball radius, so we can use common
// denominators of 120x80 to get square rooms
// Reverse the order to show the easiest on top of the menu
const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(),
// even size 1 actually works, but larger mazes take forever to generate
minSize = 4, defaultSize = 10;
const sizeNames = {
1: "Insane", 2: "Gigantic", 4: "Enormous", 5: "Huge", 8: "Large",
10: "Medium", 16: "Small", 20: "Tiny", 40: "Trivial",
};
// even size 1 actually works, but larger mazes take forever to generate
if (!BANGLEJS2) {
const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(), minSize = 4, defaultSize = 10;
} else {
const sizes = [1, 2, 4, 5, 8, 10, 16, 20 ].reverse(), minSize = 4, defaultSize = 10;
}
/**
* Draw something to all screen buffers
* @param draw {function} Callback which performs the drawing
@ -45,17 +50,17 @@
// use unbuffered graphics for UI stuff
function showMessage(message, title) {
Bangle.setLCDMode();
if (!BANGLEJS2) Bangle.setLCDMode();
return E.showMessage(message, title);
}
function showPrompt(prompt, options) {
Bangle.setLCDMode();
if (!BANGLEJS2) Bangle.setLCDMode();
return E.showPrompt(prompt, options);
}
function showMenu(menu) {
Bangle.setLCDMode();
if (!BANGLEJS2) Bangle.setLCDMode();
return E.showMenu(menu);
}
@ -105,7 +110,7 @@
generateMaze(); // this shows unbuffered progress messages
if (settings.cheat && r>1) findRoute(); // not enough memory for r==1 :-(
Bangle.setLCDMode("doublebuffered");
if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered");
clearAll();
drawAll(drawMaze);
intervalID = setInterval(tick, 100);
@ -307,6 +312,7 @@
const range = {top: 0, left: 0, bottom: rows, right: cols};
const w = sW/cols, h = sH/rows;
g.clear();
if (BANGLEJS2) g.setBgColor(bgColour);
g.setColor(0.76, 0.60, 0.42);
for(let row = range.top; row<=range.bottom; row++) {
for(let col = range.left; col<=range.right; col++) {

View File

@ -1,2 +1,3 @@
0.01: Initial version of Balltastic released! Happy!
0.02: Set LCD timeout for Espruino 2v10 compatibility
0.03: Now also works on Bangle.js 2

View File

@ -1,11 +1,12 @@
BANGLEJS2 = process.env.HWVERSION==2;
Bangle.setLCDBrightness(1);
Bangle.setLCDMode("doublebuffered");
if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered");
Bangle.setLCDTimeout(0);
let points = 0;
let level = 1;
let levelSpeedStart = 0.8;
let nextLevelPoints = 20;
let nextLevelPoints = 10;
let levelSpeedFactor = 0.2;
let counterWidth = 10;
let gWidth = g.getWidth() - counterWidth;
@ -81,12 +82,23 @@ function drawLevelText() {
g.setColor("#26b6c7");
g.setFontAlign(0, 0);
g.setFont("4x6", 5);
g.drawString("Level " + level, 120, 80);
g.drawString("Level " + level, g.getWidth()/2, g.getHeight()/2);
}
function drawPointsText() {
g.setColor("#26b6c7");
g.setFontAlign(0, 0);
g.setFont("4x6", 2);
g.drawString("Points " + points, g.getWidth()/2, g.getHeight()-20);
}
function draw() {
//bg
if (!BANGLEJS2) {
g.setColor("#71c6cf");
} else {
g.setColor("#002000");
}
g.fillRect(0, 0, g.getWidth(), g.getHeight());
//counter
@ -94,6 +106,7 @@ function draw() {
//draw level
drawLevelText();
drawPointsText();
//dot
g.setColor("#ff0000");
@ -152,7 +165,7 @@ function count() {
if (counter <= 0) {
running = false;
clearInterval(drawInterval);
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50);
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Game over!");},50);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,12 +1,13 @@
{
"id": "balltastic",
"name": "Balltastic",
"version": "0.02",
"version": "0.03",
"description": "Simple but fun ball eats dots game.",
"icon": "app.png",
"screenshots": [{"url":"bangle2-balltastic-screenshot.png"}],
"type": "app",
"tags": "game,fun",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"balltastic.app.js","url":"app.js"},
{"name":"balltastic.img","url":"app-icon.js","evaluate":true}

View File

@ -11,3 +11,4 @@
Support to choose between humidity and wind speed for weather circle progress
Support to show time and progress until next sunrise or sunset
Load daily steps from Bangle health if available
0.07: Allow configuration of minimal heart rate confidence

View File

@ -29,6 +29,7 @@ function loadSettings() {
settings = storage.readJSON("circlesclock.json", 1) || {
'minHR': 40,
'maxHR': 200,
'confidence': 0,
'stepGoal': 10000,
'stepDistanceGoal': 8000,
'stepLength': 0.8,
@ -599,10 +600,12 @@ Bangle.on('lock', function(isLocked) {
Bangle.on('HRM', function(hrm) {
if (isCircleEnabled("hr")) {
if (hrm.confidence >= (settings.confidence || 0)) {
hrtValue = hrm.bpm;
if (Bangle.isLCDOn())
drawHeartRate();
}
}
});

View File

@ -1,7 +1,7 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
"version":"0.06",
"version":"0.07",
"description": "A clock with circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}],

View File

@ -35,6 +35,16 @@
},
onchange: x => save('maxHR', x),
},
'hr confidence': {
value: "confidence" in settings ? settings.confidence : 0,
min: 0,
max : 100,
step: 10,
format: x => {
return x;
},
onchange: x => save('confidence', x),
},
'step goal': {
value: "stepGoal" in settings ? settings.stepGoal : 10000,
min: 2000,

View File

@ -24,12 +24,7 @@ if (!settings) resetSettings();
function showMenu() {
const datemenu = {
'': {
'title': 'Set Date',
'predraw': function() {
datemenu.Day.value = settings.day;
datemenu.Month.value = settings.month;
datemenu.Year.value = settings.year;
}
'title': 'Set Date'
},
'Day': {
value: settings.day,
@ -65,4 +60,3 @@ function showMenu() {
}
showMenu();

View File

@ -6,7 +6,7 @@
"description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files",
"icon": "icons8-filing-cabinet-48.png",
"tags": "tools",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"fileman.app.js","url":"fileman.app.js"},

View File

@ -6,3 +6,4 @@
0.06: Move the next strike time to the first row of display
0.07: Change the boot function to avoid reloading the entire watch
0.08: Default to no strikes. Fix file-not-found issue during the first boot. Add data file.
0.09: Add some customisation options

View File

@ -1,5 +1,6 @@
const storage = require('Storage');
var settings = storage.readJSON('hourstrike.json', 1);
const chimes = ["Buzz", "Beep"];
function updateSettings() {
storage.write('hourstrike.json', settings);
@ -26,6 +27,12 @@ function showMainMenu() {
mainmenu.Strength = {
value: settings.vlevel*10, min: 1, max: 10, format: v=>v/10,
onchange: v=> {settings.vlevel = v/10; updateSettings();}};
mainmenu.Strikecount = {
value: settings.scount, min: 1, max: 2, format: v=>v,
onchange: v=> {settings.scount = v; updateSettings();}};
mainmenu.Chimetype = {
value: settings.buzzOrBeep, min: 0, max: 1, format: v => chimes[v],
onchange: v=> {settings.buzzOrBeep = v; updateSettings();}};
mainmenu['< Back'] = ()=>load();
return E.showMenu(mainmenu);
}

View File

@ -30,9 +30,23 @@
}
function strike_func () {
var setting = require('Storage').readJSON('hourstrike.json',1)||[];
if (0 == setting.buzzOrBeep) {
if (2 == setting.scount) {
Bangle.buzz(200, setting.vlevel||0.5)
.then(() => new Promise(resolve => setTimeout(resolve,200)))
.then(() => Bangle.buzz(200, setting.vlevel||0.5));
} else {
Bangle.buzz(200, setting.vlevel||0.5);
}
} else {
if (2 == setting.scount) {
Bangle.beep(200)
.then(() => new Promise(resolve => setTimeout(resolve,100)))
.then(() => Bangle.beep(300));
} else {
Bangle.beep(200);
}
}
setup();
}
setup();

View File

@ -1 +1 @@
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"next_hour":-1,"next_minute":-1}
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"scount":2,"buzzOrBeep":0,"next_hour":-1,"next_minute":-1}

View File

@ -2,7 +2,7 @@
"id": "hourstrike",
"name": "Hour Strike",
"shortName": "Hour Strike",
"version": "0.08",
"version": "0.09",
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
"icon": "app-icon.png",
"tags": "tool,alarm",

View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Add the option to enable touching the widget only on clock and settings.

102
apps/lightswitch/README.md Normal file
View File

@ -0,0 +1,102 @@
# Light Switch Widget
Whis this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness.
In addition it shows the lock status with the option to personalize the lock icon with a tiny image.
---
### Control
---
* __On / off__
Single touch the widget to en-/disable the backlight.
* __Change brightness__ _(can be disabled)_
First touch the widget, then quickly touch the screen again and drag up/down until you reach your wished brigthness.
* __Double tap to flash backlight__ _(can be disabled)_
By defaut you can double tap on the right side of your bangle to flash the backlight for a short duration.
(While the backlight is active your bangle will be unlocked.)
* __Double tap to unlock__ _(disabled by default)_
If a side is defined in the app settings, your bangle will be unlocked if you double tap on that side.
---
### Settings
---
#### Widget - Change the apperance of the widget:
* __Bulb col__
_red_ / _yellow_ / _green_ / __cyan__ / _blue_ / _magenta_
Define the color used for the lightbulbs inner circle.
The selected color will be dimmed depending on the actual brightness value.
* __Image__
__default__ / _random_ / _..._
Set your favourite lock icon image. (If no image file is found _no image_ will be displayed.)
* _random_ -> Select a random image on each time the widget is drawn.
#### Control - Change when and how to use the widget:
* __Touch__
_on def clk_ / _on all clk_ / _clk+setting_ / _clk+launch_ / _except apps_ / __always on__
Select when touching the widget is active to en-/disable the backlight.
* _on def clk_ -> only on your selected main clock face
* _on all clk_ -> on all apps of the type _clock_
* _clk+setting_ -> on all apps of the type _clock_ and in the settings
* _clk+launch_ -> on all apps of the types _clock_ and _launch_
* _except apps_ -> on all apps of the types _clock_ and _launch_ and in the settings
* _always on_ -> always enabled when the widget is displayed
* __Drag Delay__
_off_ / _50ms_ / _100ms_ / _..._ / __500ms__ / _..._ / _1000ms_
Change the maximum delay between first touch and re-touch/drag to change the brightness or disable changing the brightness completely.
* __Min Value__
_1%_ / _2%_ / _..._ / __10%__ / _..._ / _100%_
Set the minimal level of brightness you can change to.
#### Unlock - Set double tap side to unlock:
* __TapSide__
__off__ / _left_ / _right_ / _top_ / _bottom_ / _front_ / _back_
#### Flash - Change if and how to flash the backlight:
* __TapSide__
_off_ / _left_ / __right__ / _top_ / _bottom_ / _front_ / _back_
Set double tap side to flash the backlight or disable completely.
* __Tap__
_on locked_ / _on unlocked_ / __always on__
Select when a double tap is recognised.
* __Timeout__
_0.5s_ / _1s_ / _..._ / __2s__ / _..._ / _10s_
Change how long the backlight will be activated on a flash.
* __Min Value__
_1%_ / _2%_ / _..._ / __20%__ / _..._ / _100%_
Set the minimal level of brightness for the backlight on a flash.
---
### Images
---
| Lightbulb | Default lock icon |
|:-----------------------------:|:-----------------------:|
| ![](images/lightbulb.png) | ![](images/default.png) |
| ( _full_ / _dimmed_ / _off_ ) | ( _on_ / _off_ ) |
Examples in default light and dark theme.
| Lock | Heart | Invader | JS | Smiley | Skull | Storm |
|:----:|:-----:|:-------:|:--:|:------:|:-----:|:-----:|
| ![](images/image_lock.png) | ![](images/image_heart.png) | ![](images/image_invader.png) | ![](images/image_js.png) | ![](images/image_smiley.png) | ![](images/image_skull.png) | ![](images/image_storm.png) |
This images are stored in a seperate file _(lightswitch.images.json)_.
---
### Worth Mentioning
---
#### To do list
* Catch the touch and draw input related to this widget to prevent actions in the active app.
_(For now I have no idea how to achieve this, help is appreciated)_
* Manage images for the lock icon through a _Customize and Upload App_ page.
#### Requests, Bugs and Feedback
Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) or send me a [mail](mailto:banglejs@storm64.de).
#### Thanks
Huge thanks to Gordon Williams and all the motivated developers.
#### Creator
Storm64 ([Mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64))
#### License
[MIT License](LICENSE)

17
apps/lightswitch/boot.js Normal file
View File

@ -0,0 +1,17 @@
// load settings
var settings = Object.assign({
value: 1,
isOn: true
}, require("Storage").readJSON("lightswitch.json", true) || {});
// set brightness
Bangle.setLCDBrightness(settings.isOn ? settings.value : 0);
// remove tap listener to prevent uncertainties
Bangle.removeListener("tap", require("lightswitch.js").tapListener);
// add tap listener to unlock and/or flash backlight
if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
// clear variable
settings = undefined;

View File

@ -0,0 +1,37 @@
{
"lock": {
"str": "BQcBAAEYxiA=",
"x": 9,
"y": 15,
},
"heart": {
"str": "CQjBAQD4//+chAAACA4Pj+8=",
"x": 7,
"y": 14,
},
"invader": {
"str": "DQqDASQASQASSEAAECSQEAEASQEkkkAQEgkgkAEkkkkkAgkkkggEEAAEEAAEgkAASQAAASQ=",
"x": 5,
"y": 13,
},
"js": {
"str": "CAqBAd//2NfZ3tHfX78=",
"x": 7,
"y": 13,
},
"skull": {
"str": "CQqBAcHAZTKcH/+OfMGfAA==",
"x": 7,
"y": 13,
},
"smiley": {
"str": "CwqDASQAAASQNtsAQNttsANgMBsBsBgNgNtttsBsNsNgBsANgCBttgCSAAACQA==",
"x": 6,
"y": 13,
},
"storm": {
"str": "CQmDASAAACBttgBgABgBttgCMAACQNsASRgASSBgCSSACSA=",
"x": 7,
"y": 13,
}
}

View File

@ -0,0 +1 @@
# Light Switch Images

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

124
apps/lightswitch/lib.js Normal file
View File

@ -0,0 +1,124 @@
// from boot accassible functions
exports = {
// listener function //
// tap listener to flash backlight
tapListener: function(data) {
// check for double tap and direction
if (data.double) {
// setup shortcut to this widget or load from storage
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({
unlockSide: "",
tapSide: "right",
tapOn: "always",
}, require("Storage").readJSON("lightswitch.json", true) || {});
// cache lock status
var locked = Bangle.isLocked();
// check to unlock
if (locked && data.dir === w.unlockSide) Bangle.setLocked();
// check to flash
if (data.dir === w.tapSide && (w.tapOn === "always" || locked === (w.tapOn === "locked"))) require("lightswitch.js").flash();
// clear variables
w = undefined;
locked = undefined;
}
},
// external function //
// function to flash backlight
flash: function(tOut) {
// setup shortcut to this widget or load from storage
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({
tOut: 3000,
minFlash: 0.2,
value: 1,
isOn: true
}, require("Storage").readJSON("lightswitch.json", true) || {});
// chack if locked, backlight off or actual value lower then minimal flash value
if (Bangle.isLocked() || !w.isOn || w.value < w.minFlash) {
// set inner bulb and brightness
var setBrightness = function(w, value) {
if (w.drawInnerBulb) w.drawInnerBulb(value);
Bangle.setLCDBrightness(value);
};
// override timeout if defined
if (!tOut) tOut = w.tOut;
// check lock state
if (Bangle.isLocked()) {
// cache options
var options = Bangle.getOptions();
// set shortened lock and backlight timeout
Bangle.setOptions({
lockTimeout: tOut,
backlightTimeout: tOut
});
// unlock
Bangle.setLocked(false);
// set timeout to reset options
setTimeout(Bangle.setOptions, tOut + 100, options);
// clear variable
options = undefined;
} else {
// set timeout to reset backlight
setTimeout((w, funct) => {
if (!Bangle.isLocked()) funct(w, w.isOn ? w.value : 0);
}, tOut, w, setBrightness);
}
// enable backlight
setTimeout((w, funct) => {
funct(w, w.value < w.minFlash ? w.minFlash : w.value);
}, 10, w, setBrightness);
// clear variable
setBrightness = undefined;
}
// clear variable
w = undefined;
},
// external access to internal function //
// refference to widget function or set backlight and write to storage if not skipped
changeValue: function(value, skipWrite) {
// check if widgets are loaded
if (global.WIDGETS) {
// execute inside widget
WIDGETS.lightswitch.changeValue(value, skipWrite);
} else {
// load settings from storage
var filename = "lightswitch.json";
var storage = require("Storage");
var settings = Object.assign({
value: 1,
isOn: true
}, storage.readJSON(filename, true) || {});
// check value
if (value) {
// set new value
settings.value = value;
} else {
// switch backlight status
settings.isOn = !settings.isOn;
}
// set brightness
Bangle.setLCDBrightness(settings.isOn ? settings.value : 0);
// write changes to storage if not skipped
if (!skipWrite) storage.writeJSON(filename, settings);
// clear variables
filename = undefined;
storage = undefined;
settings = undefined;
}
}
};

View File

@ -0,0 +1,28 @@
{
"id": "lightswitch",
"name": "Light Switch Widget",
"shortName": "Light Switch",
"version": "0.02",
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
"icon": "images/app.png",
"screenshots": [
{"url": "images/screenshot_1.png"},
{"url": "images/screenshot_2.png"},
{"url": "images/screenshot_3.png"},
{"url": "images/screenshot_4.png"}
],
"type": "widget",
"tags": "tool,widget,brightness,lock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name": "lightswitch.boot.js", "url": "boot.js"},
{"name": "lightswitch.js", "url": "lib.js"},
{"name": "lightswitch.settings.js", "url": "settings.js"},
{"name": "lightswitch.wid.js", "url": "widget.js"}
],
"data": [
{"name": "lightswitch.json"},
{"name": "lightswitch.images.json", "url": "images.json"}
]
}

View File

@ -0,0 +1,155 @@
(function(back) {
var filename = "lightswitch.json";
// set Storage and load settings
var storage = require("Storage");
var settings = Object.assign({
colors: "011",
image: "default",
touchOn: "clock,launch",
dragDelay: 500,
minValue: 0.1,
unlockSide: "",
tapSide: "right",
tapOn: "always",
tOut: 2000,
minFlash: 0.2
}, storage.readJSON(filename, true) || {});
var images = storage.readJSON(filename.replace(".", ".images."), true) || false;
// write change to storage and widget
function writeSetting(key, value, drawWidgets) {
// reread settings to only change key
settings = Object.assign(settings, storage.readJSON(filename, true) || {});
// change the value of key
settings[key] = value;
// write to storage
storage.writeJSON(filename, settings);
// check if widgets are loaded
if (global.WIDGETS) {
// setup shortcut to the widget
var w = WIDGETS.lightswitch;
// assign changes to widget
w = Object.assign(w, settings);
// redraw widgets if neccessary
if (drawWidgets) Bangle.drawWidgets();
}
}
// generate entry for circulating values
function getEntry(key) {
var entry = entries[key];
// check for existing titles to decide value type
if (entry.value) {
// return entry for string value
return {
value: entry.value.indexOf(settings[key]),
format: v => entry.title ? entry.title[v] : entry.value[v],
onchange: function(v) {
this.value = v = v >= entry.value.length ? 0 : v < 0 ? entry.value.length - 1 : v;
writeSetting(key, entry.value[v], entry.drawWidgets);
if (entry.exec) entry.exec(entry.value[v]);
}
};
} else {
// return entry for numerical value
return {
value: settings[key] * entry.factor,
step: entry.step,
format: v => v > 0 ? v + entry.unit : "off",
onchange: function(v) {
this.value = v = v > entry.max ? entry.min : v < entry.min ? entry.max : v;
writeSetting(key, v / entry.factor, entry.drawWidgets);
},
};
}
}
// define menu entries with circulating values
var entries = {
colors: {
title: ["red", "yellow", "green", "cyan", "blue", "magenta"],
value: ["100", "110", "010", "011", "001", "101"],
drawWidgets: true
},
image: {
title: images ? undefined : ["no found"],
value: images ? ["default", "random"].concat(Object.keys(images)) : ["default"],
exec: function(value) {
// draw selected image in upper right corner
var x = 152,
y = 26,
i = images ? images[value] : false;
g.reset();
if (!i) g.setColor(g.theme.bg);
g.drawImage(atob("Dw+BADAYYDDAY//v////////////////////////3/8A"), x + 4, y);
if (i) g.drawImage(atob(i.str), x + i.x, y - 9 + i.y);
i = undefined;
}
},
touchOn: {
title: ["on def clk", "on all clk", "clk+launch", "clk+setting", "except apps", "always on"],
value: ["", "clock", "clock,setting.app.js", "clock,launch", "clock,setting.app.js,launch", "always"],
drawWidgets: true
},
dragDelay: {
factor: 1,
unit: "ms",
min: 0,
max: 1000,
step: 50
},
minValue: {
factor: 100,
unit: "%",
min: 1,
max: 100,
step: 1
},
unlockSide: {
title: ["off", "left", "right", "top", "bottom", "front", "back"],
value: ["", "left", "right", "top", "bottom", "front", "back"]
},
tapOn: {
title: ["on locked", "on unlocked", "always on"],
value: ["locked", "unlocked", "always"]
},
tOut: {
factor: 0.001,
unit: "s",
min: 0.5,
max: 10,
step: 0.5
}
};
// copy duplicated entries
entries.tapSide = entries.unlockSide;
entries.minFlash = entries.minValue;
// show main menu
function showMain() {
var mainMenu = E.showMenu({
"": {
title: "Light Switch"
},
"< Back": () => back(),
"-- Widget --------": 0,
"Bulb col": getEntry("colors"),
"Image": getEntry("image"),
"-- Control -------": 0,
"Touch": getEntry("touchOn"),
"Drag Delay": getEntry("dragDelay"),
"Min Value": getEntry("minValue"),
"-- Unlock --------": 0,
"TapSide": getEntry("unlockSide"),
"-- Flash ---------": 0,
"TapSide ": getEntry("tapSide"),
"Tap": getEntry("tapOn"),
"Timeout": getEntry("tOut"),
"Min Value ": getEntry("minFlash")
});
}
// draw main menu
showMain();
})

View File

@ -0,0 +1,72 @@
/*** Available settings for lightswitch ***
* colors: string // colors used for the bulb
// set with g.setColor(val*col[0], val*col[1], val*col[2])
"100" -> red
"110" -> yellow
"010" -> green
"011" -> cyan (default)
"001" -> blue
"101" -> magenta
* image: string //
"default" ->
"random" ->
* touchOn: string // select when widget touch is active
"" -> only on default clock
"clock" -> on all clocks
"clock,launch" -> on all clocks and lanchers (default)
"always" -> always
* dragDelay: int // drag listener reset time in ms
// time until a drag is needed to activate backlight changing mode
0 -> disabled
500 -> (default)
* minValue: float // minimal brightness level that can be set by dragging
0.05 to 1, 0.1 as default
* unlockSide: string // side of the watch to double tap on to flash backlight
0/false/undefined -> backlight flash disabled
right/left/up/down/front/back -> side to tap on (default: right)
* tapSide: string // side of the watch to double tap on to flash backlight
0/false/undefined -> backlight flash disabled
right/left/up/down/front/back -> side to tap on (default: right)
* tapOn: string // select when tap to flash backlight is active
"locked" -> only when locked
"unlocked" -> only when unlocked (default)
"always" -> always
* tOut: int // backlight flash timeout in ms
3000 (default)
* minFlash: float // minimal brightness level when
0.05 to 1, 0.2 as default
*** Cached values ***
* value: float // active brightness value (0-1)
1 (default)
* isOn: bool // active backlight status
true (default)
*/
{
// settings
"colors": "011",
"image": "default",
"touchOn": "clock,launch",
"dragDelay": 500,
"minValue": 0.1,
"unlockSide": "",
"tapSide": "right",
"tapOn": "always",
"tOut": 2000,
"minFlash": 0.2,
// cached values
"value": 1,
"isOn": true
}

255
apps/lightswitch/widget.js Normal file
View File

@ -0,0 +1,255 @@
(function() {
// load settings
var settings = Object.assign({
colors: "011",
image: "default",
touchOn: "clock,launch",
dragDelay: 500,
minValue: 0.1,
unlockSide: "",
tapSide: "right",
tapOn: "always",
tOut: 3000,
value: 1,
isOn: true
}, require("Storage").readJSON("lightswitch.json", true) || {});
// write widget with loaded settings
WIDGETS.lightswitch = Object.assign(settings, {
// set area, sortorder, width and dragStatus
area: "tr",
sortorder: 10,
width: 23,
dragStatus: "off",
// internal function //
// write settings to storage
writeSettings: function(changes) {
// define variables
var filename = "lightswitch.json";
var storage = require("Storage");
// write changes into json file
storage.writeJSON(filename, Object.assign(
storage.readJSON(filename, true) || {}, changes
));
// clear variables
filename = undefined;
storage = undefined;
},
// internal function //
// draw inner bulb circle
drawInnerBulb: function(value) {
// check if active or value is set
if (value || this.isOn) {
// use set value or load from widget
value = value || this.value;
// calculate color
g.setColor(
value * this.colors[0],
value * this.colors[1],
value * this.colors[2]
);
} else {
// backlight off
g.setColor(0);
}
// draw circle
g.drawImage(atob("CwuBAB8H8f9/////////f8fwfAA="), this.x + 6, this.y + 6);
},
// internal function //
// draw widget icon
drawIcon: function(locked) {
// define icons
var icons = {
bulb: "DxSBAAAAD4BgwYDCAIgAkAEgAkAEgAiAIYDBgwH8A/gH8A/gH8AfABwA",
shine: "FxeBAAgQIAgggBBBABAECAAALAABhAAEAAAAAAAAAAAAAAAHAABwAAAAAAAAAAAAAAAQABDAABoAAAgQBABABACACAIACAA=",
lock: "DxCBAAAAH8B/wMGBgwMGBgwf/H/8+Pnx8/fn78/fn/8f/A==",
image: "DxSBAA/gP+Dg4YDDAYYDDAYYDH/9////////////////////////+//g"
};
// read images
var images = require("Storage").readJSON("lightswitch.images.json", true) || false;
// select image if images are found
var image = (!images || image === "default") ? false :
(function(i) {
if (i === "random") {
i = Object.keys(images);
i = i[parseInt(Math.random() * i.length)];
}
return images[i];
})(this.image);
// clear widget area
g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 24);
// draw shine if backlight is active
if (this.isOn) g.drawImage(atob(icons.shine), this.x, this.y);
// draw icon depending on lock status and image
g.drawImage(atob(!locked ? icons.bulb : image ? icons.image : icons.lock), this.x + 4, this.y + 4);
// draw image on lock
if (locked && image) g.drawImage(atob(image.str), this.x + image.x, this.y + image.y);
// draw bulb color depending on backlight status
if (!locked) this.drawInnerBulb();
// clear variables
icons = undefined;
images = undefined;
image = undefined;
},
// internal function //
// change or switch backlight and icon and write to storage if not skipped
changeValue: function(value, skipWrite) {
// check value
if (value) {
// set new value
this.value = value;
// check backlight status
if (this.isOn) {
// redraw only inner bulb circle
this.drawInnerBulb();
} else {
// activate backlight
this.isOn = true;
// redraw complete widget icon
this.drawIcon(false);
}
} else {
// switch backlight status
this.isOn = !this.isOn;
// redraw widget icon
this.drawIcon(false);
}
// set brightness
Bangle.setLCDBrightness(this.isOn ? this.value : 0);
// write changes to storage if not skipped
if (!skipWrite) this.writeSettings({
isOn: this.isOn,
value: this.value
});
},
// listener function //
// drag listener for brightness change mode
dragListener: function(event) {
// setup shortcut to this widget
var w = WIDGETS.lightswitch;
// first drag recognised
if (event.b && typeof w.dragStatus === "number") {
// reset drag timeout
clearTimeout(w.dragStatus);
// change drag status to indicate ongoing drag action
w.dragStatus = "ongoing";
// feedback for brightness change mode
Bangle.buzz(50);
}
// read y position, pleasant usable area 20-170
var y = event.y;
y = y < 20 ? 0 : y > 170 ? 150 : y - 20;
// calculate brightness respecting minimal value in settings
var value = (1 - Math.round(y / 1.5) / 100) * (1 - w.minValue) + w.minValue;
// change brigthness value, skip write to storage while still touching
w.changeValue(value, event.b);
// on touch release remove drag listener and reset drag status to indicate stopped drag action
if (!event.b) {
Bangle.removeListener("drag", w.dragListener);
w.dragStatus = "off";
}
// clear variables
w = undefined;
y = undefined;
value = undefined;
},
// listener function //
// touch listener for light control
touchListener: function(button, cursor) {
// setup shortcut to this widget
var w = WIDGETS.lightswitch;
// skip all if drag action ongoing
if (w.dragStatus === "off") {
// check if inside widget area
if (!(!w || cursor.x < w.x || cursor.x > w.x + w.width ||
cursor.y < w.y || cursor.y > w.y + 23)) {
// first touch feedback
Bangle.buzz(25);
// check if drag is disabled
if (w.dragDelay) {
// add drag listener
Bangle.on("drag", w.dragListener);
// set drag timeout
w.dragStatus = setTimeout((w) => {
// remove drag listener
Bangle.removeListener("drag", w.dragListener);
// clear drag timeout
if (typeof w.dragStatus === "number") clearTimeout(w.dragStatus);
// reset drag status to indicate stopped drag action
w.dragStatus = "off";
}, w.dragDelay, w);
}
// switch backlight
w.changeValue();
}
}
// clear variable
w = undefined;
},
// main widget function //
// display and setup/reset function
draw: function(locked) {
// setup shortcut to this widget
var w = WIDGETS.lightswitch;
// set lcd brightness on unlocking
// all other cases are catched by the boot file
if (locked === false) Bangle.setLCDBrightness(w.isOn ? w.value : 0);
// read lock status
locked = Bangle.isLocked();
// remove listeners to prevent uncertainties
Bangle.removeListener("lock", w.draw);
Bangle.removeListener("touch", w.touchListener);
Bangle.removeListener("tap", require("lightswitch.js").tapListener);
// draw widget icon
w.drawIcon(locked);
// add lock listener
Bangle.on("lock", w.draw);
// add touch listener to control the light depending on settings
if (w.touchOn === "always" || !global.__FILE__ ||
w.touchOn.includes(__FILE__) ||
w.touchOn.includes(require("Storage").readJSON(__FILE__.replace("app.js", "info")).type))
Bangle.on("touch", w.touchListener);
// add tap listener to unlock and/or flash backlight
if (w.unlockSide || w.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
// clear variables
w = undefined;
}
});
// clear variable
settings = undefined;
})()

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Set pace format to mm:ss, time format to h:mm:ss,
added settings to opt out of GPS and HRM
0.03: Fixed distance calculation, tested against Garmin Etrex, Amazfit GTS 2

View File

@ -5,6 +5,7 @@ var fontHeading = "6x8:2";
var fontValue = B2 ? "6x15:2" : "6x8:3";
var headingCol = "#888";
var running = false;
var fixCount = 0;
var startTime;
var startSteps;
// This & previous GPS readings
@ -102,8 +103,6 @@ var layout = new Layout( {
clearState();
layout.render();
function onTimer() {
layout.clock.label = locale.time(new Date(),1);
if (!running) {
@ -125,21 +124,36 @@ function onTimer() {
stepHistory[0]=0;
}
function radians(a) {
return a*Math.PI/180;
}
// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km
// https://www.movable-type.co.uk/scripts/latlong.html
function calcDistance(a,b) {
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
var y = radians(b.lat-a.lat);
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
}
Bangle.on("GPS", function(fix) {
layout.gps.bgCol = fix.fix ? "#0f0" : "#f00";
lastGPS = thisGPS;
if (!fix.fix) { return; } // only process actual fixes
if (fixCount++ == 0) {
Bangle.buzz(); // first fix, does not need to respect quiet mode
lastGPS = fix; // initialise on first fix
}
thisGPS = fix;
if (running && fix.fix && lastGPS.fix) {
// work out distance - moving from a to b
var a = Bangle.project(lastGPS);
var b = Bangle.project(thisGPS);
var dx = a.x-b.x, dy = a.y-b.y;
var d = Math.sqrt(dx*dx+dy*dy); // this should be the distance in meters
if (running) {
var d = calcDistance(lastGPS, thisGPS);
distance += d;
layout.dist.label=locale.distance(distance);
var duration = Date.now() - startTime; // in ms
var speed = distance * 1000 / duration; // meters/sec
layout.pace.label = formatPace(speed);
lastGPS = fix;
}
});
Bangle.on("HRM", function(h) {

View File

@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
"version":"0.02",
"version":"0.03",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",

View File

@ -14,7 +14,7 @@
{ "url": "screenshot-mag.png" }
],
"type": "app",
"tags": "tool,sensors",
"tags": "tool,sensors,bluetooth",
"supports" : [ "BANGLEJS2" ],
"allow_emulator": true,
"readme": "README.md",