Merge branch 'master' into calculator

master
Frederic R 2020-04-14 10:29:06 +01:00 committed by GitHub
commit f5f1059627
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 798 additions and 75 deletions

View File

@ -7,3 +7,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
* Added optional `README.md` file for apps
* Remove 2v04 version warning, add links in About to official/developer versions
* Fix issue removing an app that was just installed (Fix #253)
* Add `Favourite` functionality

View File

@ -119,7 +119,7 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
"version":"0.12",
"version":"0.13",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"storage": [
@ -915,7 +915,7 @@
{ "id": "marioclock",
"name": "Mario Clock",
"icon": "marioclock.png",
"version":"0.09",
"version":"0.12",
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
"tags": "clock,mario,retro",
"type": "clock",
@ -1148,9 +1148,9 @@
},
{ "id": "batchart",
"name": "Battery Chart",
"shortName":"BatChart",
"shortName":"Battery Chart",
"icon": "app.png",
"version":"0.03",
"version":"0.05",
"description": "A widget and an app for recording and visualizing battery percentage over time.",
"tags": "app,widget,battery,time,record,chart,tool",
"storage": [
@ -1159,7 +1159,49 @@
{"name":"batchart.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "calculator",
{ "id": "nato",
"name": "NATO Alphabet",
"shortName" : "NATOAlphabet",
"icon": "nato.png",
"version":"0.01",
"type": "app",
"description": "Learn the NATO Phonetic alphabet plus some numbers.",
"tags": "app,learn,visual",
"allow_emulator":true,
"storage": [
{"name":"nato.app.js","url":"nato.js"},
{"name":"nato.img","url":"nato-icon.js","evaluate":true}
]
},
{ "id": "numerals",
"name": "Numerals Clock",
"shortName": "Numerals Clock",
"icon": "numerals.png",
"version":"0.01",
"description": "A simple big numerals clock",
"tags": "numerals,clock",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"numerals.app.js","url":"numerals.app.js"},
{"name":"numerals.img","url":"numerals-icon.js","evaluate":true},
{"name":"numerals.settings.js","url":"numerals.settings.js"}
]
},
{ "id": "bledetect",
"name": "BLE Detector",
"shortName":"BLE Detector",
"icon": "bledetect.png",
"version":"0.02",
"description": "Detect BLE devices and show some informations.",
"tags": "app,bluetooth,tool",
"readme": "README.md",
"storage": [
{"name":"bledetect.app.js","url":"bledetect.js"},
{"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
]
},
{ "id": "calculator",
"name": "Calculator",
"shortName":"Calculator",
"icon": "calculator.png",

View File

@ -1,3 +1,5 @@
0.01: New app and widget
0.02: Widget stores data to file (1 dataset/10min)
0.03: Rotate log files once a week.
0.04: chart in the app is now active.
0.05: Display temperature and LCD state in chart

View File

@ -1,20 +1,195 @@
// place your const, vars, functions or classes here
const GraphXZero = 40;
const GraphYZero = 180;
const GraphY100 = 80;
function renderBatteryChart(){
g.drawString("t", 215, 175);
g.drawLine(40,190,40,80);
const GraphMarkerOffset = 5;
const MaxValueCount = 144;
const GraphXMax = GraphXZero + MaxValueCount;
const GraphLcdY = GraphYZero + 10;
// const GraphCompassY = GraphYZero + 16;
// const GraphBluetoothY = GraphYZero + 22;
// const GraphGpsY = GraphYZero + 28;
// const GraphHrmY = GraphYZero + 34;
var Storage = require("Storage");
function renderCoordinateSystem() {
g.setFont("6x8", 1);
g.drawString("%", 39, 70);
g.drawString("100", 15, 75);
g.drawLine(35,80,40,80);
// Left Y axis (Battery)
g.setColor(1, 1, 0);
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
g.drawString("%", 39, GraphY100 - 10);
g.drawString("50", 20,125);
g.drawLine(35,130,40,130);
g.setFontAlign(1, -1, 0);
g.drawString("100", 30, GraphY100 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
g.drawString("0", 25, 175);
g.drawLine(35,180,210,180);
g.drawString("50", 30, GraphYZero - 50 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 130, GraphXZero, 130);
g.drawString("Chart not yet functional", 60, 125);
g.drawString("0", 30, GraphYZero - GraphMarkerOffset);
g.setColor(1,1,1);
g.setFontAlign(1, -1, 0);
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
// Right Y axis (Temperature)
g.setColor(0.4, 0.4, 1);
g.drawLine(GraphXMax, GraphYZero + GraphMarkerOffset, GraphXMax, GraphY100);
g.drawString("°C", GraphXMax + GraphMarkerOffset, GraphY100 - 10);
g.setFontAlign(-1, -1, 0);
g.drawString("20", GraphXMax + 2 * GraphMarkerOffset, GraphYZero - GraphMarkerOffset);
g.drawLine(GraphXMax + GraphMarkerOffset, 130, GraphXMax, 130);
g.drawString("30", GraphXMax + 2 * GraphMarkerOffset, GraphYZero - 50 - GraphMarkerOffset);
g.drawLine(GraphXMax + GraphMarkerOffset, 80, GraphXMax, 80);
g.drawString("40", GraphXMax + 2 * GraphMarkerOffset, GraphY100 - GraphMarkerOffset);
g.setColor(1,1,1);
}
function decrementDay(dayToDecrement) {
return dayToDecrement === 0 ? 6 : dayToDecrement-1;
}
function loadData() {
const startingDay = new Date().getDay();
// Load data for the current day
let logFileName = "bclog" + startingDay;
let dataLines = loadLinesFromFile(MaxValueCount, logFileName);
// Top up to MaxValueCount from previous days as required
let previousDay = decrementDay(startingDay);
while (dataLines.length < MaxValueCount
&& previousDay !== startingDay) {
let topUpLogFileName = "bclog" + previousDay;
let remainingLines = MaxValueCount - dataLines.length;
let topUpLines = loadLinesFromFile(remainingLines, topUpLogFileName);
dataLines = topUpLines.concat(dataLines);
previousDay = decrementDay(previousDay);
}
return dataLines;
}
function loadLinesFromFile(requestedLineCount, fileName) {
let allLines = [];
let returnLines = [];
var readFile = Storage.open(fileName, "r");
while ((nextLine = readFile.readLine())) {
if(nextLine) {
allLines.push(nextLine);
}
}
readFile = null;
if (allLines.length <= 0) return;
let linesToReadCount = Math.min(requestedLineCount, allLines.length);
let startingLineIndex = Math.max(0, allLines.length - requestedLineCount - 1);
for (let i = startingLineIndex; i < linesToReadCount + startingLineIndex; i++) {
if(allLines[i]) {
returnLines.push(allLines[i]);
}
}
allLines = null;
return returnLines;
}
function renderData(dataArray) {
const switchableConsumers = {
none: 0,
lcd: 1,
compass: 2,
bluetooth: 4,
gps: 8,
hrm: 16
};
//const timestampIndex = 0;
const batteryIndex = 1;
const temperatureIndex = 2;
const switchabelsIndex = 3;
var allConsumers = switchableConsumers.none | switchableConsumers.lcd | switchableConsumers.compass | switchableConsumers.bluetooth | switchableConsumers.gps | switchableConsumers.hrm;
for (let i = 0; i < dataArray.length; i++) {
const element = dataArray[i];
var dataInfo = element.split(",");
// Battery percentage
g.setColor(1, 1, 0);
g.setPixel(GraphXZero + i, GraphYZero - parseInt(dataInfo[batteryIndex]));
// Temperature
g.setColor(0.4, 0.4, 1);
let scaledTemp = Math.floor(((parseFloat(dataInfo[temperatureIndex]) * 100) - 2000)/20) + ((((parseFloat(dataInfo[temperatureIndex]) * 100) - 2000) % 100)/25);
g.setPixel(GraphXZero + i, GraphYZero - scaledTemp);
// LCD state
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.lcd == switchableConsumers.lcd) {
g.setColor(1, 1, 1);
g.setFontAlign(1, -1, 0);
g.drawString("LCD", GraphXZero - GraphMarkerOffset, GraphLcdY - 2, true);
g.drawLine(GraphXZero + i, GraphLcdY, GraphXZero + i, GraphLcdY + 1);
}
// // Compass state
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
// g.setColor(0, 1, 0);
// g.setFontAlign(-1, -1, 0);
// g.drawString("Compass", GraphXMax + GraphMarkerOffset, GraphCompassY - 2, true);
// g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1);
// }
// // Bluetooth state
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
// g.setColor(0, 0, 1);
// g.setFontAlign(1, -1, 0);
// g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true);
// g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1);
// }
// // Gps state
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
// g.setColor(0.8, 0.5, 0.24);
// g.setFontAlign(-1, -1, 0);
// g.drawString("GPS", GraphXMax + GraphMarkerOffset, GraphGpsY - 2, true);
// g.drawLine(GraphXZero + i, GraphGpsY, GraphXZero + i, GraphGpsY + 1);
// }
// // Hrm state
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
// g.setColor(1, 0, 0);
// g.setFontAlign(1, -1, 0);
// g.drawString("HRM", GraphXZero - GraphMarkerOffset, GraphHrmY - 2, true);
// g.drawLine(GraphXZero + i, GraphHrmY, GraphXZero + i, GraphHrmY + 1);
// }
}
dataArray = null;
}
function renderBatteryChart() {
renderCoordinateSystem();
let data = loadData();
renderData(data);
data = null;
}
// special function to handle display switch on
@ -22,6 +197,9 @@ Bangle.on('lcdPower', (on) => {
if (on) {
// call your app function here
// If you clear the screen, do Bangle.drawWidgets();
g.clear()
Bangle.loadWidgets();
Bangle.drawWidgets();
renderBatteryChart();
}
});

View File

@ -60,7 +60,6 @@
}
}
// Called by the heart app to reload settings and decide what's
function reload() {
WIDGETS["batchart"].width = 24;

2
apps/bledetect/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Fixed issue with wrong device informations

14
apps/bledetect/README.md Normal file
View File

@ -0,0 +1,14 @@
# BLE Detector
BLE Detector it's an app born for testing purpose that aim to show as informations as possible about near BLE devices.
## Features
BLE Detector shows:
- Device name (if available)
- Received Signal Strength Indication (RSSI)
- Manufacturer
- MAC Address
More informations will coming with future versions.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgJGhGAEKuIxAXXGCoXBGCoXCDCgXDJKYXDGCYUBhAwUFgQwPEogTCGBwNFFYYYNHwoEGJJQlFCIgKCdR4XHJBQNEI6IOFO6IPEDQYGDahoYEa6BJFxBFPJJIuQGAouRGAoWSGAgXTSIoAEgUgL6cCkQACDJCOFGAYWDAAJFLX4gWFGA4sFC40gJQYuHwBEDAQISCMYowEFgoJDCAwYBAwZYEC45AEgIHERAgXMA4i4FC6bPDC4hXFC5B7FC57CHI54XIawgXRVwS/JC5SuDC4wGGC45HBFAQRCAooXIVwYRBAAoXLLIwAFC5IuDGCIuFDAyQLABphKABgwaC6owB"))

View File

@ -0,0 +1,59 @@
let menu = {
"": { "title": "BLE Detector" },
"RE-SCAN": () => scan()
};
function showMainMenu() {
menu["< Back"] = () => load();
return E.showMenu(menu);
}
function showDeviceInfo(device){
const deviceMenu = {
"": { "title": "Device Info" },
"name": {
value: device.name
},
"rssi": {
value: device.rssi
},
"manufacturer": {
value: device.manufacturer
}
};
deviceMenu[device.id] = () => {};
deviceMenu["< Back"] = () => showMainMenu();
return E.showMenu(deviceMenu);
}
function scan() {
menu = {
"": { "title": "BLE Detector" },
"RE-SCAN": () => scan()
};
waitMessage();
NRF.findDevices(devices => {
devices.forEach(device =>{
let deviceName = device.id.substring(0,17);
if (device.name) {
deviceName = device.name;
}
menu[deviceName] = () => showDeviceInfo(device);
});
showMainMenu(menu);
}, { active: true });
}
function waitMessage() {
E.showMenu();
E.showMessage("scanning");
}
scan();
waitMessage();

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -6,4 +6,7 @@
0.06: Performance refactor, and enhanced graphics!
0.07: Swipe right to change between Mario and Toad characters, swipe left to toggle night mode
0.08: Update date panel to be info panel toggling between Date, Battery and Temperature. Add Princes Daisy
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
0.10: Swiping left to enable night-mode now also reduces LCD brightness through 3 levels before returning to day-mode.
0.11: User settings persisted and read to file.
0.12: Add info banner message when phone (dis)connects. Display low-battery warning (<=10%)

View File

@ -8,7 +8,7 @@ Enjoy watching Mario, or one of the other game characters run through a level wh
## Features
* Multiple characters - swipe the screen right to change the character between `Mario`, `Toad`, and `Daisy`
* Night and Day modes - swipe left to toggle mode
* Night and Day modes - swipe left to enter night mode, with 3 levels of darkness before returning to day mode.
* Smooth animation
* Awesome 8-bit style grey-scale graphics
* Mario jumps to change the time, every minute

View File

@ -16,6 +16,8 @@ const is12Hour = settings["12hour"] || false;
// Screen dimensions
let W, H;
// Screen brightness
let brightness = 1;
let intervalRef, displayTimeoutRef = null;
@ -79,6 +81,16 @@ const phone = {
messageType: null,
};
const SETTINGS_FILE = "marioclock.json";
function readSettings() {
return require('Storage').readJSON(SETTINGS_FILE, 1) || {};
}
function writeSettings(newSettings) {
require("Storage").writeJSON(SETTINGS_FILE, newSettings);
}
function phoneOutbound(msg) {
Bluetooth.println(JSON.stringify(msg));
}
@ -164,7 +176,17 @@ function switchCharacter() {
}
function toggleNightMode() {
nightMode = !nightMode;
if (!nightMode) {
nightMode = true;
return;
}
brightness -= 0.30;
if (brightness <= 0) {
brightness = 1;
nightMode = false;
}
Bangle.setLCDBrightness(brightness);
}
function incrementTimer() {
@ -324,16 +346,20 @@ function drawToadFrame(idx, x, y) {
function drawNotice(x, y) {
if (phone.message === null) return;
let img;
switch (phone.messageType) {
case "call":
const callImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
g.drawImage(callImg, characterSprite.x, characterSprite.y - 16);
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
break;
case "notify":
const msgImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
g.drawImage(msgImg, characterSprite.x, characterSprite.y - 16);
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
break;
case "lowBatt":
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INFrgABB4oOEBoQPFBwwDGB0uHAAIOLJRB3OSpApDBoQAHB4INLByANNAwo"));
break;
}
if (img) g.drawImage(img, characterSprite.x, characterSprite.y - 16);
}
function drawCharacter(date, character) {
@ -555,8 +581,39 @@ function startTimers(){
redraw();
}
function loadSettings() {
const settings = readSettings();
if (!settings) return;
if (settings.character) characterSprite.character = settings.character;
if (settings.nightMode) nightMode = settings.nightMode;
if (settings.brightness) {
brightness = settings.brightness;
Bangle.setLCDBrightness(brightness);
}
}
function updateSettings() {
const newSettings = {
character: characterSprite.character,
nightMode: nightMode,
brightness: brightness,
};
writeSettings(newSettings);
}
function checkBatteryLevel() {
if (Bangle.isCharging()) return;
if (E.getBattery() > 10) return;
if (phone.message !== null) return;
phoneNewMessage("lowBatt", "Warning, battery is low");
}
// Main
function init() {
loadSettings();
clearInterval();
// Initialise display
@ -606,23 +663,31 @@ function init() {
default:
toggleNightMode();
}
updateSettings();
});
// Phone connectivity
try { NRF.wake(); } catch (e) {}
NRF.on('disconnect', () => Bangle.buzz());
NRF.on('disconnect', () => {
phoneNewMessage(null, "Phone disconnected");
});
NRF.on('connect', () => {
setTimeout(() => {
phoneOutbound({ t: "status", bat: E.getBattery() });
}, ONE_SECOND * 2);
Bangle.buzz();
phoneNewMessage(null, "Phone connected");
});
GB = (evt) => phoneInbound(evt);
startTimers();
setInterval(checkBatteryLevel, ONE_SECOND * 60 * 10);
checkBatteryLevel();
}
// Initialise!
init()
init();

1
apps/nato/changelog.txt Normal file
View File

@ -0,0 +1 @@
0.01: New App!

1
apps/nato/nato-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgFCiIABiAGFiINJAAUS///CAgGEgMT//zBoYXFmIiCC40fEooXF+QXJn4lCC5ARDC4oFC//xMAoXDJAQXFBgY9DC4wKCC4p2CPA4XDCQQXEOwXxPA4XBEQJICC4p2BmICCC44KBJAIXEiIJBkMvPAwXCWgYXFAgQMBPAoXCBwUxC4jtDeI4XDJAQXDFYXxHAoXGJAYXDLYPykUieIwXDJAYXDG4IAEPAgXCRgJICPYoAEPAgXDZ4TcDmYXGMAgXDUAZiEPwIABCALEBC5BZC+YQCRwRsEC45ID+S5BCAkBEYJ4DC4hID+IbCIAYjCCIYXGEgMxXoJwEgI3CA4JQDAAwaBmQGDFIQ3CC5UzkSLBdwIIDmYXCWY4jBCAJBCPYQ0EC5bXGkLuDC5QtEAAXzPoZMCmZwB+YFCbYkykQFCVoZMDWALnDQwRjDeoZIDZAgJCWwYeBFATWFC5LuHawgXKdwyJDD4YXIOAMzH4gICmIXKEwQXXkQXFKAKQFC85HNO64XDU44XMX48Sa5zvCmJICA4YXLE4fziIACJ4PyM4gXHCAQwBCwI2GC5JADAApGFC5ERmYWFFwwXHDARJCMgYWFB4MTmYiFLgMjCwMyiIuGE4QABNIyPDBQgA=="))

106
apps/nato/nato.js Normal file
View File

@ -0,0 +1,106 @@
// Teach a user the NATO Phonetic Alphabet + numbers
// Based on the Morse Code app
const FONT_NAME = 'Vector12';
const FONT_SIZE = 80;
const SCREEN_PIXELS = 240;
const UNIT = 100;
const NATO_MAP = {
A: 'ALFA',
B: 'BRAVO',
C: 'CHARLIE',
D: 'DELTA',
E: 'ECHO',
F: 'FOXTROT',
G: 'GOLF',
H: 'HOTEL',
I: 'INDIA',
J: 'JULIETT',
K: 'KILO',
L: 'LIMA',
M: 'MIKE',
N: 'NOVEMBER',
O: 'OSCAR',
P: 'PAPA',
Q: 'QUEBEC',
R: 'ROMEO',
S: 'SIERRA',
T: 'TANGO',
U: 'UNIFORM',
V: 'VICTOR',
W: 'WHISKEY',
X: 'X-RAY',
Y: 'YANKEE',
Z: 'ZULU',
'0': 'ZE-RO',
'1': 'WUN',
'2': 'TOO',
'3': 'TREE',
'4': 'FOW-ER',
'5': 'FIFE',
'6': 'SIX',
'7': 'SEV-EN',
'8': 'AIT',
'9': 'NIN-ER',
};
let INDEX = 0;
let showLetter = true;
const writeText = (txt) => {
g.clear();
g.setFont(FONT_NAME, FONT_SIZE);
var width = g.stringWidth(txt);
// Fit text to screen
var fontFix = FONT_SIZE;
while(width > SCREEN_PIXELS-10){
fontFix--;
g.setFont(FONT_NAME, fontFix);
width = g.stringWidth(txt);
}
g.drawString(txt, (SCREEN_PIXELS / 2) - (width / 2), SCREEN_PIXELS / 2);
};
const writeLetter = () => {
writeText(Object.keys(NATO_MAP)[INDEX]);
};
const writeCode = () => {
writeText(NATO_MAP[Object.keys(NATO_MAP)[INDEX]]);
};
const toggle = () => {
showLetter = !showLetter;
if(showLetter){
writeLetter();
}else {
writeCode();
}
};
// Bootstrapping
g.clear();
g.setFont(FONT_NAME, FONT_SIZE);
g.setColor(0, 1, 0);
g.setFontAlign(-1, 0, 0);
const step = (positive) => () => {
if (positive) {
INDEX = INDEX + 1;
if (INDEX > Object.keys(NATO_MAP).length - 1) INDEX = 0;
} else {
INDEX = INDEX - 1;
if (INDEX < 0) INDEX = Object.keys(NATO_MAP).length - 1;
}
showLetter = true; // for toggle()
writeLetter();
};
writeLetter();
// Press the middle button to see the NATO Phonetic wording
setWatch(toggle, BTN2, { repeat: true });
// Allow user to switch between letters
setWatch(step(true), BTN1, { repeat: true });
setWatch(step(false), BTN3, { repeat: true });

BIN
apps/nato/nato.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

1
apps/numerals/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

17
apps/numerals/README.md Normal file
View File

@ -0,0 +1,17 @@
# Numerals Clock
This is a simple big numerals clock.
Settings can be accessed through the app/widget settings menu of the Bangle.js
## Settings available
### color:
* rnd - shows numerals in different color combinations every time the watches wakes
* r/g - red/green
* y/w - yellow/white
* o/c - orange/cyan
* b/y - blue/yellow'ish
### draw mode
* fill - fill numerals
* frame - only shows outline of numerals

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/ABMBzIADyAJIAAkQBoMZBIoXCBIwADyIkIGAIuKGAQkIBJIwEEKQANC/4XWR58RiIHFWpAXFe4QRFcpAXFewQRFcxAXEFwQwGA4QXKiAXDGAgX/C/4X/C/4X/C7uQCwcBBwYXNBwYuEC54wCFwgXPzMRiIHFC54AHC/4XiCAoXRhIHDyK3GAAwOBJA0QG45VGC4YwCD4YwKFwgABcgIfEAwIAHBwgA/AAgA=="))

View File

@ -0,0 +1,93 @@
/**
* Bangle.js Numerals Clock
*
* + Original Author: Raik M. https://github.com/ps-igel
* + Created: April 2020
* + see README.md for details
*/
var numerals = {
0:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,9,9,1],[30,21,61,21,69,29,69,61,61,69,30,69,22,61,22,29,30,21]],
1:[[59,1,82,1,90,9,90,82,82,90,73,90,65,82,65,27,59,27,51,19,51,9,59,1]],
2:[[9,1,82,1,90,9,90,47,82,55,21,55,21,64,82,64,90,72,90,82,82,90,9,90,1,82,1,43,9,35,70,35,70,25,9,25,1,17,1,9,9,1]],
3:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,74,9,66,70,66,70,57,9,57,1,49,1,41,9,33,70,33,70,25,9,25,1,17,1,9,9,1]],
4:[[9,1,14,1,22,9,22,34,69,34,69,9,77,1,82,1,90,9,90,82,82,90,78,90,70,82,70,55,9,55,1,47,1,9,9,1]],
5:[[9,1,82,1,90,9,90,17,82,25,21,25,21,35,82,35,90,43,90,82,82,90,9,90,1,82,1,72,9,64,71,64,71,55,9,55,1,47,1,9,9,1]],
6:[[9,1,82,1,90,9,90,14,82,22,22,22,22,36,82,36,90,44,90,82,82,90,9,90,1,82,1,9,9,1],[22,55,69,55,69,69,22,69,22,55]],
7:[[9,1,82,1,90,9,90,15,15,90,9,90,1,82,1,76,54,23,9,23,1,15,1,9,9,1]],
8:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,9,9,1],[22,22,69,22,69,36,22,36,22,22],[22,55,69,55,69,69,22,69,22,55]],
9:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,77,9,69,69,69,69,55,9,55,1,47,1,9,9,1],[22,22,69,22,69,36,22,36,22,22]],
};
var _hCol = ["#ff5555","#ffff00","#FF9901","#2F00FF"];
var _mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"];
var _rCol = 0;
var interval = 0;
const REFRESH_RATE = 10E3;
function translate(tx, ty, p) {
return p.map((x, i)=> x+((i%2)?ty:tx));
}
function fill(poly){
return g.fillPoly(poly);
}
function frame(poly){
return g.drawPoly(poly);
}
let settings = require('Storage').readJSON('numerals.json',1);
if (!settings) {
settings = {
color: 0,
drawMode: "fill"
};
}
function drawNum(num,col,x,y,func){
g.setColor(col);
let tx = x*100+35;
let ty = y*100+35;
for (let i=0;i<numerals[num].length;i++){
if (i>0) g.setColor((func==fill)?"#000000":col);
func(translate(tx, ty,numerals[num][i]));
}
}
function draw(drawMode){
let d = new Date();
let h1 = Math.floor(d.getHours()/10);
let h2 = d.getHours()%10;
let m1 = Math.floor(d.getMinutes()/10);
let m2 = d.getMinutes()%10;
g.clearRect(0,24,240,240);
drawNum(h1,_hCol[_rCol],0,0,eval(drawMode));
drawNum(h2,_hCol[_rCol],1,0,eval(drawMode));
drawNum(m1,_mCol[_rCol],0,1,eval(drawMode));
drawNum(m2,_mCol[_rCol],1,1,eval(drawMode));
}
Bangle.setLCDMode();
clearWatch();
setWatch(Bangle.showLauncher, BTN1, {repeat:false,edge:"falling"});
g.clear();
clearInterval();
if (settings.color>0) _rCol=settings.color-1;
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
draw(settings.drawMode);
Bangle.on('lcdPower', function(on) {
if (on) {
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
draw(settings.drawMode);
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
}else
{
clearInterval(interval);
}
});
Bangle.loadWidgets();
Bangle.drawWidgets();

