Merge branch 'espruino:master' into master

master
wagnerf42 2023-12-22 17:26:49 +01:00 committed by GitHub
commit 3fe40c7fe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1104 additions and 412 deletions

1
apps/bad/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: attempt to import

8
apps/bad/README.md Normal file
View File

@ -0,0 +1,8 @@
# Bad Apple demo ![](app.png)
"Bad Apple" is like "Hello World" for graphics system. So this is it
for Bangle.js2. Watch have no speaker, so vibration motor is used,
instead, to produce (pretty quiet) sound.
Tools for preparing bad.araw and bad.vraw are in prep/ directory. Full
3 minute demo should actually fit to the watch.

1
apps/bad/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIifiAFWj//Aod///gAgMH///+AFBn4FB/AQDAoYEB//8gEBAokDAoX+ApguCAAIFqGoYFNLIZHBApZxFAoyDCAoqJCSoqsBUIcPAoKtF4AFBJAS/DHIQAeA="))

BIN
apps/bad/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

65
apps/bad/bad.app.js Normal file
View File

@ -0,0 +1,65 @@
/* sox Rear_Right.wav -r 4k -b 8 -c 1 -e unsigned-integer 0.raw vol 2
aplay -r 4000 /tmp/0.raw
*/
/* https://forum.espruino.com/conversations/348912/ */
let pin = D19;
function play(name, callback) {
function playChar(offs) {
var l = 10240;
var s = require("Storage").read(name, offs, l);
//print("Waveform " + name + " " + s.length);
if (!s.length) {
digitalWrite(pin,0);
if (callback) callback();
return;
}
var w = new Waveform(s.length);
var b = w.buffer;
b.set(s);
//print("Buffer", s.length);
//for (var i=s.length-1;i>=0;i--)b[i]/=4;
w.startOutput(pin, 4000);
w.on("finish", function(buf) {
playChar(offs+l);
});
}
analogWrite(pin, 0.1, {freq:40000});
playChar(0);
}
function video(name, callback) {
function frame() {
var s = require("Storage").read(name, offs, l);
if (!s)
return;
g.drawImage(s, 0, 0, { scale: 2 });
g.flip();
offs += l;
}
g.clear();
var offs = 0;
//var l = 3875; for 176x176
//var l = 515; for 64x64
var l = 971;
setInterval(frame, 200);
}
function run() {
clearInterval(i);
print("Running");
play('bad.araw');
t1 = getTime();
video('bad.vraw');
print("100 frames in ", getTime()-t1);
// 1.7s, unscaled
// 2.68s, scale 1.01
// 5.73s, scale 2.00
// 9.93s, scale 2, full screen
// 14.4s scaled. 176/64
}
print("Loaded");
i = setInterval(run, 100);

BIN
apps/bad/bad.araw Normal file

Binary file not shown.

BIN
apps/bad/bad.vraw Normal file

Binary file not shown.

16
apps/bad/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{ "id": "bad",
"name": "Bad Apple",
"version":"0.01",
"description": "Bad Apple demo",
"icon": "app.png",
"readme": "README.md",
"supports" : ["BANGLEJS2"],
"allow_emulator": false,
"tags": "game",
"storage": [
{"name":"bad.app.js","url":"bad.app.js"},
{"name":"bad.vraw","url":"bad.vraw"},
{"name":"bad.araw","url":"bad.araw"},
{"name":"bad.img","url":"app-icon.js","evaluate":true}
]
}

32
apps/bad/prep/img_convert.py Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/python3
from PIL import Image
import os
def convert_image(input_path, output_width, output_height):
img = Image.open(input_path)
img_resized = img.resize((output_width, output_height), Image.ANTIALIAS)
img_gray = img_resized.convert('L')
img_1bpp = img_gray.point(lambda x: 0 if x < 128 else 255, '1')
return img_1bpp
def convert_and_append_header(input_directory, size):
input_files = [f for f in os.listdir(input_directory) if f.startswith("image_") and f.endswith(".png")]
input_files.sort()
header_bytes = size.to_bytes(1, byteorder='big') + size.to_bytes(1, byteorder='big') + b'\x01'
for i, input_file in enumerate(input_files):
input_path = os.path.join(input_directory, input_file)
img_1bpp = convert_image(input_path, size, size)
output_file = input_path + ".raw"
with open(output_file, 'wb') as raw_file:
raw_file.write(header_bytes)
raw_file.write(img_1bpp.tobytes())
if __name__ == "__main__":
input_directory = "." # Replace with the path to your image directory
output_width = 88
output_file_path = "output_with_header.raw" # Replace with the desired output file path
convert_and_append_header(input_directory, output_width)

20
apps/bad/prep/run Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
# aplay -r 4000 /tmp/0.raw
#bug: Terminal exists on b.js, it is dumb terminal, not vt100.
rm image_*.png image_*.png.raw output.wav ../bad.araw ../bad.vraw
I=bad.mp4
S=1:18
E=1:50
ffmpeg -i $I -ss $S -to $E -vn -acodec pcm_u8 -ar 4000 -ac 1 -y output.wav
./wav_divider.py
mv output.raw ../bad.araw
ffmpeg -i $I -ss $S -to $E -r 5 -vf fps=5 image_%04d.png
./img_convert.py
cat *.png.raw > ../bad.vraw
ls -al ../bad.*