BIN
apps/numerals/numerals.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,33 @@
(function(back) {
function updateSettings() {
storage.write('numerals.json', numeralsSettings);
};
function resetSettings() {
numeralsSettings = {
color: 0,
drawMode: "fill"
};
updateSettings();
}
let numeralsSettings = storage.readJSON('numerals.json',1);
if (!numeralsSettings) resetSettings();
let dm = ["fill","frame"];
let col = ["rnd","r/g","y/w","o/c","b/y"]
var menu={
"" : { "title":"Numerals"},
"Colors": {
value: 0|numeralsSettings.color,
min:0,max:4,
format: v=>col[v],
onchange: v=> { numeralsSettings.color=v; updateSettings();}
},
"Draw mode": {
value: 0|dm.indexOf(numeralsSettings.drawMode),
min:0,max:1,
format: v=>dm[v],
onchange: v=> { numeralsSettings.drawMode=dm[v]; updateSettings();}
},
"< back": back
};
E.showMenu(menu);
})

View File

@ -12,3 +12,6 @@
0.12: Fix memory leak (#206)
Bring App settings nearer the top
Move LCD Timeout to wakeup menu
0.13: Fix memory leak for App settings
Make capitalization more consistent
Move LCD Brightness menu into more general LCD menu

View File

@ -64,7 +64,7 @@ function showMainMenu() {
const mainmenu = {
'': { 'title': 'Settings' },
'Make Connectable': ()=>makeConnectable(),
'App/widget settings': ()=>showAppSettingsMenu(),
'App/Widget Settings': ()=>showAppSettingsMenu(),
'BLE': {
value: settings.ble,
format: boolFormat,
@ -81,7 +81,7 @@ function showMainMenu() {
updateSettings();
}
},
'Debug info': {
'Debug Info': {
value: settings.log,
format: v => v ? "Show" : "Hide",
onchange: () => {
@ -89,17 +89,6 @@ function showMainMenu() {
updateSettings();
}
},
'LCD Brightness': {
value: settings.brightness,
min: 0.1,
max: 1,
step: 0.1,
onchange: v => {
settings.brightness = v || 1;
updateSettings();
Bangle.setLCDBrightness(settings.brightness);
}
},
'Beep': {
value: 0 | beepV.indexOf(settings.beep),
min: 0, max: 2,
@ -134,7 +123,7 @@ function showMainMenu() {
}
},
'Set Time': ()=>showSetTimeMenu(),
'LCD Wake-Up': ()=>showWakeUpMenu(),
'LCD': ()=>showLCDMenu(),
'Reset Settings': ()=>showResetMenu(),
'Turn Off': ()=>Bangle.off(),
'< Back': ()=>load()
@ -142,10 +131,21 @@ function showMainMenu() {
return E.showMenu(mainmenu);
}
function showWakeUpMenu() {
const wakeUpMenu = {
'': { 'title': 'LCD Wake-Up' },
function showLCDMenu() {
const lcdMenu = {
'': { 'title': 'LCD' },
'< Back': ()=>showMainMenu(),
'LCD Brightness': {
value: settings.brightness,
min: 0.1,
max: 1,
step: 0.1,
onchange: v => {
settings.brightness = v || 1;
updateSettings();
Bangle.setLCDBrightness(settings.brightness);
}
},
'LCD Timeout': {
value: settings.timeout,
min: 0,
@ -157,7 +157,7 @@ function showWakeUpMenu() {
Bangle.setLCDTimeout(settings.timeout);
}
},
'Wake On BTN1': {
'Wake on BTN1': {
value: settings.options.wakeOnBTN1,
format: boolFormat,
onchange: () => {
@ -165,7 +165,7 @@ function showWakeUpMenu() {
updateOptions();
}
},
'Wake On BTN2': {
'Wake on BTN2': {
value: settings.options.wakeOnBTN2,
format: boolFormat,
onchange: () => {
@ -173,7 +173,7 @@ function showWakeUpMenu() {
updateOptions();
}
},
'Wake On BTN3': {
'Wake on BTN3': {
value: settings.options.wakeOnBTN3,
format: boolFormat,
onchange: () => {
@ -197,7 +197,7 @@ function showWakeUpMenu() {
updateOptions();
}
},
'Wake On Twist': {
'Wake on Twist': {
value: settings.options.wakeOnTwist,
format: boolFormat,
onchange: () => {
@ -236,7 +236,7 @@ function showWakeUpMenu() {
}
}
}
return E.showMenu(wakeUpMenu)
return E.showMenu(lcdMenu)
}
function showLocaleMenu() {
@ -450,7 +450,7 @@ function showAppSettings(app) {
}
try {
// pass showAppSettingsMenu as "back" argument
appSettings(showAppSettingsMenu);
appSettings(()=>showAppSettingsMenu());
} catch (e) {
console.log(`${app.name} settings error:`, e)
return showError('Error in settings');

View File

@ -37,7 +37,13 @@ try{
ERROR("apps.json not valid JSON");
}
apps.forEach((app,addIdx) => {
const APP_KEYS = [
'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
'sortorder', 'readme', 'custom', 'interface', 'storage', 'allow_emulator',
];
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate'];
apps.forEach((app,appIdx) => {
if (!app.id) ERROR(`App ${appIdx} has no id`);
//console.log(`Checking ${app.id}...`);
var appDir = APPSDIR+app.id+"/";
@ -105,9 +111,15 @@ apps.forEach((app,addIdx) => {
ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`);
}
}
for (const key in file) {
if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`);
}
});
//console.log(fileNames);
if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`);
if (isApp && !fileNames.includes(app.id+".img")) ERROR(`App ${app.id} has no JS icon`);
if (app.type=="widget" && !fileNames.includes(app.id+".wid.js")) ERROR(`Widget ${app.id} has no entrypoint`);
for (const key in app) {
if (!APP_KEYS.includes(key)) ERROR(`App ${app.id} has unknown key ${key}`);
}
});

View File

@ -96,6 +96,7 @@
<label class="chip" filterid="widget">Widgets</label>
<label class="chip" filterid="bluetooth">Bluetooth</label>
<label class="chip" filterid="outdoors">Outdoors</label>
<label class="chip" filterid="favourites">Favourites</label>
</div>
<div class="panel">
<div class="panel-header">
@ -134,7 +135,8 @@
<h3>Utilities</h3>
<p><button class="btn" id="settime">Set Bangle.js Time</button>
<button class="btn" id="removeall">Remove all Apps</button>
<button class="btn" id="installdefault">Install default apps</button></p>
<button class="btn" id="installdefault">Install default apps</button>
<button class="btn" id="installfavourite">Install favourite apps</button></p>
</div>
</div>

View File

@ -1,6 +1,8 @@
var appJSON = []; // List of apps and info from apps.json
var appsInstalled = []; // list of app JSON
var files = []; // list of files on Bangle
var favourites = []; // list of user favourite app
const FAVOURITE = "favouriteapps.json";
httpGet("apps.json").then(apps=>{
try {
@ -18,7 +20,7 @@ httpGet("apps.json").then(apps=>{
function showChangeLog(appid) {
var app = appNameToApp(appid);
function show(contents) {
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});;
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});
}
httpGet(`apps/${appid}/ChangeLog`).
then(show).catch(()=>show("No Change Log available"));
@ -142,6 +144,20 @@ function handleAppInterface(app) {
});
}
function handleAppFavourite(favourite, app){
if (favourite) {
favourites = favourites.concat([app.id]);
} else {
if ([ "boot","setting"].includes(app.id)) {
showToast(app.name + ' is required, can\'t remove it' , 'warning');
}else {
favourites = favourites.filter(e => e != app.id);
}
}
localStorage.setItem("favouriteapps.json", JSON.stringify(favourites));
refreshLibrary();
}
// =========================================== Top Navigation
function showTab(tabname) {
htmlToArray(document.querySelectorAll("#tab-navigate .tab-item")).forEach(tab => {
@ -156,7 +172,7 @@ function showTab(tabname) {
// =========================================== Library
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value)
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value);
var hash = window.location.hash ? window.location.hash.slice(1) : '';
var activeFilter = !!~chips.indexOf(hash) ? hash : '';
@ -165,27 +181,34 @@ var currentSearch = '';
function refreshFilter(){
var filtersContainer = document.querySelector("#librarycontainer .filter-nav");
filtersContainer.querySelector('.active').classList.remove('active');
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active')
else filtersContainer.querySelector('.chip[filterid]').classList.add('active')
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active');
else filtersContainer.querySelector('.chip[filterid]').classList.add('active');
}
function refreshLibrary() {
var panelbody = document.querySelector("#librarycontainer .panel-body");
var visibleApps = appJSON;
if (activeFilter) {
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
if ( activeFilter == "favourites" ) {
visibleApps = visibleApps.filter(app => app.id && (favourites.filter( e => e == app.id).length));
}else{
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
}
}
if (currentSearch) {
visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch));
}
favourites = (localStorage.getItem(FAVOURITE)) === null ? JSON.parse('["boot","launch","setting"]') : JSON.parse(localStorage.getItem("favouriteapps.json"));
panelbody.innerHTML = visibleApps.map((app,idx) => {
var appInstalled = appsInstalled.find(a=>a.id==app.id);
var version = getVersionInfo(app, appInstalled);
var versionInfo = version.text;
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
var readme = `<a href="#" onclick="showReadme('${app.id}')">Read more...</a>`;
var favourite = favourites.find(e => e == app.id);
return `<div class="tile column col-6 col-sm-12 col-xs-12">
<div class="tile-icon">
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure><br/>
@ -195,7 +218,8 @@ function refreshLibrary() {
<p class="tile-subtitle">${escapeHtml(app.description)}${app.readme?`<br/>${readme}`:""}</p>
<a href="https://github.com/espruino/BangleApps/tree/master/apps/${app.id}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
</div>
<div class="tile-action">
<div class="tile-action">
<button class="btn btn-link btn-action btn-lg ${!app.custom?"text-error":"d-hide"}" appid="${app.id}" title="Favorite"><i class="icon"></i>${favourite?"&#x2665;":"&#x2661;"}</button>
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
<button class="btn btn-link btn-action btn-lg ${app.allow_emulator?"":"d-hide"}" appid="${app.id}" title="Try in Emulator"><i class="icon icon-share"></i></button>
<button class="btn btn-link btn-action btn-lg ${version.canUpdate?"":"d-hide"}" appid="${app.id}" title="Update App"><i class="icon icon-refresh"></i></button>
@ -232,7 +256,7 @@ function refreshLibrary() {
// upload
icon.classList.remove("icon-upload");
icon.classList.add("loading");
uploadApp(app)
uploadApp(app);
} else if (icon.classList.contains("icon-menu")) {
// custom HTML update
icon.classList.remove("icon-menu");
@ -250,6 +274,10 @@ function refreshLibrary() {
updateApp(app);
} else if (icon.classList.contains("icon-download")) {
handleAppInterface(app);
} else if ( button.innerText == String.fromCharCode(0x2661)) {
handleAppFavourite(true, app);
} else if ( button.innerText == String.fromCharCode(0x2665) ) {
handleAppFavourite(false, app);
}
});
});
@ -262,17 +290,17 @@ refreshLibrary();
function uploadApp(app) {
return getInstalledApps().then(()=>{
if (appsInstalled.some(i => i.id === app.id)) {
return updateApp(app)
return updateApp(app);
}
Comms.uploadApp(app).then((appJSON) => {
Progress.hide({ sticky: true })
Progress.hide({ sticky: true });
if (appJSON) {
appsInstalled.push(appJSON)
appsInstalled.push(appJSON);
}
showToast(app.name + ' Uploaded!', 'success')
showToast(app.name + ' Uploaded!', 'success');
}).catch(err => {
Progress.hide({ sticky: true })
showToast('Upload failed, ' + err, 'error')
Progress.hide({ sticky: true });
showToast('Upload failed, ' + err, 'error');
}).finally(()=>{
refreshMyApps();
refreshLibrary();
@ -286,8 +314,8 @@ function removeApp(app) {
return showPrompt("Delete","Really remove '"+app.name+"'?").then(() => {
return getInstalledApps().then(()=>{
// a = from appid.info, app = from apps.json
return Comms.removeApp(appsInstalled.find(a => a.id === app.id))
})
return Comms.removeApp(appsInstalled.find(a => a.id === app.id));
});
}).then(()=>{
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
showToast(app.name+" removed successfully","success");
@ -315,13 +343,13 @@ function updateApp(app) {
if (app.custom) return customApp(app);
return getInstalledApps().then(() => {
// a = from appid.info, app = from apps.json
let remove = appsInstalled.find(a => a.id === app.id)
let remove = appsInstalled.find(a => a.id === app.id);
// no need to remove files which will be overwritten anyway
remove.files = remove.files.split(',')
.filter(f => f !== app.id + '.info')
.filter(f => !app.storage.some(s => s.name === f))
.join(',')
return Comms.removeApp(remove)
.join(',');
return Comms.removeApp(remove);
}).then(()=>{
showToast(`Updating ${app.name}...`);
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
@ -397,7 +425,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
// check icon to figure out what we should do
if (icon.classList.contains("icon-delete")) removeApp(app);
if (icon.classList.contains("icon-refresh")) updateApp(app);
if (icon.classList.contains("icon-download")) handleAppInterface(app)
if (icon.classList.contains("icon-download")) handleAppInterface(app);
});
});
}
@ -405,7 +433,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
let haveInstalledApps = false;
function getInstalledApps(refresh) {
if (haveInstalledApps && !refresh) {
return Promise.resolve(appsInstalled)
return Promise.resolve(appsInstalled);
}
showLoadingIndicator("myappscontainer");
// Get apps and files
@ -453,7 +481,7 @@ filtersContainer.addEventListener('click', ({ target }) => {
activeFilter = target.getAttribute('filterid') || '';
refreshFilter();
refreshLibrary();
window.location.hash = activeFilter
window.location.hash = activeFilter;
});
var librarySearchInput = document.querySelector("#searchform input");
@ -526,7 +554,7 @@ document.getElementById("installdefault").addEventListener("click",event=>{
upload();
}).catch(function() {
Progress.hide({sticky:true});
reject()
reject();
});
}
upload();
@ -541,3 +569,48 @@ document.getElementById("installdefault").addEventListener("click",event=>{
showToast("App Install failed, "+err,"error");
});
});
// Install all favoutrie apps in one go
document.getElementById("installfavourite").addEventListener("click",event=>{
var defaultApps, appCount;
asyncLocalStorage.getItem(FAVOURITE).then(json=>{
defaultApps = JSON.parse(json);
defaultApps = defaultApps.map( appid => appJSON.find(app=>app.id==appid) );
if (defaultApps.some(x=>x===undefined))
throw "Not all apps found";
appCount = defaultApps.length;
return showPrompt("Install Defaults","Remove everything and install favourite apps?");
}).then(() => {
return Comms.removeAllApps();
}).then(()=>{
Progress.hide({sticky:true});
appsInstalled = [];
showToast(`Existing apps removed. Installing ${appCount} apps...`);
return new Promise((resolve,reject) => {
function upload() {
var app = defaultApps.shift();
if (app===undefined) return resolve();
Progress.show({title:`${app.name} (${appCount-defaultApps.length}/${appCount})`,sticky:true});
Comms.uploadApp(app,"skip_reset").then((appJSON) => {
Progress.hide({sticky:true});
if (appJSON) appsInstalled.push(appJSON);
showToast(`(${appCount-defaultApps.length}/${appCount}) ${app.name} Uploaded`);
upload();
}).catch(function() {
Progress.hide({sticky:true});
reject();
});
}
upload();
});
}).then(()=>{
return Comms.setTime();
}).then(()=>{
showToast("Favourites apps successfully installed!","success");
return getInstalledApps(true);
}).catch(err=>{
Progress.hide({sticky:true});
showToast("App Install failed, "+err,"error");
});
});

View File

@ -86,6 +86,7 @@ function showToast(message, type) {
var style = "toast-primary";
if (type=="success") style = "toast-success";
else if (type=="error") style = "toast-error";
else if (type=="warning") style = "toast-warning";
else if (type!==undefined) console.log("showToast: unknown toast "+type);
var toastcontainer = document.getElementById("toastcontainer");
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`);

View File

@ -67,3 +67,16 @@ function getVersionInfo(appListing, appInstalled) {
canUpdate : canUpdate
}
}
const asyncLocalStorage = {
setItem: function (key, value) {
return Promise.resolve().then(function () {
localStorage.setItem(key, value);
});
},
getItem: function (key) {
return Promise.resolve().then(function () {
return localStorage.getItem(key);
});
}
};