13
apps/bad/prep/wav_divider.py Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/python3
def divide_bytes(input_file, output_file):
with open(input_file, 'rb') as infile:
with open(output_file, 'wb') as outfile:
byte = infile.read(1)
while byte:
# Convert byte to integer, divide by 4, and write back as byte
new_byte = bytes([int.from_bytes(byte, byteorder='big') // 3])
outfile.write(new_byte)
byte = infile.read(1)
divide_bytes("output.wav", "output.raw")

2
apps/binaryclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Added app
0.02: Removed unneeded squares

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AEUiAAQEEkECBRAX/C/4Xrd+hCDI4kgR/4X/C/4XIAF53/C/4X/A4gSDC4kgC5AAvR/4X/C/4A/ADoA=="))

BIN
apps/binaryclk/app-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

39
apps/binaryclk/app.js Normal file
View File

@ -0,0 +1,39 @@
function draw() {
var dt = new Date();
var h = dt.getHours(), m = dt.getMinutes();
const t = [];
t[0] = Math.floor(h/10);
t[1] = Math.floor(h%10);
t[2] = Math.floor(m/10);
t[3] = Math.floor(m%10);
g.reset();
g.clearRect(Bangle.appRect);
let i = 0;
const sq = 29;
const gap = 8;
const mgn = 20;
const pos = sq + gap;
for (let r = 3; r >= 0; r--) {
for (let c = 0; c < 4; c++) {
if (t[c] & Math.pow(2, r)) {
g.fillRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq);
} else {
g.drawRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq);
}
}
i++;
}
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
}
g.clear();
draw();
var secondInterval = setInterval(draw, 60000);
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -0,0 +1,16 @@
{
"id": "binaryclk",
"name": "Bin Clock",
"version": "0.02",
"description": "Clock face to show binary time in 24 hr format",
"icon": "app-icon.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"binaryclk.app.js","url":"app.js"},
{"name":"binaryclk.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -5,3 +5,4 @@
0.05: Show calendar colors & improved all day events.
0.06: Improved multi-line locations & titles
0.07: Buzz 30, 15 and 1 minute before an event
0.08: No buzz during quiet hours & tweaked 30-minute buzz

View File

@ -119,10 +119,12 @@ function fullRedraw() {
function buzzForEvents() {
let nextEvent = next[0]; if (!nextEvent) return;
if (nextEvent.allDay) return;
// No buzz for all day events or events before 7am
// TODO: make this configurable
if (nextEvent.allDay || (new Date(nextEvent.timestamp * 1000)).getHours() < 7) return;
let minToEvent = Math.round((nextEvent.timestamp - getTime()) / 60.0);
switch (minToEvent) {
case 30: require("buzz").pattern(","); break;
case 30: require("buzz").pattern(":"); break;
case 15: require("buzz").pattern(", ,"); break;
case 1: require("buzz").pattern(": : :"); break;
}

View File

@ -2,7 +2,7 @@
"id": "calclock",
"name": "Calendar Clock",
"shortName": "CalClock",
"version": "0.07",
"version": "0.08",
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
"icon": "calclock.png",
"type": "clock",

View File

@ -32,12 +32,14 @@ function readFile(input) {
const jCalData = ICAL.parse(icalText);
const comp = new ICAL.Component(jCalData);
const vtz = comp.getFirstSubcomponent('vtimezone');
const tz = new ICAL.Timezone(vtz);
const tz = vtz != null ? new ICAL.Timezone(vtz) : null;
// Fetch the VEVENT part
comp.getAllSubcomponents('vevent').forEach(vevent => {
const event = new ICAL.Event(vevent);
if (tz != null) {
event.startDate.zone = tz;
}
holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists
const holiday = eventToHoliday(event);

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Bugfixes
0.03: Use Bangle.setBacklight()

View File

@ -6,7 +6,6 @@ Bangle.loadWidgets(); // load before first appRect call
const FIELD_WIDTH = Bangle.appRect.w/8;
const FIELD_HEIGHT = Bangle.appRect.h/8;
const SETTINGS_FILE = "chess.json";
const DEFAULT_TIMEOUT = Bangle.getOptions().lockTimeout;
const ICON_SIZE=45;
const ICON_BISHOP = require("heatshrink").decompress(atob("lstwMB/4Ac/wFE4IED/kPAofgn4FDGon8j4QEBQgQE4EHBQcACwfAgF/BQYWD8EAHAX+NgI4C+AQEwAQDDYIhDDYMDCAQKBGQQsHHogKDCAJODCAI3CHoQKCHoIQDHoIQCFgoQBFgfgIQYmBEIQECKgIrCBYQKDC4OBg/8iCvEAC+AA="));
const ICON_PAWN = require("heatshrink").decompress(atob("lstwMB/4At/AFEGon4h4FDwE/AgX8CAngCAkAv4bDgYbECAf4gAhD4AhD/kAg4mDCAkACAYbBEIYQBG4gbDEII9DFhXAgEfBQYWDEwJUC/wKBGQXwCAgEBE4RCBCAYmBCAQmCCAQmBCAbdCCAIbCQ4gAYwA="));
@ -192,6 +191,7 @@ Bangle.drawWidgets();
// drag selected field
Bangle.on('drag', (ev) => {
if (showmenu) return;
const newx = curfield[0]+ev.dx;
const newy = curfield[1]+ev.dy;
if (newx >= 0 && newx <= 7*FIELD_WIDTH) {
@ -230,7 +230,7 @@ Bangle.on('touch', (button, xy) => {
drawSelectedField();
if (!finished) {
// do computer move
Bangle.setLCDTimeout(0.1); // this can take some time, turn off to save power
Bangle.setBacklight(false); // this can take some time, turn off to save power
showMessage(/*LANG*/"Calculating..");
setTimeout(() => {
const compMove = state.findmove(settings.computer_level+1);
@ -240,15 +240,15 @@ Bangle.on('touch', (button, xy) => {
}
Bangle.setLCDPower(true);
Bangle.setLocked(false);
Bangle.setLCDTimeout(DEFAULT_TIMEOUT/1000); // restore
Bangle.setBacklight(true);
if (!showmenu) {
showAlert(result.string);
}
}, 200); // execute after display update
}, 300); // execute after display update
}
};
move(posFrom, posTo,cb);
}, 200); // execute after display update
}, 100); // execute after display update
} // piece_sel === 0
startfield[0] = startfield[1] = undefined;
piece_sel = 0;
@ -277,7 +277,9 @@ setWatch(() => {
E.showMenu({
"" : { title : /*LANG*/"Chess settings" },
"< Back" : () => closeMenu(),
/*LANG*/"Exit" : () => load(),
/*LANG*/"New Game" : () => {
finished = false;
state = engine.p4_fen2state(engine.P4_INITIAL_BOARD);
writeSettings();
closeMenu();
@ -296,6 +298,5 @@ setWatch(() => {
writeSettings();
}
},
/*LANG*/"Exit" : () => load(),
});
}, BTN, { repeat: true, edge: "falling" });

View File

@ -2,7 +2,7 @@
"id": "chess",
"name": "Chess",
"shortName": "Chess",
"version": "0.02",
"version": "0.03",
"description": "Chess game based on the [p4wn engine](https://p4wn.sourceforge.net/). Drag on the touchscreen to move the green cursor onto a piece, select it with a single touch and drag the now red cursor around. Release the piece with another touch to finish the move. The button opens a menu.",
"icon": "app.png",
"tags": "game",

View File

@ -0,0 +1 @@
1.00: first release

View File

@ -0,0 +1,22 @@
# Colours Demo
This is a simple app to demonstrate colours on a Bangle 2.
The colours are "optimised" for the Bangle 2's 3-bit display. They only include values which use either the full, half or no primary RGB colour, which should reduce the artifacts due to dithering (the exception are light and dark grey).
![](screenshot.png)
Use this app for choosing colours for your own project, and copy the colour definitions from the source code.
## Use colours in other projects
Copy-and-paste the colour constants to be used in your own app from `coloursdemo.app.js`. They are sandwiched between the "BEGIN" and "END" comments at the beginning of the file.
With the constants available in your own code, you can for example set the foreground colour to yellow with:
g.setColor(COLOUR_YELLOW);
This works for any graphics call requiring a colour value (like `g.setBgColor()`).

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AC8sAAYqpmVdr2Irwvklkzq4qBx4ADxAvDM0EyxAABFwgABF4k5rsyGTksF5MzBwdjAAVdnIzCF69dF5FdEYUyF4YADGQSPVF5LwCRwIvHAAIvVllXF5DwCRwgAFNobwbxFeEISOIAAMzF6zwCsgqBDoMsmUzWQMzF5MyeC4lBEwM5nNAsgABGgMyX5JeWF4IsBFYYADnIvBHgJmBrouDBYIvZnIvHLwIABnBvCMwSOXeAQvImU4F4QADMwReXF5csFwwxDF7IlCYAqOEF44uYF5MzF5ReZR4LwBF4qOKnAvalgvBYAk6RxYvaeAs6EYK+lMAZOBlgtBAQS+jF4QoBSQQjBGRKOcF4YjCMgM4AAIyCBoaOcF4YwCAYIvCGQxeceAQvDGoIvFGQYveSAguJF8iOHAAYueF4iOqeAksRyz8CAAzwNR1RgDMQZeIADJ0JqwmCGQoFB0gAEq2A5wAG0ky54AFrowGFQVXAAIyGmVWF8VWF4QyGlmAF8QsDLYIyFFwovbGAIuDSoqOHF8CJCF4aOHF7q/CqyVEAoIuGF7hgEAAiOIF7xhDYgiOHF7oxDXwLyCRxAvfGAYAhF5QA/AH4AEA"))

View File

@ -0,0 +1,128 @@
/*
* Demonstrate colours
*/
// BEGIN colour constants
const COLOUR_BLACK = 0x0000; // same as: g.setColor(0, 0, 0)
const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25)
const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5)
const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75)
const COLOUR_WHITE = 0xffff; // same as: g.setColor(1, 1, 1)
const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0)
const COLOUR_GREEN = 0x07e0; // same as: g.setColor(0, 1, 0)
const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1)
const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0)
const COLOUR_MAGENTA = 0xf81f; // same as: g.setColor(1, 0, 1)
const COLOUR_CYAN = 0x07ff; // same as: g.setColor(0, 1, 1)
const COLOUR_LIGHT_RED = 0xfc10; // same as: g.setColor(1, 0.5, 0.5)
const COLOUR_LIGHT_GREEN = 0x87f0; // same as: g.setColor(0.5, 1, 0.5)
const COLOUR_LIGHT_BLUE = 0x841f; // same as: g.setColor(0.5, 0.5, 1)
const COLOUR_LIGHT_YELLOW = 0xfff0; // same as: g.setColor(1, 1, 0.5)
const COLOUR_LIGHT_MAGENTA = 0xfc1f; // same as: g.setColor(1, 0.5, 1)
const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1)
const COLOUR_DARK_RED = 0x8000; // same as: g.setColor(0.5, 0, 0)
const COLOUR_DARK_GREEN = 0x0400; // same as: g.setColor(0, 0.5, 0)
const COLOUR_DARK_BLUE = 0x0010; // same as: g.setColor(0, 0, 0.5)
const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0)
const COLOUR_DARK_MAGENTA = 0x8010; // same as: g.setColor(0.5, 0, 0.5)
const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5)
const COLOUR_PINK = 0xf810; // same as: g.setColor(1, 0, 0.5)
const COLOUR_LIMEGREEN = 0x87e0; // same as: g.setColor(0.5, 1, 0)
const COLOUR_ROYALBLUE = 0x041f; // same as: g.setColor(0, 0.5, 1)
const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0)
const COLOUR_INDIGO = 0x801f; // same as: g.setColor(0.5, 0, 1)
const COLOUR_TURQUOISE = 0x07f0; // same as: g.setColor(0, 1, 0.5)
// END colour constants
// array of colours to be demoed:
// [ colour value, label colour, label ]
const demo = [
[ COLOUR_LIGHT_RED, COLOUR_BLACK, 'LIGHT RED' ],
[ COLOUR_RED, COLOUR_WHITE, 'RED' ],
[ COLOUR_DARK_RED, COLOUR_WHITE, 'DARK RED' ],
[ COLOUR_LIGHT_YELLOW, COLOUR_BLACK, 'LIGHT YELLOW' ],
[ COLOUR_YELLOW, COLOUR_BLACK, 'YELLOW' ],
[ COLOUR_DARK_YELLOW, COLOUR_WHITE, 'DARK YELLOW' ],
[ COLOUR_LIGHT_GREEN, COLOUR_BLACK, 'LIGHT GREEN' ],
[ COLOUR_GREEN, COLOUR_BLACK, 'GREEN' ],
[ COLOUR_DARK_GREEN, COLOUR_WHITE, 'DARK GREEN' ],
[ COLOUR_LIGHT_CYAN, COLOUR_BLACK, 'LIGHT CYAN' ],
[ COLOUR_CYAN, COLOUR_BLACK, 'CYAN' ],
[ COLOUR_DARK_CYAN, COLOUR_WHITE, 'DARK CYAN' ],
[ COLOUR_LIGHT_BLUE, COLOUR_BLACK, 'LIGHT BLUE' ],
[ COLOUR_BLUE, COLOUR_WHITE, 'BLUE' ],
[ COLOUR_DARK_BLUE, COLOUR_WHITE, 'DARK BLUE' ],
[ COLOUR_LIGHT_MAGENTA, COLOUR_BLACK, 'LIGHT MAGENTA' ],
[ COLOUR_MAGENTA, COLOUR_WHITE, 'MAGENTA' ],
[ COLOUR_DARK_MAGENTA, COLOUR_WHITE, 'DARK MAGENTA' ],
[ COLOUR_LIMEGREEN, COLOUR_BLACK, 'LIMEGREEN' ],
[ COLOUR_TURQUOISE, COLOUR_BLACK, 'TURQUOISE' ],
[ COLOUR_ROYALBLUE, COLOUR_WHITE, 'ROYALBLUE' ],
[ COLOUR_ORANGE, COLOUR_BLACK, 'ORANGE' ],
[ COLOUR_PINK, COLOUR_WHITE, 'PINK' ],
[ COLOUR_INDIGO, COLOUR_WHITE, 'INDIGO' ],
[ COLOUR_LIGHT_GREY, COLOUR_BLACK, 'LIGHT GREY' ],
[ COLOUR_GREY, COLOUR_BLACK, 'GREY' ],
[ COLOUR_DARK_GREY, COLOUR_WHITE, 'DARK GREY' ],
[ COLOUR_WHITE, COLOUR_BLACK, 'WHITE' ],
[ COLOUR_BLACK, COLOUR_WHITE, 'BLACK' ],
];
const columns = 3;
const rows = 10;
// initialise
g.clear(true);
g.setFont('6x8').setFontAlign(-1, -1);
// calc some values required to draw the grid
const colWidth = Math.floor(g.getWidth() / columns);
const rowHeight = Math.floor(g.getHeight() / rows);
const xStart = Math.floor((g.getWidth() - (columns * colWidth)) / 2);
var x = xStart;
var y = Math.floor((g.getHeight() - (rows * rowHeight)) / 2);
// loop through the colours to be demoed
for (var idx in demo) {
var colour = demo[idx][0];
var labelColour = demo[idx][1];
var label = demo[idx][2];
// draw coloured box
g.setColor(colour).fillRect(x, y, x + colWidth - 1, y + rowHeight - 1);
// label it
g.setColor(labelColour).drawString(g.wrapString(label, colWidth).join("\n"), x, y);
x += colWidth;
if ((x + colWidth) >= g.getWidth()) {
x = xStart;
y += rowHeight;
}
}
// there's an "unused" box left - cross it out
g.setColor(COLOUR_RED);
g.drawLine(x, y, x + colWidth - 1, y + rowHeight - 1);
g.drawLine(x, y + rowHeight - 1, x + colWidth - 1, y);
// exit on button press
setWatch(e => { Bangle.showClock(); }, BTN1);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,17 @@
{
"id": "coloursdemo",
"name": "Colours Demo",
"shortName":"Colours Demo",
"version":"1.00",
"description": "Simple app to demonstrate colours",
"icon": "coloursdemo.png",
"screenshots": [{ "url": "screenshot.png" }],
"type": "app",
"tags": "tool",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{ "name":"coloursdemo.app.js", "url":"coloursdemo.app.js" },
{ "name":"coloursdemo.img", "url":"coloursdemo-icon.js", "evaluate":true }
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -29,3 +29,4 @@
0.26: Implement API for activity fetching
0.27: Fix typo in daily summary graph code causing graph not to load
Fix daily summaries for 31st of the month
0.28: Calculate distance from steps if myprofile is installed and stride length is set

View File

@ -13,7 +13,7 @@ To view data, run the `Health` app from your watch.
Stores:
* Heart rate
* Step count
* Step count (can calculate distance if myprofile is installed and stride length is set)
* Movement
## Settings

View File

@ -1,3 +1,6 @@
let settings;
const myprofile = require("Storage").readJSON("myprofile.json",1)||{};
function menuMain() {
E.showMenu({
"": { title: /*LANG*/"Health Tracking" },
@ -5,16 +8,31 @@ function menuMain() {
/*LANG*/"Step Counting": () => menuStepCount(),
/*LANG*/"Movement": () => menuMovement(),
/*LANG*/"Heart Rate": () => menuHRM(),
/*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>menuMain())
/*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>{loadSettings();menuMain();})
});
}
function menuStepCount() {
E.showMenu({
const menu = {
"": { title:/*LANG*/"Steps" },
/*LANG*/"< Back": () => menuMain(),
/*LANG*/"per hour": () => stepsPerHour(),
/*LANG*/"per day": () => stepsPerDay()
/*LANG*/"per hour": () => stepsPerHour(menuStepCount),
/*LANG*/"per day": () => stepsPerDay(menuStepCount)
};
if (myprofile.strideLength) {
menu[/*LANG*/"distance"] = () => menuDistance();
}
E.showMenu(menu);
}
function menuDistance() {
const distMult = 1*require("locale").distance(myprofile.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm'
E.showMenu({
"": { title:/*LANG*/"Distance" },
/*LANG*/"< Back": () => menuStepCount(),
/*LANG*/"per hour": () => stepsPerHour(menuDistance, distMult),
/*LANG*/"per day": () => stepsPerDay(menuDistance, distMult)
});
}
@ -36,23 +54,35 @@ function menuHRM() {
});
}
function stepsPerHour() {
function stepsPerHour(back, mult) {
E.showMessage(/*LANG*/"Loading...");
current_selection = "stepsPerHour";
var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.steps);
setButton(menuStepCount);
barChart(/*LANG*/"HOUR", data);
if (mult !== undefined) {
// Calculate distance from steps
data.forEach((d, i) => data[i] = d*mult+0.5);
}
setButton(back, mult);
barChart(/*LANG*/"HOUR", data, mult);
}
function stepsPerDay() {
function stepsPerDay(back, mult) {
E.showMessage(/*LANG*/"Loading...");
current_selection = "stepsPerDay";
var data = new Uint16Array(32);
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps);
setButton(menuStepCount);
barChart(/*LANG*/"DAY", data);
drawHorizontalLine(settings.stepGoal);
// Include data for today
if (data[(new Date()).getDate()] === 0) {
data[(new Date()).getDate()] = Bangle.getHealthStatus("day").steps;
}
if (mult !== undefined) {
// Calculate distance from steps
data.forEach((d, i) => data[i] = d*mult+0.5);
}
setButton(back, mult);
barChart(/*LANG*/"DAY", data, mult);
drawHorizontalLine(settings.stepGoal * (mult || 1));
}
function hrmPerHour() {
@ -64,7 +94,7 @@ function hrmPerHour() {
data[h.hr]+=h.bpm;
if (h.bpm) cnt[h.hr]++;
});
data.forEach((d,i)=>data[i] = d/cnt[i]);
data.forEach((d,i)=>data[i] = d/cnt[i]+0.5);
setButton(menuHRM);
barChart(/*LANG*/"HOUR", data);
}
@ -78,7 +108,7 @@ function hrmPerDay() {
data[h.day]+=h.bpm;
if (h.bpm) cnt[h.day]++;
});
data.forEach((d,i)=>data[i] = d/cnt[i]);
data.forEach((d,i)=>data[i] = d/cnt[i]+0.5);
setButton(menuHRM);
barChart(/*LANG*/"DAY", data);
}
@ -92,7 +122,7 @@ function movementPerHour() {
data[h.hr]+=h.movement;
cnt[h.hr]++;
});
data.forEach((d,i)=>data[i] = d/cnt[i]);
data.forEach((d,i)=>data[i] = d/cnt[i]+0.5);
setButton(menuMovement);
barChart(/*LANG*/"HOUR", data);
}
@ -106,7 +136,7 @@ function movementPerDay() {
data[h.day]+=h.movement;
cnt[h.day]++;
});
data.forEach((d,i)=>data[i] = d/cnt[i]);
data.forEach((d,i)=>data[i] = d/cnt[i]+0.5);
setButton(menuMovement);
barChart(/*LANG*/"DAY", data);
}
@ -180,7 +210,7 @@ function drawHorizontalLine(value) {
g.setColor(g.theme.fg).drawLine(0, top ,g.getWidth(), top);
}
function setButton(fn) {
function setButton(fn, mult) {
Bangle.setUI({mode:"custom",
back:fn,
swipe:(lr,ud) => {
@ -194,12 +224,16 @@ function setButton(fn) {
}
drawBarChart();
if (current_selection == "stepsPerDay") {
drawHorizontalLine(settings.stepGoal);
drawHorizontalLine(settings.stepGoal * (mult || 1));
}
}});
}
function loadSettings() {
settings = require("Storage").readJSON("health.json",1)||{};
}
Bangle.loadWidgets();
Bangle.drawWidgets();
var settings = require("Storage").readJSON("health.json",1)||{};
loadSettings();
menuMain();

View File

@ -2,7 +2,7 @@
"id": "health",
"name": "Health Tracking",
"shortName": "Health",
"version": "0.27",
"version": "0.28",
"description": "Logs health data and provides an app to view it",
"icon": "app.png",
"tags": "tool,system,health",

View File

@ -8,6 +8,7 @@
function setSettings() {
require("Storage").writeJSON("health.json", settings);
}
E.showMenu({
"": { title: /*LANG*/"Health Tracking" },
@ -39,6 +40,7 @@
setSettings();
}
},
/*LANG*/"Step Goal Notification": {
value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false,
format: () => (settings.stepGoalNotification ? 'Yes' : 'No'),
@ -46,6 +48,6 @@
settings.stepGoalNotification = !settings.stepGoalNotification;
setSettings();
}
}
},
});
})

View File

@ -18,3 +18,4 @@
Remove calls to Bangle.loadWidgets as they are not needed and create warnings
0.32: Added setting to show single timezone small, like where multiple ones are configured
0.33: Tidy up and fix clearInterval(undefined) errors
0.34: Fix 'fast load' so clock doesn't always redraw when screen unlocked/locked

View File

@ -443,7 +443,7 @@ Bangle.setUI({
drawTimeout = undefined;
//if (BANGLEJS2)
Bangle.removeListener("drag",onDrag);
Bangle.removeListener("onLock",onLock);
Bangle.removeListener("lock",onLock);
}});
g.clear().setRotation(defaultRotation); // clean app screen and make sure the default rotation is set

View File

@ -2,7 +2,7 @@
"id": "hworldclock",
"name": "Hanks World Clock",
"shortName": "Hanks World Clock",
"version": "0.33",
"version": "0.34",
"description": "Current time zone plus up to three others",
"allow_emulator":true,
"icon": "app.png",

View File

@ -27,3 +27,4 @@
0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
0.28: Battery Vref implemented correctly.
0.29: Support fastload.
0.30: Add many new colors to the settings and allows random colors on startup if enabled.

View File

@ -19,6 +19,7 @@ the "sched" app must be installed on your device.
* The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen.
* Customizable theming colors in the settings menu of the app.
* Allow random colors on startup.
* Enable or disable the alarm feature.
* Enable or disbale the graphs for steps + hrm.

View File

@ -16,18 +16,36 @@ let settings = {
themeColor3BG: "#0094FF",
disableAlarms: false,
disableData: false,
randomColors: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key];
}
/*
* Colors to use
*/
let color1 = settings.themeColor3BG;
let color2 = settings.themeColor1BG;
let color3 = settings.themeColor2BG;
//Colors to use
var color_options = [
'Green', 'Orange', 'Cyan', 'Purple', 'Red', 'Blue', 'Yellow', 'White',
'Purple', 'Pink', 'Light Green', 'Brown', 'Turquoise', 'Magenta', 'Lime',
'Gold', 'Sky Blue', 'Rose', 'Lavender', 'Amber', 'Indigo', 'Teal',
'Crimson', 'Maroon', 'Firebrick', 'Dark Red', 'Aqua', 'Emerald', 'Royal Blue',
'Sunset Orange', 'Turquoise Blue', 'Hot Pink', 'Goldenrod', 'Deep Sky Blue'
];
var bg_code = [
'#00ff00', '#FF9900', '#0094FF', '#FF00DC', '#ff0000', '#0000ff', '#ffef00', '#FFFFFF',
'#FF00FF', '#6C00FF', '#99FF00', '#8B4513', '#40E0D0', '#FF00FF', '#00FF00', '#FFD700',
'#87CEEB', '#FF007F', '#E6E6FA', '#FFBF00', '#4B0082', '#008080', '#DC143C', '#800000',
'#B22222', '#8B0000', '#00FFFF', '#008000', '#4169E1', '#FF4500', '#40E0D0', '#FF69B4',
'#DAA520', '#00BFFF'
];
let color1;
let color2;
let color3;
let cWhite = "#FFFFFF";
let cBlack = "#000000";
let cGrey = "#424242";
@ -58,10 +76,77 @@ let convert24to16 = function(input)
return "0x"+RGB565.toString(16);
};
let color1C = convert24to16(color1);//Converting colors to the correct format.
let color2C = convert24to16(color2);
let color3C = convert24to16(color3);
//Converting colors to the correct format.
let randomColors = function () {
if (settings.randomColors) {
do {
color1 = getRandomColor();
color2 = getRandomColor();
color3 = getRandomColor();
} while (!areColorsDistinct(color1, color2, color3));
} else {
color1 = settings.themeColor3BG;
color2 = settings.themeColor1BG;
color3 = settings.themeColor2BG;
}
// Converting colors to the correct format.
color1C = convert24to16(color1);
color2C = convert24to16(color2);
color3C = convert24to16(color3);
};
// Function to get a random color from the bg_code array.
let getRandomColor = function () {
return bg_code[Math.floor(Math.random() * bg_code.length)];
};
// Function to check if three colors are distinct enough.
let areColorsDistinct = function (color1, color2, color3) {
return (
color1 !== color2 &&
color2 !== color3 &&
color1 !== color3 &&
hasSufficientContrast(color1, color2) &&
hasSufficientContrast(color2, color3) &&
hasSufficientContrast(color1, color3)
);
};
// Function to calculate contrast between two colors.
let hasSufficientContrast = function (color1, color2) {
const contrastThreshold = 0.10; // Adjust this threshold based on your preference.
// Calculate the luminance values (for simplicity, assuming sRGB color space).
const luminance1 = getLuminance(color1);
const luminance2 = getLuminance(color2);
// Calculate the contrast ratio.
const contrastRatio = (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05);
// Check if the contrast ratio meets the threshold.
return contrastRatio >= contrastThreshold;
};
// Function to calculate luminance from a hex color.
let getLuminance = function (hexColor) {
const rgb = hexToRgb(hexColor);
return 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
};
// Function to convert hex color to RGB.
let hexToRgb = function (hex) {
const bigint = parseInt(hex.slice(1), 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return { r, g, b };
};
randomColors();//Apply random colors if applied
/*
* Requirements and globals
*/
@ -224,7 +309,7 @@ let drawData = function(key, y, c){
let _drawData = function(key, y, c){
key = key.toUpperCase()
key = key.toUpperCase();
let text = key;
let value = "ERR";
let should_print= true;
@ -271,7 +356,7 @@ let _drawData = function(key, y, c){
value = Math.round(data.altitude);
printRow(text, value, y, c);
}
})
});
} else if(key == "CORET"){
value = locale.temp(parseInt(E.getTemperature()));
@ -374,7 +459,7 @@ let drawPosition0 = function(){
drawHorizontalBgLine(color2, batStart, batX2, 171, 5);
drawHorizontalBgLine(cGrey, batX2, 172, 171, 5);
for(let i=0; i+batStart<=172; i+=parseInt(batWidth/4)){
drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8)
drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8);
}
// Draw Infos
@ -603,7 +688,7 @@ let getWeather = function(){
let speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934;
weather.wind = Math.round(wind[1] * speedFactor);
return weather
return weather;
} catch(ex) {
// Return default
@ -649,7 +734,7 @@ let getAlarmMinutes = function(){
let increaseAlarm = function(){
try{
let minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
let alarm = require('sched')
let alarm = require('sched');
alarm.setAlarm(TIMER_IDX, {
timer : (minutes+5)*60*1000,
});
@ -662,7 +747,7 @@ let decreaseAlarm = function(){
let minutes = getAlarmMinutes();
minutes -= 5;
let alarm = require('sched')
let alarm = require('sched');
alarm.setAlarm(TIMER_IDX, undefined);
if(minutes > 0){
@ -773,7 +858,6 @@ Bangle.setUI({mode:"clock",remove:function() {
widget_utils.cleanup();
}});
Bangle.loadWidgets();
// Clear the screen once, at startup and draw clock
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
draw();

View File

@ -15,6 +15,7 @@
themeColor3BG: "#0094FF",
disableAlarms: false,
disableData: false,
randomColors: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
@ -28,9 +29,21 @@
var dataOptions = ["Steps", "Battery", "BattVolt", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"];
var speedOptions = ["kph", "mph"];
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green'];
var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F'];
var color_options = [
'Green', 'Orange', 'Cyan', 'Purple', 'Red', 'Blue', 'Yellow', 'White',
'Purple', 'Pink', 'Light Green', 'Brown', 'Turquoise', 'Magenta', 'Lime',
'Gold', 'Sky Blue', 'Rose', 'Lavender', 'Amber', 'Indigo', 'Teal',
'Crimson', 'Maroon', 'Firebrick', 'Dark Red', 'Aqua', 'Emerald', 'Royal Blue',
'Sunset Orange', 'Turquoise Blue', 'Hot Pink', 'Goldenrod', 'Deep Sky Blue'
];
var bg_code = [
'#00ff00', '#FF9900', '#0094FF', '#FF00DC', '#ff0000', '#0000ff', '#ffef00', '#FFFFFF',
'#FF00FF', '#6C00FF', '#99FF00', '#8B4513', '#40E0D0', '#FF00FF', '#00FF00', '#FFD700',
'#87CEEB', '#FF007F', '#E6E6FA', '#FFBF00', '#4B0082', '#008080', '#DC143C', '#800000',
'#B22222', '#8B0000', '#00FFFF', '#008000', '#4169E1', '#FF4500', '#40E0D0', '#FF69B4',
'#DAA520', '#00BFFF'
];
E.showMenu({
'': { 'title': 'LCARS Clock' },
'< Back': back,
@ -80,7 +93,7 @@
},
'Theme Color 1': {
value: 0 | bg_code.indexOf(settings.themeColor1BG),
min: 0, max: 11,
min: 0, max: 34,
format: v => color_options[v],
onchange: v => {
settings.themeColor1BG = bg_code[v];
@ -89,7 +102,7 @@
},
'Theme Color 2': {
value: 0 | bg_code.indexOf(settings.themeColor2BG),
min: 0, max: 11,
min: 0, max: 34,
format: v => color_options[v],
onchange: v => {
settings.themeColor2BG = bg_code[v];
@ -98,7 +111,7 @@
},
'Theme Color 3': {
value: 0 | bg_code.indexOf(settings.themeColor3BG),
min: 0, max: 11,
min: 0, max: 34,
format: v => color_options[v],
onchange: v => {
settings.themeColor3BG = bg_code[v];
@ -121,5 +134,13 @@
save();
},
},
'Random colors on open': {
value: settings.randomColors,
format: () => (settings.randomColors ? 'Yes' : 'No'),
onchange: () => {
settings.randomColors = !settings.randomColors;
save();
},
},
});
})

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.29",
"version":"0.30",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",

18
apps/myprofile/README.md Normal file
View File

@ -0,0 +1,18 @@
# My Profile
Configure your personal profile. All settings are optional and are only stored on the watch.
## Available settings
| Setting | Description | Displayed in | Stored in | Default value | How to measure |
| ------------- | ------------------------------- | ------------------- | ------------ | ------------- | ----------------------------------------------------------------- |
| Birthday | Used to calculate age | year, month, day | 'YYYY-MM-DD' | 01.01.1970 | - |
| HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.<br/> If unsure set to 220-age. |
| HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up |
| Height | Body height | local length unit | meter | 0 (=not set) | - |
| Stride length | distance traveled with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 |
## Developer notes
- Feel free to add additional settings.
- For values without reasonable defaults never assume that a value is set. Always check the value before using it.

BIN
apps/myprofile/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,18 @@
{ "id": "myprofile",
"name": "My Profile",
"shortName":"My Profile",
"icon": "app.png",
"type": "settings",
"version":"0.01",
"description": "Configure your personal profile. All settings are optional and only stored on the watch.",
"readme": "README.md",
"tags": "tool,utility",
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"myprofile.settings.js","url":"settings.js"}
],
"data": [
{"name":"myprofile.json"}
]
}

138
apps/myprofile/settings.js Normal file
View File

@ -0,0 +1,138 @@
(function(back) {
const FILE = "myprofile.json";
const myprofile = Object.assign({
minHrm: 60,
maxHrm: 200,
strideLength: 0, // 0 = not set
birthday: '1970-01-01',
height: 0, // 0 = not set
}, require('Storage').readJSON(FILE, true) || {});
function writeProfile() {
require('Storage').writeJSON(FILE, myprofile);
}
const ageMenu = () => {
const date = new Date(myprofile.birthday);
E.showMenu({
"" : { "title" : /*LANG*/"Birthday" },
"< Back" : () => {
if (date != new Date(myprofile.birthday)) {
// Birthday changed
if (date > new Date()) {
E.showPrompt(/*LANG*/"Birthday must not be in future!", {
buttons : {"Ok":true},
}).then(() => ageMenu());
} else {
const age = (new Date()).getFullYear() - date.getFullYear();
const newMaxHRM = 220-age;
E.showPrompt(/*LANG*/`Set HR max to ${newMaxHRM} calculated from age?`).then(function(v) {
myprofile.birthday = date.getFullYear() + "-" + (date.getMonth() + 1).toString().padStart(2, '0') + "-" + date.getDate().toString().padStart(2, '0');
if (v) {
myprofile.maxHrm = newMaxHRM;
}
writeProfile();
mainMenu();
});
}
} else {
mainMenu();
}
},
/*LANG*/"Day": {
value: date ? date.getDate() : null,
min: 1,
max: 31,
wrap: true,
onchange: v => {
date.setDate(v);
}
},
/*LANG*/"Month": {
value: date ? date.getMonth() + 1 : null,
format: v => require("date_utils").month(v),
onchange: v => {
date.setMonth((v+11)%12);
}
},
/*LANG*/"Year": {
value: date ? date.getFullYear() : null,
min: 1900,
max: (new Date()).getFullYear(),
onchange: v => {
date.setFullYear(v);
}
},
});
};
const mainMenu = () => {
E.showMenu({
"" : { "title" : /*LANG*/"My Profile" },
"< Back" : () => back(),
/*LANG*/"Birthday" : () => ageMenu(),
/*LANG*/'Height': {
value: myprofile.height,
min: 0, max: 300,
step:0.01,
format: v => v ? require("locale").distance(v, 2) : '-',
onchange: v => {
if (v !== myprofile.height) {
// height changed
myprofile.height = v;
setTimeout(() => {
const newStrideLength = myprofile.height * 0.414;
E.showPrompt(/*LANG*/`Set Stride length to ${require("locale").distance(newStrideLength, 2)} calculated from height?`).then(function(v) {
if (v) {
myprofile.strideLength = newStrideLength;
}
writeProfile();
mainMenu();
});
}, 1);
}
}
},
/*LANG*/'HR max': {
format: v => /*LANG*/`${v} BPM`,
value: myprofile.maxHrm,
min: 30, max: 220,
onchange: v => {
myprofile.maxHrm = v;
writeProfile();
}
},
/*LANG*/'HR min': {
format: v => /*LANG*/`${v} BPM`,
value: myprofile.minHrm,
min: 30, max: 220,
onchange: v => {
myprofile.minHrm = v;
writeProfile();
}
},
/*LANG*/"Stride length": {
value: myprofile.strideLength,
min:0.00,
step:0.01,
format: v => v ? require("locale").distance(v, 2) : '-',
onchange: v => {
myprofile.strideLength=v;
writeProfile();
},
},
});
};
mainMenu();
})

View File

@ -31,3 +31,4 @@
0.24: Allow zooming by clicking the screen
0.25: Enable scaled image filtering on 2v19+ firmware
0.26: Ensure that when redrawing, we always cancel any in-progress track draw
0.27: Display message if no map is installed

View File

@ -268,7 +268,12 @@ function showMap() {
}, btn: () => showMenu() });
}
if (m.maps.length === 0) {
E.showPrompt(/*LANG*/'Please upload a map first.', {buttons : {/*LANG*/"Ok":true}}).then(v => load());
} else {
showMap();
}
// Write settings on exit via button
setWatch(() => writeSettings(), BTN, { repeat: true, edge: "rising" });

View File

@ -2,7 +2,7 @@
"id": "openstmap",
"name": "OpenStreetMap",
"shortName": "OpenStMap",
"version": "0.26",
"version": "0.27",
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
"readme": "README.md",
"icon": "app.png",

View File

@ -33,9 +33,11 @@ m.maps = require("Storage").list(/openstmap\.\d+\.json/).map(f=>{
m.maps.sort((a,b) => b.scale-a.scale); // sort by scale so highest resolution is drawn last
// we base our start position on the middle of the first map
m.map = m.maps[0];
if (m.map) {
m.scale = m.map.scale; // current scale (based on first map)
m.lat = m.map.lat; // position of middle of screen
m.lon = m.map.lon; // position of middle of screen
}
// return number of tiles drawn
exports.draw = function() {

View File

@ -6,3 +6,9 @@
0.06: Initial internal git(hub) release. Added icon and such.
0.07: Begin work on presentation parts.
0.08: Presentation parts!
0.09: Code cleanup and windows 11 support.
0.10: Bugfixes.
0.11: Fix mouse move getting stuck.
0.12: Added support for mouse dragging action (click then drag).
0.13: Removed mouse dragging (too buggy/unuseful).
0.14: Bugfix and add pointer mode. Also added default screen when no parts are there.

View File

@ -1,2 +1,80 @@
# Presentor
Use your Bangle to present!
This app basically turns your BangleJS watch into a presentor tool.
Very useful for presentations or when you are teaching a lesson.
### Features:
+ Control your (powerpoint) slides by swiping left and right.
+ See pre-programmed presentation notes by swiping up and down.
+ See if you are on time in your presentation.
+ Get notified if you go over-time for a certain subject.
+ Control cursor or spotlight in mouse mode (pressing the button).
### Usage:
Here is a step by step guide (thanks mu1)
1. If bluetooth connected to PC/laptop (check in bluetooth settings):
Select Bangle.js > Remove Device
2. On your smartphone, in "Gadgetbridge", close connection by long-pressing "Bangle.js"
3. Open banglejs.com/apps and select "Connect" (right upper corner)
4. Select Bangle.js in blue bar in pop-up window and select "Connect"
5. Search "Presentor" app and select diskette icon
6. Select "Clear" to start making new notes.
7. Fill the subjects, times in minutes (seconds optional) and Notes (optional)
The subject/times are on your watch displayed in a tall font, the notes in a very small font
Note: If you don't fill in any notes you can still save and the presentor will be for input only and not give any timing details.
8. Select "Save" and exit the pop-up window
9. Select "Disconnect" (right upper corner)
10. Open the Presentor app. This will start the correct HID service as well.
11. Open "Bluetooth & other devices" screen on PC/laptop and select "+" to add a device
12. Select "Bluetooth" and select Bangle.js
If "Try connecting your device again." is shown, switch PC/laptop bluetooth off and on again
13. Start your presentation.
- Swipe up to start timer for first subject
- Swipe down to pause
- Push button to switch between mouse mode and timer/presentation mode
14. Timer function
- Next subject: Swipe up
- Previous subject: Swipe down
- Pause timer: Swipe down to begin
15. Presentation function
- Next presentation slide: Swipe right or just touch
- Previous presentation slide: Swipe left
16. Mouse mode
- Swipe to move pointer over the presentation screen
- Tap to left click.
- Hold longer to right click.
17. Holding mode
In timer/presentation mode, hold one finger on your Bangle screen, point your arm foreward
- Mouse up/down on screen: Tilt your hand up/down
- Mouse left/right on screen: Rotate your hand counterclockwise/clockwise
18. Spotlight mode
- Dubble press button to go into presentor mode.
- Allows to move mouse but no clicking.
- Presses SHIFT+F10 on start (you can use app such as powertoys to set spotlight to this combo)
- Presses F10 at the end (will dis-engage spotlight for instance in powertoys)
19. End of presentation?
Switch bluetooth PC/laptop off or see step 1
20. Re-activate smartphone bluetooth (incl. Gadgetbridge)
### Creator
[7kasper](https://github.com/7kasper)
Issues or feature requests are welcome on my [Github](https://github.com/7kasper/BangleApps) or on the [Espruino Forums](https://forum.espruino.com/conversations/371443/)!

View File

@ -1,80 +1,12 @@
// Presentor by 7kasper (Kasper Müller)
// Version 3.0
const SpecialReport = new Uint8Array([
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x05, // USAGE_MAXIMUM (Button 5)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x38, // USAGE (Wheel)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3)
0x81, 0x06, // INPUT (Data,Var,Rel)
0x05, 0x0c, // USAGE_PAGE (Consumer Devices)
0x0a, 0x38, 0x02, // USAGE (AC Pan)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0, // END_COLLECTION
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x02, // REPORT_ID (2)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x01, // INPUT (Cnst,Ary,Abs)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x73, // USAGE_MAXIMUM (Keyboard F24)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x73, // LOGICAL_MAXIMUM (115)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0 // END_COLLECTION
]);
const MouseButton = {
NONE : 0,
LEFT : 1,
RIGHT : 2,
MIDDLE : 4,
BACK : 8,
FORWARD: 16
};
const kb = require("ble_hid_keyboard");
// Version 0.14
// Imports
const bt = require("ble_hid_combo");
const Layout = require("Layout");
const Locale = require("locale");
// App Layout
let mainLayout = new Layout({
'type': 'v',
filly: 1,
@ -153,18 +85,20 @@ let lasty = 0;
// Mouse states
let holding = false;
let trackPadMode = false;
let focusMode = false;
// Timeout IDs.
let timeoutId = -1;
let timeoutHolding = -1;
let timeoutDraw = -1;
let timeoutSendMouse = -1;
let homeRoll = 0;
let homePitch = 0;
let mCal = 0;
let mttl = 0;
let cttl = 0;
let bttl = 0;
// BT helper.
let clearToSend = true;
@ -174,7 +108,7 @@ let ptimers = [];
function delay(t, v) {
return new Promise((resolve) => {
setTimeout(resolve, t)
setTimeout(resolve, t);
});
}
@ -266,6 +200,11 @@ function drawMain() {
function doPPart(r) {
pparti += r;
if (settings.pparts.length == 0) {
mainLayout.Subject.label = 'PRESENTOR';
mainLayout.Notes.label = '';
return;
}
if (pparti < 0) {
pparti = -1;
mainLayout.Subject.label = 'PAUSED';
@ -289,56 +228,34 @@ function doPPart(r) {
drawMainFrame();
}
NRF.setServices(undefined, { hid : SpecialReport });
// TODO: figure out how to detect HID.
// Turn on Bluetooth as presentor.
NRF.setServices(undefined, { hid : bt.report });
NRF.on('HID', function() {
if (!HIDenabled) {
Bangle.buzz(200);
HIDenabled = true;
});
function moveMouse(x,y,b,wheel,hwheel,callback) {
if (!HIDenabled) return;
if (!b) b = 0;
if (!wheel) wheel = 0;
if (!hwheel) hwheel = 0;
NRF.sendHIDReport([1,b,x,y,wheel,hwheel,0,0], function() {
if (callback) callback();
});
}
});
//
NRF.setAdvertising([
{}, // include original Advertising packet
[ // second packet containing 'appearance'
2, 1, 6, // standard Bluetooth flags
3,3,0x12,0x18, // HID Service
3,0x19,0xCA,0x03 // Appearance: Presentation Remote
]
]);
// function getSign(x) {
// return ((x > 0) - (x < 0)) || +x;
// }
function scroll(wheel,hwheel,callback) {
moveMouse(0,0,0,wheel,hwheel,callback);
}
// Single click a certain button (immidiatly release).
function clickMouse(b, callback) {
if (!HIDenabled) return;
NRF.sendHIDReport([1,b,0,0,0,0,0,0], function() {
NRF.sendHIDReport([1,0,0,0,0,0,0,0], function() {
if (callback) callback();
});
});
}
function pressKey(keyCode, modifiers, callback) {
if (!HIDenabled) return;
if (!modifiers) modifiers = 0;
NRF.sendHIDReport([2, modifiers,0,keyCode,0,0,0,0], function() {
NRF.sendHIDReport([2,0,0,0,0,0,0,0], function() {
if (callback) callback();
});
});
}
function handleAcc(acc) {
let rRoll = acc.y * -50;
let rPitch = acc.x * -100;
if (mCal > 10) {
//console.log("x: " + (rRoll - homeRoll) + " y:" + (rPitch - homePitch));
moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch);
bt.moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch);
} else {
//console.log("homeroll: " +homeRoll +"homepitch: " + homePitch);
homeRoll = rRoll * 0.7 + homeRoll * 0.3;
@ -347,34 +264,37 @@ function handleAcc(acc) {
}
}
Bangle.on('lock', function(on) {
if (on && holding) {
if (on && (holding || trackPadMode)) {
Bangle.setLocked(false);
Bangle.setLCDPower(1);
}
});
function startHolding() {
pressKey(kb.KEY.F10);
bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT);
holding = true;
focusMode = true;
Bangle.buzz();
E.showMessage('Holding');
Bangle.on('accel', handleAcc);
Bangle.setLCDPower(1);
}
function stopHolding() {
clearTimeout(timeoutId);
if (holding) {
pressKey(kb.KEY.F10);
bt.tapKey(bt.KEY.F10);
// bt.tapKey(bt.KEY.F10);
homePitch = 0;
homeRoll = 0;
holding = false;
focusMode = false;
mCal = 0;
Bangle.removeListener('accel', handleAcc);
Bangle.buzz();
drawMain();
} else {
timeoutId = setTimeout(drawMain, 1000);
}
// else {
// timeoutId = setTimeout(drawMain, 1000);
// }
clearTimeout(timeoutHolding);
timeoutHolding = -1;
}
@ -395,25 +315,29 @@ Bangle.on('drag', function(e) {
//let qX = getSign(difX) * Math.pow(Math.abs(difX), 1.2);
//let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2);
let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY;
moveMouse(qX, qY, 0, 0, 0, function() {
setTimeout(function() {clearToSend = true;}, 50);
bt.moveMouse(qX, qY, 0, 0, 0, function() {
timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50);
});
lastx = e.x;
lasty = e.y;
mttl = getTime();
console.log("Dx: " + (qX) + " Dy: " + (qY));
} else if (timeoutSendMouse == -1) { // Can happen perhaps on single bluetooth failure.
timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50);
}
if (!e.b) {
if (!focusMode) {
// short press
if (getTime() - cttl < 0.2) {
clickMouse(MouseButton.LEFT);
bt.clickButton(bt.BUTTON.LEFT);
console.log("click left");
}
// longer press in center
else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) {
clickMouse(MouseButton.RIGHT);
bt.clickButton(bt.BUTTON.RIGHT);
console.log("click right");
}
}
cttl = 0;
lastx = 0;
lasty = 0;
@ -430,15 +354,17 @@ Bangle.on('drag', function(e) {
} else if(lastx > 40){
// E.showMessage('right');
//kb.tap(kb.KEY.RIGHT, 0);
scroll(-1);
bt.scroll(-1);
} else if(lastx < -40){
// E.showMessage('left');
//kb.tap(kb.KEY.LEFT, 0);
scroll(1);
} else if(lastx==0 && lasty==0 && holding == false){
// E.showMessage('press');
clickMouse(MouseButton.LEFT);
bt.scroll(1);
}
// Todo re-implement? Seems bit buggy or unnecessary for now.
// else if(lastx==0 && lasty==0 && holding == false){
// // E.showMessage('press');
// bt.clickButton(bt.BUTTON.LEFT);
// }
stopHolding();
lastx = 0;
lasty = 0;
@ -452,20 +378,37 @@ Bangle.on('drag', function(e) {
}
});
function onBtn() {
if (trackPadMode) {
if ((getTime() - bttl < 0.4 && !focusMode)) {
E.showMessage('Pointer');
focusMode = true;
bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT);
} else {
trackPadMode = false;
stopHolding();
drawMain();
if (focusMode) {
bt.tapKey(bt.KEY.F10);
focusMode = false;
}
}
} else {
stopHolding();
clearToSend = true;
trackPadMode = true;
E.showMessage('Mouse');
// Also skip drawing thingy for now.
if (timeoutDraw != -1) {
clearTimeout(timeoutDraw);
timeoutDraw = -1;
}
bttl = getTime();
}
Bangle.buzz();
}
setWatch(onBtn, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat: true});
// Start App
loadSettings();
drawMain();

View File

@ -1,7 +1,7 @@
{
"id": "presentor",
"name": "Presentor",
"version": "0.08",
"version": "0.14",
"description": "Use your Bangle to present!",
"icon": "app.png",
"type": "app",

View File

@ -43,3 +43,5 @@
0.34: Avoid prompting when creating a new file (#3081)
0.35: Handle loading without a settings file (default record setting)
0.36: When recording with 1 second periods, log time with one decimal.
0.37: 1 second periods + gps log => log when gps event is received, not with
setInterval.

View File

@ -2,7 +2,7 @@
"id": "recorder",
"name": "Recorder",
"shortName": "Recorder",
"version": "0.36",
"version": "0.37",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget,clkinfo",

View File

@ -2,7 +2,7 @@
let storageFile; // file for GPS track
let entriesWritten = 0;
let activeRecorders = [];
let writeInterval;
let writeSetup;
let loadSettings = function() {
var settings = require("Storage").readJSON("recorder.json",1)||{};
@ -194,11 +194,14 @@
}
}
let writeOnGPS = function() {writeLog(settings.period);};
// Called by the GPS app to reload settings and decide what to do
let reload = function() {
var settings = loadSettings();
if (writeInterval) clearInterval(writeInterval);
writeInterval = undefined;
if (typeof writeSetup === "number") clearInterval(writeSetup);
writeSetup = undefined;
Bangle.removeListener('GPS', writeOnGPS);
activeRecorders.forEach(rec => rec.stop());
activeRecorders = [];
@ -222,7 +225,12 @@
}
// start recording...
WIDGETS["recorder"].draw();
writeInterval = setInterval(writeLog, settings.period*1000, settings.period);
if (settings.period===1 && settings.record.includes("gps")) {
Bangle.on('GPS', writeOnGPS);
writeSetup = true;
} else {
writeSetup = setInterval(writeLog, settings.period*1000, settings.period);
}
} else {
WIDGETS["recorder"].width = 0;
storageFile = undefined;
@ -230,7 +238,7 @@
}
// add the widget
WIDGETS["recorder"]={area:"tl",width:0,draw:function() {
if (!writeInterval) return;
if (!writeSetup) return;
g.reset().drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2);
activeRecorders.forEach((recorder,i)=>{
recorder.draw(this.x+15+(i>>1)*12, this.y+(i&1)*12);
@ -239,7 +247,7 @@
reload();
Bangle.drawWidgets(); // relayout all widgets
},isRecording:function() {
return !!writeInterval;
return !!writeSetup;
},setRecording:function(isOn, options) {
/* options = {
force : [optional] "append"/"new"/"overwrite" - don't ask, just do what's requested

View File

@ -20,7 +20,7 @@ function readFile(input) {
const jCalData = ICAL.parse(icalText);
const comp = new ICAL.Component(jCalData);
const vtz = comp.getFirstSubcomponent('vtimezone');
const tz = new ICAL.Timezone(vtz);
const tz = vtz != null ? new ICAL.Timezone(vtz) : null;
// Fetch the VEVENT part
comp.getAllSubcomponents('vevent').forEach(vevent => {
@ -73,7 +73,9 @@ function getAlarmDefaults() {
}
function eventToAlarm(event, tz, offsetMs) {
if (tz != null) {
event.startDate.zone = tz;
}
const dateOrig = event.startDate.toJSDate();
const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig;

View File

@ -9,3 +9,4 @@ when fastloading.
0.08: Issue newline before GB commands (solves issue with console.log and ignored commands)
0.09: Don't send the gadgetbridge wake command twice. Once should do since we
issue newline before GB commands.
0.10: Some refactoring to shorten the code.

View File

@ -1,6 +1,6 @@
{
/*
Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}}));
* Bluetooth.println(JSON.stringify({t:"intent", target:"", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], package:"", class:"", mimetype:"", data:"", extra:{someKey:"someValueOrString", anotherKey:"anotherValueOrString",...}}));
*/
let R;
@ -133,6 +133,17 @@ let backToGfx = function() {
The functions for interacting with Android and the Spotify app
*/
let createCommand = function(o) {
return ()=>{
Bluetooth.println("");
Bluetooth.println(JSON.stringify(o));
};
};
let assembleSearchString = function() {
return (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\""));
};
simpleSearch = "";
let simpleSearchTerm = function() { // input a simple search term without tags, overrides search with tags (artist and track)
require("textinput").input({text:simpleSearch}).then(result => {simpleSearch = result;}).then(() => {E.showMenu(searchMenu);});
@ -153,97 +164,44 @@ let albumSearchTerm = function() { // input album to search for
require("textinput").input({text:album}).then(result => {album = result;}).then(() => {E.showMenu(searchMenu);});
};
let searchPlayWOTags = function() {//make a spotify search and play using entered terms
searchString = simpleSearch;
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayWOTags = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:simpleSearch}, flags:["FLAG_ACTIVITY_NEW_TASK"]});
let searchPlayWTags = function() {//make a spotify search and play using entered terms
searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\""));
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayWTags = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:assembleSearchString()}, flags:["FLAG_ACTIVITY_NEW_TASK"]});
let playVreden = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let playVreden = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"]});
let playVredenAlternate = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let playVredenAlternate = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]});
let searchPlayVreden = function() {//Play the track "Vreden" by Sara Parkman via search and play
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayVreden = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]});
let openAlbum = function() {//Play EP "The Blue Room" by Coldplay
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let openAlbum = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]});
let searchPlayAlbum = function() {//Play EP "The Blue Room" by Coldplay via search and play
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayAlbum = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]});
let spotifyWidget = function(action) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"}));
};
let gadgetbridgeWake = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"}));
};
let gadgetbridgeWake = createCommand({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"});
let spotifyPlaylistDW = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDW = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDM1 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM1 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDM2 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM2 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDM3 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM3 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDM4 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM4 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDM5 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM5 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDM6 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM6 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistDD = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDD = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
let spotifyPlaylistRR = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistRR = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]});
// Spotify Remote Menu
let spotifyMenu = {
@ -256,11 +214,14 @@ let spotifyMenu = {
"Exit Spotify Remote" : ()=>{load();}
};
let menuBackFunc = ()=>{
if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();
};
let controlMenu = {
"" : { title : " Controls ",
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();} },
back: menuBackFunc },
"Play" : ()=>{Bangle.musicControl("play");},
"Pause" : ()=>{Bangle.musicControl("pause");},
"Previous" : ()=>{spotifyWidget("PREVIOUS");},
@ -271,32 +232,30 @@ let controlMenu = {
let searchMenu = {
"" : { title : " Search ",
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();} },
"Search term w/o tags" : ()=>{simpleSearchTerm();},
"Execute search and play w/o tags" : ()=>{searchPlayWOTags();},
"Search term w tag \"artist\"" : ()=>{artistSearchTerm();},
"Search term w tag \"track\"" : ()=>{trackSearchTerm();},
"Search term w tag \"album\"" : ()=>{albumSearchTerm();},
"Execute search and play with tags" : ()=>{searchPlayWTags();},
back: menuBackFunc },
"Search term w/o tags" : simpleSearchTerm,
"Execute search and play w/o tags" : searchPlayWOTags,
"Search term w tag \"artist\"" : artistSearchTerm,
"Search term w tag \"track\"" : trackSearchTerm,
"Search term w tag \"album\"" : albumSearchTerm,
"Execute search and play with tags" : searchPlayWTags,
};
let savedMenu = {
"" : { title : " Saved ",
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();} },
"Play Discover Weekly" : ()=>{spotifyPlaylistDW();},
"Play Daily Mix 1" : ()=>{spotifyPlaylistDM1();},
"Play Daily Mix 2" : ()=>{spotifyPlaylistDM2();},
"Play Daily Mix 3" : ()=>{spotifyPlaylistDM3();},
"Play Daily Mix 4" : ()=>{spotifyPlaylistDM4();},
"Play Daily Mix 5" : ()=>{spotifyPlaylistDM5();},
"Play Daily Mix 6" : ()=>{spotifyPlaylistDM6();},
"Play Daily Drive" : ()=>{spotifyPlaylistDD();},
"Play Release Radar" : ()=>{spotifyPlaylistRR();},
"Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();},
"Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();},
"Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();},
back: menuBackFunc },
"Play Discover Weekly" : spotifyPlaylistDW,
"Play Daily Mix 1" : spotifyPlaylistDM1,
"Play Daily Mix 2" : spotifyPlaylistDM2,
"Play Daily Mix 3" : spotifyPlaylistDM3,
"Play Daily Mix 4" : spotifyPlaylistDM4,
"Play Daily Mix 5" : spotifyPlaylistDM5,
"Play Daily Mix 6" : spotifyPlaylistDM6,
"Play Daily Drive" : spotifyPlaylistDD,
"Play Release Radar" : spotifyPlaylistRR,
"Play \"Vreden\" by Sara Parkman via uri-link" : playVreden,
"Open \"The Blue Room\" EP (no autoplay)" : openAlbum,
"Play \"The Blue Room\" EP via search&play" : searchPlayAlbum,
};
Bangle.loadWidgets();

View File

@ -1,7 +1,7 @@
{
"id": "spotrem",
"name": "Remote for Spotify",
"version": "0.09",
"version": "0.10",
"description": "Control spotify on your android device.",
"readme": "README.md",
"type": "app",

View File

@ -2,3 +2,4 @@
0.02: Checks for correct firmware; E.setDST(...) moved to boot.js
0.03: Convert Yes/No On/Off in settings to checkboxes
0.04: Give the boot file the highest priority to ensure it runs before sched (fix #2663)
0.05: Tweaks to ensure Gadgetbridge can't overwrite timezone on 2v19.106 and later

View File

@ -1,15 +1,15 @@
(() => {
if (E.setDST) {
var dstSettings = require('Storage').readJSON('widdst.json',1)||{};
let dstSettings = require('Storage').readJSON('widdst.json',1)||{};
if (dstSettings.has_dst) {
E.setDST(60*dstSettings.dst_size, 60*dstSettings.tz, dstSettings.dst_start.dow_number, dstSettings.dst_start.dow,
dstSettings.dst_start.month, dstSettings.dst_start.day_offset, 60*dstSettings.dst_start.at,
dstSettings.dst_end.dow_number, dstSettings.dst_end.dow, dstSettings.dst_end.month, dstSettings.dst_end.day_offset,
60*dstSettings.dst_end.at);
/* on 2v19.106 and later, E.setTimeZone overwrites E.setDST so we
manually disable E.setTimeZone here to stop Gadgetbridge resetting the timezone */
E.setTimeZone = function(){};
} else {
E.setDST(0,0,0,0,0,0,0,0,0,0,0,0);
}
}
})()

View File

@ -1,6 +1,6 @@
{ "id": "widdst",
"name": "Daylight Saving",
"version":"0.04",
"version":"0.05",
"description": "Widget to set daylight saving rules. Requires Espruino 2v15 or later - see the instructions below for more information.",
"icon": "icon.png",
"type": "widget",

2
apps/widhrzone/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Read maximum HRM from myprofile

View File

@ -2,7 +2,7 @@
"id": "widhrzone",
"name": "Heart rate zone widget",
"shortName": "HRzone widget",
"version": "0.01",
"version": "0.02",
"description": "Widget that displays the current out of five heart rate training zones 1. HEALTH (50-60% of max. HR, Recovery, grey), 2. FAT-B (60-70% of max. HR, burns fat, blue), 3. AROBIC (70-80% of max. HR, Endurance, green), 4. ANAROB (80-90% of max. HR, Speed, yellow), 5. MAX (90-100% of max. HR, red). Only visible when heart rate monitor is active and inside one of the zones. Requires to set the maximum heart rate in settings (if unsure set to 220-age).",
"icon": "widget.png",
"type": "widget",
@ -10,8 +10,7 @@
"supports": ["BANGLEJS","BANGLEJS2"],
"screenshots" : [ { "url":"screenshot.png" } ],
"storage": [
{"name":"widhrzone.wid.js","url":"widget.js"},
{"name":"widhrzone.settings.js","url":"settings.js"}
{"name":"widhrzone.wid.js","url":"widget.js"}
],
"data": [{"name":"widhrzone.json"}]
"dependencies": {"myprofile":"app"}
}

View File

@ -1,26 +0,0 @@
(function(back) {
const CONFIGFILE = "widhrzone.json";
// Load settings
const settings = Object.assign({
maxHrm: 200,
}, require("Storage").readJSON(CONFIGFILE,1) || {});
function writeSettings() {
require('Storage').writeJSON(CONFIGFILE, settings);
}
// Show the menu
E.showMenu({
"" : { "title" : "HRzone widget" },
"< Back" : () => back(),
/*LANG*/'HR max': {
format: v => v,
value: settings.maxHrm,
min: 30, max: 220,
onchange: v => {
settings.maxHrm = v;
writeSettings();
}
},
});
});

View File

@ -1,20 +1,18 @@
(() => {
const config = Object.assign({
maxHrm: 200,
}, require("Storage").readJSON("widhrzone.json",1) || {});
const myprofile = require("Storage").readJSON("myprofile.json",1)||{};
require("FontTeletext5x9Ascii").add(Graphics);
const calczone = (bpm) => {
if (bpm <= config.maxHrm*0.5) {
if (bpm <= myprofile.maxHrm*0.5) {
return 0;
} else if (bpm <= config.maxHrm*0.60) {
} else if (bpm <= myprofile.maxHrm*0.60) {
return 1;
} else if (bpm <= config.maxHrm*0.70) {
} else if (bpm <= myprofile.maxHrm*0.70) {
return 2;
} else if (bpm <= config.maxHrm*0.80) {
} else if (bpm <= myprofile.maxHrm*0.80) {
return 3;
} else if (bpm <= config.maxHrm*0.90) {
} else if (bpm <= myprofile.maxHrm*0.90) {
return 4;
} else { // > 0.9
return 5;

View File

@ -6,3 +6,4 @@
0.06: Darkmode, custom colours, and fix a bug with acting on mylocation changes
0.07: Use default Bangle formatter for booleans
0.08: Better formula for the moon's phase
0.09: Fix variable declaration

View File

@ -1,7 +1,7 @@
{
"id": "widmp",
"name": "Moon Phase",
"version": "0.08",
"version": "0.09",
"description": "Display the current moon phase in blueish (in light mode) or white (in dark mode) for both hemispheres. In the southern hemisphere the 'My Location' app is needed.",
"icon": "widget.png",
"type": "widget",

View File

@ -6,11 +6,11 @@
// https://github.com/deirdreobyrne/LunarPhase
function moonPhase(sec) {
d = (4.847408287988257 + sec/406074.7465115577) % (2.0*Math.PI);
m = (6.245333801867877 + sec/5022682.784840698) % (2.0*Math.PI);
l = (4.456038755040014 + sec/378902.2499653011) % (2.0*Math.PI);
t = d+1.089809730923715e-01 * Math.sin(l)-3.614132757006379e-02 * Math.sin(m)+2.228248661252023e-02 * Math.sin(d+d-l)+1.353592753655652e-02 * Math.sin(d+d)+4.238560208195022e-03 * Math.sin(l+l)+1.961408105275610e-03 * Math.sin(d);
k = (1.0 - Math.cos(t))/2.0;
let d = (4.847408287988257 + sec/406074.7465115577) % (2.0*Math.PI);
let m = (6.245333801867877 + sec/5022682.784840698) % (2.0*Math.PI);
let l = (4.456038755040014 + sec/378902.2499653011) % (2.0*Math.PI);
let t = d+1.089809730923715e-01 * Math.sin(l)-3.614132757006379e-02 * Math.sin(m)+2.228248661252023e-02 * Math.sin(d+d-l)+1.353592753655652e-02 * Math.sin(d+d)+4.238560208195022e-03 * Math.sin(l+l)+1.961408105275610e-03 * Math.sin(d);
let k = (1.0 - Math.cos(t))/2.0;
if ((t >= Math.PI) && (t < 2.0*Math.PI)) {
k = -k;
}
@ -19,7 +19,7 @@
function loadLocation() {
// "mylocation.json" is created by the "My Location" app
location = require("Storage").readJSON("mylocation.json",1)||{"lat":50.1236,"lon":8.6553,"location":"Frankfurt"};
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":50.1236,"lon":8.6553,"location":"Frankfurt"};
southernHemisphere = (location.lat < 0);
}
@ -63,12 +63,13 @@
function draw() {
const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11;
let leftFactor, rightFactor;
loadLocation();
g.reset().setColor(g.theme.bg);
g.fillRect(CenterX - Radius, CenterY - Radius, CenterX + Radius, CenterY + Radius);
millis = (new Date()).getTime();
let millis = (new Date()).getTime();
if ((millis - lastCalculated) >= 7000000) { // if it's more than 7,000 sec since last calculation, re-calculate!
phase = moonPhase(millis/1000);
lastCalculated = millis;

View File

@ -83,16 +83,16 @@
"Connect device\nto add to\nwhitelist": "Connecter le dispositif\nà ajouter à\nliste blanche",
"LCD Timeout": "Temporisation de l'écran LCD",
"LCD Brightness": "Luminosité de l'écran LCD",
"Wake on BTN2": "Wake sur BTN2",
"Wake on BTN3": "Wake sur BTN3",
"Wake on BTN2": "Réveil sur BTN2",
"Wake on BTN3": "Réveil sur BTN3",
"Wake on FaceUp": "Réveillez-vous sur FaceUp",
"Wake on Touch": "Réveil au toucher",
"Twist Threshold": "Seuil de torsion",
"Wake on Twist": "Réveil sur Twist",
"Twist Threshold": "Seuil de rotation",
"Wake on Twist": "Réveil sur rotation",
"Reset to Defaults": "Réinitialisation des valeurs par défaut",
"Utilities": "Utilitaires",
"Flattening battery - this can take hours.\nLong-press button to cancel": "Mise à plat de la batterie - cela peut prendre des heures.\nAppuyez longuement sur le bouton pour annuler",
"Flatten Battery": "Aplatir la batterie",
"Flattening battery - this can take hours.\nLong-press button to cancel": "Décharger la batterie - cela peut prendre des heures.\nAppuyez longuement sur le bouton pour annuler",
"Flatten Battery": "Décharger la batterie",
"Storage": "Stockage",
"Reset Settings": "Réinitialiser les paramètres",
"Log": "Journal de bord",
@ -146,7 +146,7 @@
"Show": "Afficher",
"Hide": "Cacher",
"Messages": "Messages",
"BACK": "BACK",
"BACK": "RETOUR",
"Error in settings": "Erreur dans les paramètres",
"Timer": "Minuterie",
"On": "Sur",
@ -155,7 +155,7 @@
"steps": "étapes",
"Settings": "Paramètres",
"ALARM": "ALARME",
"back": "dos",
"back": "retour",
"Yes": "Oui",
"Steps": "Étapes",
"Year": "Année",