Merge pull request #1 from espruino/master

Updating to latest version
pull/470/head
Richard Hopkins 2020-05-17 17:41:37 +01:00 committed by GitHub
commit e975bad1ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 976 additions and 180 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ node_modules
package-lock.json package-lock.json
.DS_Store .DS_Store
*.js.bak *.js.bak
appdates.csv

View File

@ -14,3 +14,7 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
* Added espruinotools.js for pretokenisation * Added espruinotools.js for pretokenisation
* Included image and compression tools in repo * Included image and compression tools in repo
* Added better upload of large files (incl. compression) * Added better upload of large files (incl. compression)
* URL fetch is now async
* Adding '#search' after the URL (when not the name of a 'filter' chip) will set up search for that term
* If `bin/pre-publish.sh` has been run and recent.csv created, add 'Sort By' chip
* New 'espruinotools' which fixes pretokenise issue when ID follows ID (fix #416)

View File

@ -344,6 +344,9 @@ that handles configuring the app.
When the app settings are opened, this function is called with one When the app settings are opened, this function is called with one
argument, `back`: a callback to return to the settings menu. argument, `back`: a callback to return to the settings menu.
Usually it will save any information in `app.json` where `app` is the name
of your app - so you should change the example accordingly.
Example `settings.js` Example `settings.js`
```js ```js
// make sure to enclose the function in parentheses // make sure to enclose the function in parentheses

View File

@ -2,7 +2,7 @@
{ "id": "boot", { "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"icon": "bootloader.png", "icon": "bootloader.png",
"version":"0.16", "version":"0.17",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"tags": "tool,system", "tags": "tool,system",
"type":"bootloader", "type":"bootloader",
@ -78,7 +78,7 @@
{ "id": "welcome", { "id": "welcome",
"name": "Welcome", "name": "Welcome",
"icon": "app.png", "icon": "app.png",
"version":"0.08", "version":"0.09",
"description": "Appears at first boot and explains how to use Bangle.js", "description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome", "tags": "start,welcome",
"allow_emulator":true, "allow_emulator":true,
@ -108,7 +108,7 @@
{ "id": "mclock", { "id": "mclock",
"name": "Morphing Clock", "name": "Morphing Clock",
"icon": "clock-morphing.png", "icon": "clock-morphing.png",
"version":"0.05", "version":"0.06",
"description": "7 segment clock that morphs between minutes and hours", "description": "7 segment clock that morphs between minutes and hours",
"tags": "clock", "tags": "clock",
"type":"clock", "type":"clock",
@ -168,7 +168,7 @@
"name": "Image background clock", "name": "Image background clock",
"shortName":"Image Clock", "shortName":"Image Clock",
"icon": "app.png", "icon": "app.png",
"version":"0.05", "version":"0.06",
"description": "A clock with an image as a background", "description": "A clock with an image as a background",
"tags": "clock", "tags": "clock",
"type" : "clock", "type" : "clock",
@ -589,7 +589,7 @@
"id": "ncstart", "id": "ncstart",
"name": "NCEU Startup", "name": "NCEU Startup",
"icon": "start.png", "icon": "start.png",
"version":"0.05", "version":"0.06",
"description": "NodeConfEU 2019 'First Start' Sequence", "description": "NodeConfEU 2019 'First Start' Sequence",
"tags": "start,welcome", "tags": "start,welcome",
"storage": [ "storage": [
@ -1272,7 +1272,7 @@
"name": "Battery Chart", "name": "Battery Chart",
"shortName":"Battery Chart", "shortName":"Battery Chart",
"icon": "app.png", "icon": "app.png",
"version":"0.09", "version":"0.10",
"readme": "README.md", "readme": "README.md",
"description": "A widget and an app for recording and visualizing battery percentage over time.", "description": "A widget and an app for recording and visualizing battery percentage over time.",
"tags": "app,widget,battery,time,record,chart,tool", "tags": "app,widget,battery,time,record,chart,tool",
@ -1421,7 +1421,7 @@
"id": "metronome", "id": "metronome",
"name": "Metronome", "name": "Metronome",
"icon": "metronome_icon.png", "icon": "metronome_icon.png",
"version": "0.04", "version": "0.05",
"readme": "README.md", "readme": "README.md",
"description": "Makes the watch blinking and vibrating with a given rate", "description": "Makes the watch blinking and vibrating with a given rate",
"tags": "tool", "tags": "tool",
@ -1435,7 +1435,8 @@
"name": "metronome.img", "name": "metronome.img",
"url": "metronome-icon.js", "url": "metronome-icon.js",
"evaluate": true "evaluate": true
} },
{"name":"metronome.settings.js","url":"settings.js"}
] ]
}, },
{ "id": "blackjack", { "id": "blackjack",
@ -1469,7 +1470,7 @@
"name": "Round clock with seconds, minutes and date", "name": "Round clock with seconds, minutes and date",
"shortName":"Round Clock", "shortName":"Round Clock",
"icon": "app.png", "icon": "app.png",
"version":"0.02", "version":"0.03",
"description": "Designed round clock with ticks for minutes and seconds and heart rate indication", "description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
"tags": "clock", "tags": "clock",
"type": "clock", "type": "clock",
@ -1572,7 +1573,7 @@
"id": "largeclock", "id": "largeclock",
"name": "Large Clock", "name": "Large Clock",
"icon": "largeclock.png", "icon": "largeclock.png",
"version": "0.02", "version": "0.03",
"description": "A readable and informational digital watch, with date, seconds and moon phase", "description": "A readable and informational digital watch, with date, seconds and moon phase",
"readme": "README.md", "readme": "README.md",
"tags": "clock", "tags": "clock",
@ -1591,12 +1592,10 @@
{ {
"name": "largeclock.settings.js", "name": "largeclock.settings.js",
"url": "settings.js" "url": "settings.js"
},
{
"name": "largeclock.json",
"url": "largeclock.json",
"evaluate": true
} }
],
"data": [
{"name":"largeclock.json"}
] ]
}, },
{ "id": "smtswch", { "id": "smtswch",
@ -1617,6 +1616,18 @@
{"name":"switch-off.img","url":"switch-off.js","evaluate":true} {"name":"switch-off.img","url":"switch-off.js","evaluate":true}
] ]
}, },
{ "id": "miplant",
"name": "Xiaomi Plant Sensor",
"shortName":"Mi Plant",
"icon": "app.png",
"version":"0.01",
"description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors",
"tags": "xiaomi,mi,plant,ble,bluetooth",
"storage": [
{"name":"miplant.app.js","url":"app.js"},
{"name":"miplant.img","url":"app-icon.js","evaluate":true}
]
},
{ {
"id": "simpletimer", "id": "simpletimer",
"name": "Timer", "name": "Timer",
@ -1708,7 +1719,46 @@
{ "name": "gallifr.settings.js", "url": "settings.js" } { "name": "gallifr.settings.js", "url": "settings.js" }
], ],
"data": [ "data": [
{"name":"app.json"} {"name":"gallifr.json"}
] ]
},
{ "id": "rndmclk",
"name": "Random Clock Loader",
"icon": "rndmclk.png",
"version":"0.02",
"description": "Load a different clock whenever the LCD is switched on.",
"readme": "README.md",
"tags": "widget,clock",
"type":"widget",
"storage": [
{"name":"rndmclk.wid.js","url":"widget.js"}
]
},
{ "id": "dotmatrixclock",
"name": "Dotmatrix Clock",
"icon": "dotmatrixclock.png",
"version":"0.01",
"description": "A clear white-on-blue dotmatrix simulated clock",
"tags": "clock,dotmatrix,retro",
"type": "clock",
"allow_emulator":true,
"readme": "README.md",
"storage": [
{"name":"dotmatrixclock.app.js","url":"app.js"},
{"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true}
]
},
{
"id": "jbm8b",
"name": "Magic 8 Ball",
"shortName": "Magic 8 Ball",
"icon": "app.png",
"description": "A simple fortune telling app",
"tags": "game",
"storage": [
{ "name": "jbm8b.app.js", "url": "app.js" },
{ "name": "jbm8b.img", "url": "app-icon.js", "evaluate": true }
],
"version": "0.03"
} }
] ]

View File

@ -7,3 +7,4 @@
0.07: Improve logging and charting of component states and add widget icon 0.07: Improve logging and charting of component states and add widget icon
0.08: Fix for Home button in the app and README added. 0.08: Fix for Home button in the app and README added.
0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state 0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state
0.10: Remove widget icon and improve listener and setInterval handling for widget (might help with https://github.com/espruino/BangleApps/issues/381)

View File

@ -1,4 +1,5 @@
(() => { (() => {
let recordingInterval = null;
const Storage = require("Storage"); const Storage = require("Storage");
const switchableConsumers = { const switchableConsumers = {
@ -14,53 +15,44 @@
const recordingInterval10Min = 60 * 10 * 1000; const recordingInterval10Min = 60 * 10 * 1000;
const recordingInterval1Min = 60 * 1000; //For testing const recordingInterval1Min = 60 * 1000; //For testing
const recordingInterval10S = 10 * 1000; //For testing const recordingInterval10S = 10 * 1000; //For testing
var recordingInterval = null;
var compassEventReceived = false; var compassEventReceived = false;
var gpsEventReceived = false; var gpsEventReceived = false;
var hrmEventReceived = false; var hrmEventReceived = false;
// draw your widget
function draw() { function draw() {
let x = this.x; // void
let y = this.y;
g.setColor(0, 1, 0);
g.fillPoly([x + 5, y, x + 5, y + 4, x + 1, y + 4, x + 1, y + 20, x + 18, y + 20, x + 18, y + 4, x + 13, y + 4, x + 13, y], true);
g.setColor(0, 0, 0);
g.drawPoly([x + 5, y + 6, x + 8, y + 12, x + 13, y + 12, x + 16, y + 18], false);
g.reset();
} }
function onMag() { function batteryChartOnMag() {
compassEventReceived = true; compassEventReceived = true;
// Stop handling events when no longer necessarry // Stop handling events when no longer necessarry
Bangle.removeListener("mag", onMag); Bangle.removeListener("mag", batteryChartOnMag);
} }
function onGps() { function batterChartOnGps() {
gpsEventReceived = true; gpsEventReceived = true;
Bangle.removeListener("GPS", onGps); Bangle.removeListener("GPS", batterChartOnGps);
} }
function onHrm() { function batteryChartOnHrm() {
hrmEventReceived = true; hrmEventReceived = true;
Bangle.removeListener("HRM", onHrm); Bangle.removeListener("HRM", batteryChartOnHrm);
} }
function getEnabledConsumersValue() { function getEnabledConsumersValue() {
// Wait for an event from each of the devices to see if they are switched on // Wait for an event from each of the devices to see if they are switched on
var enabledConsumers = switchableConsumers.none; var enabledConsumers = switchableConsumers.none;
Bangle.on('mag', onMag); Bangle.on('mag', batteryChartOnMag);
Bangle.on('GPS', onGps); Bangle.on('GPS', batterChartOnGps);
Bangle.on('HRM', onHrm); Bangle.on('HRM', batteryChartOnHrm);
// Wait two seconds, that should be enough for each of the events to get raised once // Wait two seconds, that should be enough for each of the events to get raised once
setTimeout(() => { setTimeout(() => {
Bangle.removeAllListeners(); Bangle.removeListener('mag', batteryChartOnMag);
Bangle.removeListener('GPS', batterChartOnGps);
Bangle.removeListener('HRM', batteryChartOnHrm);
}, 2000); }, 2000);
if (Bangle.isLCDOn()) if (Bangle.isLCDOn())
@ -112,14 +104,20 @@
} }
function reload() { function reload() {
WIDGETS["batchart"].width = 24; console.log("Reloading BatteryChart widget");
WIDGETS["batchart"].width = 0;
if (recordingInterval) {
clearInterval(recordingInterval);
recordingInterval = null;
}
recordingInterval = setInterval(logBatteryData, recordingInterval10Min); recordingInterval = setInterval(logBatteryData, recordingInterval10Min);
} }
// add the widget // add the widget
WIDGETS["batchart"] = { WIDGETS["batchart"] = {
area: "tl", width: 24, draw: draw, reload: reload area: "tl", width: 0, draw: draw, reload: reload
}; };
reload(); reload();

View File

@ -15,3 +15,4 @@
0.14: Move welcome loaders to *.boot.js 0.14: Move welcome loaders to *.boot.js
0.15: Added BLE HID option for Joystick and bare Keyboard 0.15: Added BLE HID option for Joystick and bare Keyboard
0.16: Detect out of memory errors and draw them onto the bottom of the screen in red 0.16: Detect out of memory errors and draw them onto the bottom of the screen in red
0.17: Don't modify beep/buzz behaviour if firmware does it automatically

View File

@ -21,6 +21,7 @@ if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
// Don't disconnect if something is already connected to us // Don't disconnect if something is already connected to us
if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep(); if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep();
// Set time, vibrate, beep, etc // Set time, vibrate, beep, etc
if (!Bangle.F_BEEPSET) {
if (!s.vibrate) Bangle.buzz=Promise.resolve; if (!s.vibrate) Bangle.buzz=Promise.resolve;
if (s.beep===false) Bangle.beep=Promise.resolve; if (s.beep===false) Bangle.beep=Promise.resolve;
else if (s.beep=="vib") Bangle.beep = function (time, freq) { else if (s.beep=="vib") Bangle.beep = function (time, freq) {
@ -35,6 +36,7 @@ else if (s.beep=="vib") Bangle.beep = function (time, freq) {
}, time); }, time);
}); });
}; };
}
Bangle.setLCDTimeout(s.timeout); Bangle.setLCDTimeout(s.timeout);
if (!s.timeout) Bangle.setLCDPower(1); if (!s.timeout) Bangle.setLCDPower(1);
E.setTimeZone(s.timezone); E.setTimeZone(s.timezone);

View File

@ -0,0 +1 @@
0.01: Create dotmatrix clock app

View File

@ -0,0 +1,28 @@
# Dotmatrix clock
A clock face simulating the classic dotmatrix displays. Shows time, date, compass, and heart rate.
![](dotmatrix-clock-screen-shot.png)
## Features
* Easy to read digits
* Simulated white-on-blue dotmatrix display
* Compass
* Heart rate monitor
* Multiple colour palletes, swipe to change
## Usage
### Sensor readings
When the display is activated by 'flipping' the watch up, the compass and heart sensors will be activated automatically, but if
you activate the LCD through a button press, then the sensors will remain off until you press button-1.
### Colours
The display defaults to blue, but you can change this to orange by swiping the screen
## Requests
If you have any feature requests, please send an email to the author paulcockrell@gmail.com`

354
apps/dotmatrixclock/app.js Executable file
View File

@ -0,0 +1,354 @@
/**
* BangleJS DotMatrixCLOCK
*
* + Original Author: Paul Cockrell https://github.com/paulcockrell
* + Created: May 2020
*/
const storage = require('Storage');
const settings = (storage.readJSON('setting.json', 1) || {});
const is12Hour = settings["12hour"] || false;
const timeout = settings.timeout || 20;
const font7x7 = {
"empty": "00000000",
"0": "3E61514945433E",
"1": "1808080808081C",
"2": "7E01013E40407F",
"3": "7E01013E01017E",
"4": "4141417F010101",
"5": "7F40407E01017E",
"6": "3E40407E41413E",
"7": "3F010202040408",
"8": "3E41413E41413E",
"9": "3E41413F01013E",
};
const font5x5 = {
"empty": "00000000",
"-": "0000FF0000",
"0": "0E1915130E",
"1": "0C0404040E",
"2": "1E010E101F",
"3": "1E010E011E",
"4": "11111F0101",
"5": "1F101E011E",
"6": "0E101E110E",
"7": "1F01020408",
"8": "0E110E110E",
"9": "0E110F010E",
"A": "040A0E1111",
"B": "1E111E111E",
"C": "0F1010100F",
"D": "1E1111111E",
"E": "1F101E101F",
"F": "1F101E1010",
"G": "0F1013110E",
"H": "11111F1111",
"I": "0E0404040E",
"J": "1F0404140C",
"L": "101010101F",
"M": "111B151111",
"N": "1119151311",
"O": "0E1111110E",
"P": "1E111E1010",
"R": "1E111E1111",
"S": "0F100E011E",
"T": "1F04040404",
"U": "111111110E",
"V": "1111110A04",
"W": "111115150A",
"Y": "110A040404",
};
// Char renderer
const COLORS = {
blue: {
BG: "#0297fe",
DARK: "#3b3ce8",
LIGHT: "#E9ffff",
},
orange: {
BG: "#f7b336",
DARK: "#ac721e",
LIGHT: "#f6fc0f",
}
};
let selectedColor = "blue";
let displayTimeoutRef, sensorTimeoutRef;
// Example
// binToHex(["0111110", "1000000", "1000000", "1111110", "1000001", "1000001", "0111110"])
function binToHex(bins) {
return bins.map(bin => ("00" + (parseInt(bin, 2).toString(16))).substr(-2).toUpperCase()).join("");
}
// Example
// hexToBin("3E40407E41413E")
function hexToBin(hexStr) {
const regEx = new RegExp("..", "g");
const bin = hexStr
.replace(regEx, el => el + '_')
.slice(0, -1)
.split('_')
.map(hex => ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8));
return bin;
}
function drawPixel(opts) {
g.setColor(opts.color);
g.fillRect(opts.x, opts.y, opts.x + opts.w, opts.y + opts.h);
}
function drawGrid(pos, dims, charAsBin, opts) {
const defaultOpts = {
pxlW: 5,
pxlH: 5,
gap: 1,
offColor: COLORS[selectedColor].DARK,
onColor: COLORS[selectedColor].LIGHT
};
const pxl = Object.assign({}, defaultOpts, opts);
for (let rowY = 0; rowY < dims.rows; rowY++) {
const y = pos.y + ((pxl.pxlH + pxl.gap) * rowY);
for (let colX = 7; colX > (7 - dims.cols); colX--) {
const x = pos.x + ((pxl.pxlW + pxl.gap) * colX);
const color = (charAsBin && parseInt(charAsBin[rowY][colX])) ? pxl.onColor : pxl.offColor;
drawPixel({
x: x,
y: y,
w: pxl.pxlW,
h: pxl.pxlH,
color: color,
});
}
}
}
function drawFont(str, font, x, y) {
let fontMap, rows, cols;
switch(font) {
case "7x7":
fontMap = font7x7;
rows = cols = 7;
break;
case "5x5":
fontMap = font5x5;
rows = cols = 5;
break;
default:
throw "Unknown font type: " + font;
}
const pxlW = 2;
const pxlH = 2;
const gap = 2;
const gutter = 3;
const charArr = str.split("");
const gridWidthTotal = (rows * (pxlW + gap)) + gutter;
for (let i = 0; i < charArr.length; i++) {
const charAsBin = fontMap.hasOwnProperty(charArr[i])?
hexToBin(fontMap[charArr[i]]):
fontMap.empty;
drawGrid(
{x: x + (i * gridWidthTotal), y: y},
{rows: rows, cols: cols},
charAsBin,
{pxlW: pxlW, pxlH: pxlH, gap: gap}
);
}
}
function drawTitles() {
g.setColor("#ffffff");
g.setFont("6x8");
g.drawString("COMPASS", 52, 49);
g.drawString("HEART", 122, 49);
g.drawString("TIME", 52, 94);
g.drawString("DATE", 52, 144);
}
function drawCompass(lastHeading) {
const directions = [
'N',
'NE',
'E',
'SE',
'S',
'SW',
'W',
'NW'
];
const cps = Bangle.getCompass();
let angle = cps.heading;
let heading = angle?
directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]:
"-- ";
heading = (heading + " ").slice(0, 3);
if (lastHeading != heading) drawFont(heading, "5x5", 40, 67);
setTimeout(drawCompass.bind(null, heading), 1000 * 2);
}
function drawHeart(hrm) {
drawFont((" " + (hrm ? hrm.bpm : "---")).slice(-3), "5x5", 109, 67);
}
function drawTime(lastHrs, lastMns, toggle) {
const date = new Date();
const h = date.getHours();
const hrs = ("00" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
const mns = ("00" + date.getMinutes()).substr(-2);
if (lastHrs != hrs) {
drawFont(hrs, "7x7", 48, 109);
}
if (lastMns != mns) {
drawFont(mns, "7x7", 124, 109);
}
const color = toggle? COLORS[selectedColor].LIGHT : COLORS[selectedColor].DARK;
// This should toggle on/off per second
drawPixel({
color: color,
x: 118, y: 118,
w: 2, h: 2,
});
drawPixel({
color: color,
x: 118, y: 125,
w: 2, h: 2,
});
setTimeout(drawTime.bind(null, hrs, mns, !toggle), 1000);
}
function drawDate(lastDate) {
const locale = require('locale');
const date = new Date();
if (lastDate != date.toISOString().split('T')[0]) {
const dow = locale.dow(date, 1).toUpperCase();
const dayNum = ("00" + date.getDate()).slice(-2);
const mon = locale.month(date).toUpperCase().slice(0, 3);
const yr = date.getFullYear().toString().slice(-2);
drawFont(dow + " " + dayNum, "5x5", 40, 159);
drawFont(mon + " " + yr, "5x5", 40, 189);
}
setTimeout(drawDate.bind(null, date.toISOString().split('T')), 1000 * 60);
}
function setSensors(state) {
// Already reading sensors and trying to activate sensors, do nothing
if (sensorTimeoutRef && state === 1) return;
// If we are activating the sensors, turn them off again in one minute
if (state === 1) {
sensorTimeoutRef = setTimeout(() => { setSensors(0); }, 1000 * 60);
} else {
if (sensorTimeoutRef) {
clearInterval(sensorTimeoutRef);
sensorTimeoutRef = null;
}
// Bit nasty, but we only redraw the heart value on sensor callback
// but we want to blank out when sensor is off, but no callback for
// that so force redraw here
drawHeart();
}
Bangle.setHRMPower(state);
Bangle.setCompassPower(state);
}
function drawScreen() {
g.setBgColor(COLORS[selectedColor].BG);
g.clearRect(0, 24, g.getWidth(), g.getHeight());
// Draw components
drawTitles();
drawCompass();
drawHeart();
drawTime();
drawDate();
}
function clearTimers(){
if (displayTimeoutRef) {
clearInterval(displayTimeoutRef);
displayTimeoutRef = null;
}
if (sensorTimeoutRef) {
clearInterval(sensorTimeoutRef);
sensorTimeoutRef = null;
}
}
function resetDisplayTimeout() {
if (displayTimeoutRef) clearInterval(displayTimeoutRef);
Bangle.setLCDPower(true);
displayTimeoutRef = setTimeout(() => {
if (Bangle.isLCDOn()) Bangle.setLCDPower(false);
clearTimers();
}, 1000 * timeout);
}
// Turn sensors on
setSensors(1);
// Reset screen
g.clear();
// Load and draw widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// Draw screen
drawScreen();
resetDisplayTimeout();
// Setup callbacks
Bangle.on('swipe', (sDir) => {
selectedColor = selectedColor === "blue" ? "orange" : "blue";
resetDisplayTimeout();
drawScreen();
});
Bangle.on('HRM', drawHeart);
setWatch(() => {
setSensors(1);
resetDisplayTimeout();
}, BTN1, {repeat: true, edge: "falling"});
setWatch(() => {
setSensors(0);
clearTimers();
Bangle.setLCDMode();
Bangle.showLauncher();
}, BTN2, {repeat: false, edge: "falling"});
Bangle.on('lcdPower', (on) => {
if(on) {
resetDisplayTimeout();
} else {
clearTimers();
setSensors(0);
}
});
Bangle.on('faceUp', (up) => {
if (up && !Bangle.isLCDOn()) {
setSensors(1);
resetDisplayTimeout();
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AAmBAEgrFAAZelFo+s1krAEcAE4IuFHIIJBAEXXE4KSEF84nCF4WBgErBYoAfEoaTBF42zF8PXF5QNBi4AgMIYv/F9nX64CDAw4ACl8vBIgGGF/4AOEgKPfI4xfoF96P/R/6PdACAv/F/4v/F/4v/F8HX68Xl8vAwIDCBIQADBIQQDBoQQDF/4AOGQqPbLAxmGL5gGDF/4AfF/6PRBIQQDSwwv/ABwoCR7xYGMwxfhF94AeF/4vr1nXBoIAf64mCF4gJEF8IkCF4YABFYQLDAEItBwIuCF9InBF4iSBwMrAEgnBFwgACXsIADFo4ABqwAkFQg="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -201,7 +201,7 @@ function showSortAppsManually() {
function setSortorder(app, val) { function setSortorder(app, val) {
app = store.readJSON(app.id + '.info', 1); app = store.readJSON(app.id + '.info', 1);
app.sortorder = val; app.sortorder = val;
store.writeJSON(app.id + '.info', app); store.write(app.id + '.info', JSON.stringify(app));
} }
function getAppsList() { function getAppsList() {

View File

@ -10,7 +10,7 @@ const cirRad = 2*Math.PI;
const proportion = 0.3; // relative size of hour hand const proportion = 0.3; // relative size of hour hand
const thickness = 4; // thickness of decorative lines const thickness = 4; // thickness of decorative lines
// retrieve settings from menu // retrieve settings from menu
let settings = require('Storage').readJSON('app.json',1)||{}; let settings = require('Storage').readJSON('gallifr.json',1)||{};
const decoration = !settings.decoration; const decoration = !settings.decoration;
const widgets = !settings.widgets; const widgets = !settings.widgets;
if (widgets) { if (widgets) {

View File

@ -1,11 +1,11 @@
// make sure to enclose the function in parentheses // make sure to enclose the function in parentheses
(function (back) { (function (back) {
let settings = require('Storage').readJSON('app.json',1)||{}; let settings = require('Storage').readJSON('gallifr.json',1)||{};
let colours = ["green","red","blue","80s"]; let colours = ["green","red","blue","80s"];
let onoff = ["on","off"]; let onoff = ["on","off"];
function save(key, value) { function save(key, value) {
settings[key] = value; settings[key] = value;
require('Storage').write('app.json',settings); require('Storage').writeJSON('gallifr.json',settings);
} }
const appMenu = { const appMenu = {
'': {'title': 'Clock Settings'}, '': {'title': 'Clock Settings'},

3
apps/jbm8b/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: First working version
0.02: Added delay in replying for dramatic effect
0.03: Fixed apps.json entry

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwhBC/AGMrq2B1gAEwNWlYthq2s64AKGYIydFpoAEGLUrFqIADqxcXFqhiDFymBFy7GCF1owTRjCSVlYudeiGsF7/XlaNqSKBeP1mBwJxQMBReO1gaEleBMDBLN1hAC1hhBAoIwNCwQAGlZINqxvFGAIXOSBAXQN4hPBC5yQIVBxfBCAgvQSBC+NFAYRDMwJHOF654DqxkBYooALF6+sbIhkEF8Z3CRIWBR6AvXFAzvQF6wnIYQJgNd5AWNdoLoGBBAvPO5pfYH4IvUUwS/GVBzXBYCpHCq2s1mBDwKOWDwRgNPAwVVMCRLCwIABCZ6OJJSAATLxZgRACJeLAAMrFz9WFxiRgRpoADwIub1guQGDmsXhqSfRiL0G1jqkMRYxRwKLUGK2sFryVEq2B1gAEwNWFkIA/AH4A/AH4AQ"))

80
apps/jbm8b/app.js Normal file
View File

@ -0,0 +1,80 @@
const affirmative = [
'It is\ncertain.',
'It is\ndicededly\nso.',
'Without\na doubt.',
'Yes\ndefinitely.',
'You may\nrely\non it.',
'As I see,\nit yes.',
'Most\nlikely.',
'Outlook\ngood.',
'Yes.',
'Signs point\nto yes.'
];
const nonCommittal = [
'Reply hazy,\ntry again.',
'Ask again\nlater.',
'Better not\ntell you\nnow.',
'Cannot\npredict\nnow.',
'Concentrate\nand\nask again.'
];
const negative = [
'Don\'t\ncount on it.',
'My reply\nis no.',
'My sources\nsay no.',
'Outlook\nis not\nso\ngood.',
'Very\ndoubtful.'
];
const title = 'Magic 8 Ball';
const answers = [affirmative, nonCommittal, negative];
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
function predict() {
// affirmative, negative or non-committal
let max = answers.length;
const a = Math.floor(getRandomArbitrary(0, max));
// sets max compared to answer category
max = answers[a].length;
const b = Math.floor(getRandomArbitrary(0, max));
// get the answer
const response = answers[a][b];
return response;
}
function draw(msg) {
// console.log(msg);
g.clear();
E.showMessage(msg, title);
}
function reply(button) {
const theButton = (typeof button === 'undefined' || isNaN(button)) ? 1 : button;
const timer = Math.floor(getRandomArbitrary(0, theButton) * 1000);
// Thinking...
draw('...');
setTimeout('draw(predict());', timer);
}
function ask() {
draw('Ask me a\nYes or No\nquestion\nand\ntouch the\nscreen');
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
ask();
// Event Handlers
Bangle.on('touch', (button) => reply(button));
setWatch(ask, BTN1, { repeat: true, edge: "falling" });
setWatch(reply, BTN3, { repeat: true, edge: "falling" });
// Back to launcher
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

BIN
apps/jbm8b/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,2 +1,3 @@
0.01: Init 0.01: Init
0.02: fix 3/4 moon orientation 0.02: fix 3/4 moon orientation
0.03: Change `largeclock.json` to 'data' file to allow settings to be preserved

View File

@ -9,10 +9,8 @@ const moonX = 215;
const moonY = 50; const moonY = 50;
const settings = require("Storage").readJSON("largeclock.json", 1); const settings = require("Storage").readJSON("largeclock.json", 1);
const BTN1app = settings.BTN1; const BTN1app = settings.BTN1 || "";
const BTN3app = settings.BTN3; const BTN3app = settings.BTN3 || "";
console.log("BTN1app", BTN1app);
console.log("BTN3app", BTN3app);
function drawMoon(d) { function drawMoon(d) {
const BLACK = 0, const BLACK = 0,
@ -174,14 +172,14 @@ Bangle.setLCDMode();
// Show launcher when middle button pressed // Show launcher when middle button pressed
clearWatch(); clearWatch();
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
setWatch( if (BTN1app) setWatch(
function() { function() {
load(BTN1app); load(BTN1app);
}, },
BTN1, BTN1,
{ repeat: false, edge: "rising" } { repeat: false, edge: "rising" }
); );
setWatch( if (BTN3app) setWatch(
function() { function() {
load(BTN3app); load(BTN3app);
}, },

View File

@ -34,7 +34,7 @@
function onchange(v) { function onchange(v) {
settings[btn] = v; settings[btn] = v;
s.write("largeclock.json", settings); s.writeJSON("largeclock.json", settings);
} }
const btnMenu = { const btnMenu = {

View File

@ -3,3 +3,4 @@
0.04: Improve performance, attempt to remove occasional glitch when LCD on (fix #279) 0.04: Improve performance, attempt to remove occasional glitch when LCD on (fix #279)
0.05: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast 0.05: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
Fix issue where first digit could get stuck going from "2x:xx" to " x:xx" (fix #365) Fix issue where first digit could get stuck going from "2x:xx" to " x:xx" (fix #365)
0.06: Support 12 hour time

View File

@ -1,3 +1,4 @@
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
var locale = require("locale"); var locale = require("locale");
var CHARW = 34; // how tall are digits? var CHARW = 34; // how tall are digits?
var CHARP = 2; // how chunky are digits? var CHARP = 2; // how chunky are digits?
@ -146,7 +147,7 @@ function drawDigits(lastText,thisText,n) {
x+=s+p+7; x+=s+p+7;
} }
} }
function drawSeconds() { function drawEverythingElse() {
var x = (CHARW + CHARP + 6)*5; var x = (CHARW + CHARP + 6)*5;
var y = Y + 2*CHARW + CHARP; var y = Y + 2*CHARW + CHARP;
var d = new Date(); var d = new Date();
@ -154,6 +155,8 @@ function drawSeconds() {
g.setFont("6x8"); g.setFont("6x8");
g.setFontAlign(-1,-1); g.setFontAlign(-1,-1);
g.drawString(("0"+d.getSeconds()).substr(-2), x, y-8, true); g.drawString(("0"+d.getSeconds()).substr(-2), x, y-8, true);
// meridian
if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x, Y + 4, true);
// date // date
g.setFontAlign(0,-1); g.setFontAlign(0,-1);
var date = locale.date(d,false); var date = locale.date(d,false);
@ -164,13 +167,15 @@ function drawSeconds() {
function showTime() { function showTime() {
if (animInterval) return; // in animation - quit if (animInterval) return; // in animation - quit
var d = new Date(); var d = new Date();
var t = (" "+d.getHours()).substr(-2)+":"+ var hours = d.getHours();
if (is12Hour) hours = ((hours + 11) % 12) + 1;
var t = (" "+hours).substr(-2)+":"+
("0"+d.getMinutes()).substr(-2); ("0"+d.getMinutes()).substr(-2);
var l = lastTime; var l = lastTime;
// same - don't animate // same - don't animate
if (t==l || l=="-----") { if (t==l || l=="-----") {
drawDigits(l,t,0); drawDigits(l,t,0);
drawSeconds(); drawEverythingElse();
lastTime = t; lastTime = t;
return; return;
} }

View File

@ -2,3 +2,4 @@
0.02: Watch vibrates with every beat 0.02: Watch vibrates with every beat
0.03: Uses mean of three time intervalls to calculate bmp 0.03: Uses mean of three time intervalls to calculate bmp
0.04: App shows instructions, Widgets remain visible, color changed 0.04: App shows instructions, Widgets remain visible, color changed
0.05: Buzz intensity and beats per bar can be changed via settings-app

View File

@ -8,6 +8,7 @@ This metronome makes your watch blink and vibrate with a given rate.
* Use `BTN1` to increase the bmp value by one. * Use `BTN1` to increase the bmp value by one.
* Use `BTN3` to decrease the bmp value by one. * Use `BTN3` to decrease the bmp value by one.
* You can change the bpm value any time by tapping the screen or using `BTN1` and `BTN3`. * You can change the bpm value any time by tapping the screen or using `BTN1` and `BTN3`.
* Intensity of buzzing and the beats per bar (default 4) can be changed with the settings-app. The first beat per bar will be marked in red.
## Attributions ## Attributions

View File

@ -6,31 +6,40 @@ var tindex=0; //index to iterate through time_diffs
Bangle.setLCDTimeout(undefined); //do not deaktivate display while running this app Bangle.setLCDTimeout(undefined); //do not deaktivate display while running this app
const storage = require("Storage");
const SETTINGS_FILE = 'metronome.settings.json';
//return setting
function setting(key) {
//define default settings
const DEFAULTS = {
'beatsperbar': 4,
'buzzintens': 0.75,
};
if (!settings) { loadSettings(); }
return (key in settings) ? settings[key] : DEFAULTS[key];
}
//load settings
let settings;
function loadSettings() {
settings = storage.readJSON(SETTINGS_FILE, 1) || {};
}
function changecolor() { function changecolor() {
const maxColors = 2;
const colors = { const colors = {
0: { value: 0xFFFF, name: "White" }, 0: { value: 0xF800, name: "Red" },
// 1: { value: 0x000F, name: "Navy" }, 1: { value: 0xFFFF, name: "White" },
// 2: { value: 0x03E0, name: "DarkGreen" }, 2: { value: 0x9492, name: "gray" },
// 3: { value: 0x03EF, name: "DarkCyan" }, 3: { value: 0xFFFF, name: "White" },
// 4: { value: 0x7800, name: "Maroon" }, 4: { value: 0x9492, name: "gray" },
// 5: { value: 0x780F, name: "Purple" }, 5: { value: 0xFFFF, name: "White" },
// 6: { value: 0x7BE0, name: "Olive" }, 6: { value: 0x9492, name: "gray" },
// 7: { value: 0xC618, name: "LightGray" }, 7: { value: 0xFFFF, name: "White" },
// 8: { value: 0x7BEF, name: "DarkGrey" },
// 9: { value: 0x001F, name: "Blue" },
// 10: { value: 0x07E0, name: "Green" },
// 11: { value: 0x07FF, name: "Cyan" },
1: { value: 0xF800, name: "Red" },
// 13: { value: 0xF81F, name: "Magenta" },
// 14: { value: 0xFFE0, name: "Yellow" },
// 15: { value: 0xFFFF, name: "White" },
// 16: { value: 0xFD20, name: "Orange" },
// 17: { value: 0xAFE5, name: "GreenYellow" },
// 18: { value: 0xF81F, name: "Pink" },
}; };
g.setColor(colors[cindex].value); g.setColor(colors[cindex].value);
if (cindex == maxColors-1) { if (cindex == setting('beatsperbar')-1) {
cindex = 0; cindex = 0;
} }
else { else {
@ -42,11 +51,16 @@ function changecolor() {
function updateScreen() { function updateScreen() {
g.clearRect(0, 50, 250, 150); g.clearRect(0, 50, 250, 150);
changecolor(); changecolor();
Bangle.buzz(50, 0.75); try {
Bangle.buzz(50, setting('buzzintens'));
}
catch(err) {
}
g.setFont("Vector",48); g.setFont("Vector",48);
g.drawString(Math.floor(bpm)+"bpm", 5, 60); g.drawString(Math.floor(bpm)+"bpm", 5, 60);
} }
Bangle.on('touch', function(button) { Bangle.on('touch', function(button) {
// setting bpm by tapping the screen. Uses the mean time difference between several tappings. // setting bpm by tapping the screen. Uses the mean time difference between several tappings.
if (tindex < time_diffs.length) { if (tindex < time_diffs.length) {

View File

@ -0,0 +1,48 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
(function(back) {
const SETTINGS_FILE = 'metronome.settings.json';
// initialize with default settings...
let s = {
'beatsperbar': 4,
'buzzintens': 0.75,
};
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage');
const saved = storage.readJSON(SETTINGS_FILE, 1) || {};
for (const key in saved) {
s[key] = saved[key];
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function(value) {
s[key] = value;
storage.write(SETTINGS_FILE, s);
};
}
const menu = {
'': { 'title': 'Metronome' },
'< Back': back,
'beats per bar': {
value: s.beatsperbar,
min: 1,
max: 8,
step: 1,
onchange: save('beatsperbar'),
},
'buzz intensity': {
value: s.buzzintens,
min: 0.0,
max: 1.0,
step: 0.25,
onchange: save('buzzintens'),
},
};
E.showMenu(menu);
});

1
apps/miplant/ChangeLog Normal file
View File

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

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+4AA/AH4A/AAPQ64AQ44ZKBYwvc6/QF9wwFF9XXF73I4IAH44/F54vdCpYQESAYvm5Avm44ADA4Yvmc44v/F/4v/F/4v/F/4v+zmjv4ABF9GrwoACrYvn2WGFwgABSh4AV0QtDFwYvmFwlhF9wuDF9QuEF82jFw4vmMIQuFv4vuEDOi0d/WgV/0YNGBIN/LrSvCABAkECAI4GAChKBF5QABCIYEDADKpCsIvJLQQ0EGoShJMBwACSJYvEB5GiMCYxJF4LuCLorTLF6IxGQILuBMYgAGC4QwP0YwHYwQbCF4K1CL4lhCohfQMBBiCFQQvEAgIsFAATxWSQyPDAYYSIHgRgZE4IsBF4QGCCI6NRMBboFXgTTIFyYwIPYWcGAb2CCI2iF6qwBD4aqERYQABIQ+cFywALLwgAqXoYvrSAQROA=="))

74
apps/miplant/app.js Normal file
View File

@ -0,0 +1,74 @@
function getImgHum() {
return require("heatshrink").decompress(atob("jUoxH+AEtlsoYYDS4ZYDAYaVDLAYFDSQYHDSIZYDBIaPDLAYLDRoZYDBoaLDLAYPDRIZYDCIaHDLAYTDQoZYDCoaDDOQYXAA+JxIYX1utDSwYBAAIzYGiwZUTgpODQpzPGGgY3OdI4aRDIIaMDJIYCDIztDGRwaJP5oaWDAwaRDBAbOC5YcKB5I="));
}
function getImgTemp() {
return require("heatshrink").decompress(atob("iUqxH+AA2sAAQLHCBASMCAoSLCPOBAAQRfI/5Hn3YACy4ACCL4ADCL5H/I/AQHCRAQJCQwQLCQgQNCQYRQCB4A/ADaPjYqTpSCRYQGCZALFA"));
}
function getImgFert() {
return require("heatshrink").decompress(atob("kklxH+AC+FwtbDbAfFAAVbEbgiGEbYiHEbQiEsIjiEQYjeEQiPdEQrXdEdKnTAAJsMD6QlJFZAAIGAIkPEaIkCrdhEaR9MT4gkLFAyjMYoojNUZ4jFEoxrGEBCJDEZSWEEZdhCwpsKJQiJFAgYgGEQwjLD4QjFCRD+KCAylGQ4gjXVhAiPEhAKDJIwiQEowIEEQo2GERgAKEYwAcEUQkDEL9VAAgHFETgAIDJwePEZwdTE5ggdMJt6AAQEEqwRMABYQDAAwkBF5AkKEBQAPEUR6ESAQicJIX+A=="));
}
var deviceInfo = {};
function parseDevice(device) {
var d = new DataView(device.serviceData["fe95"]);
var frame = d.getUint16(0,true);
var offset = 5;
if (frame&16) offset+=6; // mac address
if (frame&32) offset+=1; // capabilitities
if (frame&64) { // event
var l = d.getUint8(offset+2);
var code = d.getUint16(offset,true);
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id};
event = deviceInfo[device.id];
switch (code) {
case 0x1004: event.temperature = d.getInt16(offset+3,true)/10; break;
case 0x1006: event.humidity = d.getInt16(offset+3)/10; break;
case 0x100D:
event.temperature = d.getInt16(offset+3,true)/10;
event.humidity = d.getInt16(offset+5)/10; break;
case 0x1008: event.moisture = d.getUint8(offset+3); break;
case 0x1009: event.fertility = d.getUint16(offset+3,true)/10; break;
// case 0x1007: break; // 3 bytes? got 84,0,0 or 68,0,0
default: event.code = code;
event.raw = new Uint8Array(d.buffer, offset+3, l);
break;
}
//print(event);
show(event);
}
}
/*
eg. {
"id": "c4:7c:8d:6a:ac:79 public",
"temperature": 16.6, "code": 4103,
"raw": new Uint8Array([246, 0, 0]),
"moisture": 46, "fertility": 20.8 }
*/
function show(event) {
g.reset().setFont("6x8");
var y = 45 + 50*Object.keys(deviceInfo).indexOf(event.id);
g.drawString(event.id.substr(0,17),0,y);
g.drawImage(getImgHum(),0,y+15);
g.setFont("6x8",2);
var t = (event.moisture===undefined) ? "?" : event.moisture;
g.drawString((t+" ").substr(0,3),35,y+25,true);
g.drawImage(getImgFert(),80,y+15);
t = Math.round(event.fertility) || "?";
g.drawString((t+" ").substr(0,3), 120, y+25, true);
g.drawImage(getImgTemp(),160,y+15);
t = Math.round(event.temperature) || "?";
g.drawString((t+" ").substr(0,3), 180, y+25, true);
g.flip();
}
g.clear();
g.setFont("6x8",2).setFontAlign(0,-1).drawString("Scanning...",120,24);
Bangle.loadWidgets()
Bangle.drawWidgets()
NRF.setScan(parseDevice, { filters: [{serviceData:{"fe95":{}}}], timeout: 2000 });

BIN
apps/miplant/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -6,3 +6,4 @@
Don't run again when settings app is updated (or absent) Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings Add "Run Now" option to settings
0.05: Don't overwrite existing settings on app update 0.05: Don't overwrite existing settings on app update
0.06: Allow welcome to run after a fresh install

View File

@ -1,11 +1,8 @@
(function() { (function() {
let s = require('Storage').readJSON('ncstart.json', 1) let s = require('Storage').readJSON('ncstart.json', 1) || {};
|| require('Storage').readJSON('setting.json', 1) if (!s.welcomed) {
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('ncstart.app.js')) {
setTimeout(() => { setTimeout(() => {
s.welcomed = true require('Storage').write('ncstart.json', {welcomed: true})
require('Storage').write('ncstart.json', s)
load('ncstart.app.js') load('ncstart.app.js')
}) })
} }

2
apps/rndmclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New widget
0.02: Less invasive, change default clock setting instead of directly loading the new clock (no longer breaks Gadgetbridge notifications)

6
apps/rndmclk/README.md Normal file
View File

@ -0,0 +1,6 @@
# Summary
Random Clock is a widget that will randomly show one of the installed watch faces each time the LCD is turned on.
# How it works
Everytime the LCD is turned off, the widget randomly changes the clock. When you long press BTN 3 the next time,
you might (or might not, it's random after all) see another watch face.

BIN
apps/rndmclk/rndmclk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

35
apps/rndmclk/widget.js Normal file
View File

@ -0,0 +1,35 @@
(() => {
let currentClock = "";
/**
* Random value between zero (inclusive) and max (exclusive)
* @param {int} max
*/
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function loadRandomClock() {
// Find available clock apps (same way as in the bootloader)
var clockApps = require("Storage").list(/\.info$/).map(app => require("Storage").readJSON(app, 1) || {}).filter(app => app.type == "clock").sort((a, b) => a.sortorder - b.sortorder);
if (clockApps && clockApps.length > 0) {
var clockIndex = getRandomInt(clockApps.length);
// Only update the file if the clock really change to be nice to the FLASH mem
if (clockApps[clockIndex].src != currentClock) {
currentClock = clockApps[clockIndex].src;
settings = require("Storage").readJSON('setting.json', 1);
settings.clock = clockApps[clockIndex].src;
require("Storage").write('setting.json', settings);
}
}
}
Bangle.on('lcdPower', (on) => {
if (!on) {
loadRandomClock();
}
});
})();

View File

@ -8,3 +8,6 @@
Don't run again when settings app is updated (or absent) Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings Add "Run Now" option to settings
0.08: Don't overwrite existing settings on app update 0.08: Don't overwrite existing settings on app update
0.09: Allow welcome to run after a fresh install
More useful app menu
BTN2 now goes to menu on release

View File

@ -285,7 +285,7 @@ setWatch(()=>{
if (sceneNumber == scenes.length-1) { if (sceneNumber == scenes.length-1) {
load(); load();
} }
}, BTN2, {repeat:true,edge:"rising"}); }, BTN2, {repeat:true,edge:"falling"});
setWatch(()=>move(-1), BTN1, {repeat:true}); setWatch(()=>move(-1), BTN1, {repeat:true});
(function migrateSettings(){ (function migrateSettings(){

View File

@ -1,11 +1,8 @@
(function() { (function() {
let s = require('Storage').readJSON('welcome.json', 1) let s = require('Storage').readJSON('welcome.json', 1) || {};
|| require('Storage').readJSON('setting.json', 1) if (!s.welcomed) {
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('welcome.app.js')) {
setTimeout(() => { setTimeout(() => {
s.welcomed = true require('Storage').write('welcome.json', {welcomed: true})
require('Storage').write('welcome.json', {welcomed: "yes"})
load('welcome.app.js') load('welcome.app.js')
}) })
} }

View File

@ -3,12 +3,16 @@
|| require('Storage').readJSON('setting.json', 1) || {} || require('Storage').readJSON('setting.json', 1) || {}
E.showMenu({ E.showMenu({
'': { 'title': 'Welcome App' }, '': { 'title': 'Welcome App' },
'Run on Next Boot': { 'Run next boot': {
value: !settings.welcomed, value: !settings.welcomed,
format: v => v ? 'OK' : 'No', format: v => v ? 'Yes' : 'No',
onchange: v => require('Storage').write('welcome.json', {welcomed: !v}), onchange: v => require('Storage').write('welcome.json', {welcomed: !v}),
}, },
'Run Now': () => load('welcome.app.js'), 'Run Now': () => load('welcome.app.js'),
'Turn off & run next': () => {
require('Storage').write('welcome.json', {welcomed: false});
Bangle.off();
},
'< Back': back, '< Back': back,
}) })
}) })

18
bin/pre-publish.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
cd `dirname $0`/..
nodejs bin/sanitycheck.js || exit 1
echo "Sanity check passed."
echo "Finding app dates..."
# Create list of:
# appid,created_time,modified_time
cd apps
for appfolder in *; do
echo "$appfolder,$(git log --follow --format=%ai -- $appfolder | tail -n 1),$(git log --follow --format=%ai -- $appfolder | head -n 1)" ;
done | grep -v _example_ | grep -v unknown.png > ../appdates.csv
cd ..
echo "Ready to publish"

View File

@ -40,6 +40,12 @@
.chip { .chip {
cursor: pointer; cursor: pointer;
} }
.filter-nav {
display: inline-block;
}
.sort-nav {
float: right;
}
.tile-content { position: relative; } .tile-content { position: relative; }
.link-github { .link-github {
position:absolute; position:absolute;
@ -88,8 +94,9 @@
</div> </div>
<div class="container bangle-tab" id="librarycontainer"> <div class="container bangle-tab" id="librarycontainer">
<div>
<div class="filter-nav"> <div class="filter-nav">
<label class="chip active" filterid="">All</label> <label class="chip active" filterid="">Default</label>
<label class="chip" filterid="clock">Clocks</label> <label class="chip" filterid="clock">Clocks</label>
<label class="chip" filterid="game">Games</label> <label class="chip" filterid="game">Games</label>
<label class="chip" filterid="tool">Tools</label> <label class="chip" filterid="tool">Tools</label>
@ -98,7 +105,15 @@
<label class="chip" filterid="outdoors">Outdoors</label> <label class="chip" filterid="outdoors">Outdoors</label>
<label class="chip" filterid="favourites">Favourites</label> <label class="chip" filterid="favourites">Favourites</label>
</div> </div>
<div class="panel"> <div class="sort-nav hidden">
<span>Sort by:</span>
<label class="chip active" sortid="">None</label>
<label class="chip" sortid="created">New</label>
<label class="chip" sortid="modified">Updated</label>
</div>
</div>
<div class="panel" style="clear:both">
<div class="panel-header"> <div class="panel-header">
<div class="input-group" id="searchform"> <div class="input-group" id="searchform">
<input class="form-input" type="text" placeholder="Keywords..."> <input class="form-input" type="text" placeholder="Keywords...">

View File

@ -1,10 +1,13 @@
if (typeof btoa==="undefined")
function btoa(d) { return Buffer.from(d).toString('base64'); }
// Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64) // Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64)
function toJS(txt) { function toJS(txt) {
var json = JSON.stringify(txt); var json = JSON.stringify(txt);
var b64 = "atob("+JSON.stringify(btoa(json))+")"; var b64 = "atob("+JSON.stringify(btoa(json))+")";
var js = b64.length < json.length ? b64 : json; var js = b64.length < json.length ? b64 : json;
if (heatshrink) { if (typeof heatshrink !== "undefined") {
var ua = new Uint8Array(txt.length); var ua = new Uint8Array(txt.length);
for (var i=0;i<txt.length;i++) ua[i] = txt.charCodeAt(i); for (var i=0;i<txt.length;i++) ua[i] = txt.charCodeAt(i);
var c = heatshrink.compress(ua); var c = heatshrink.compress(ua);

View File

@ -1,6 +1,6 @@
// EspruinoTools bundle (https://github.com/espruino/EspruinoTools) // EspruinoTools bundle (https://github.com/espruino/EspruinoTools)
// Created with https://github.com/espruino/EspruinoWebIDE/blob/gh-pages/extras/create_espruinotools_js.sh // Created with https://github.com/espruino/EspruinoWebIDE/blob/gh-pages/extras/create_espruinotools_js.sh
// Based on EspruinoWebIDE 0.73.4 // Based on EspruinoWebIDE 0.73.7
/** /**
Copyright 2014 Gordon Williams (gw@pur3.co.uk) Copyright 2014 Gordon Williams (gw@pur3.co.uk)
@ -23,6 +23,7 @@ var Espruino;
* *
* Common processors are: * Common processors are:
* *
* jsCodeChanged - called when the code in the editor changes with {code}
* sending - sending code to Espruino (no data) * sending - sending code to Espruino (no data)
* transformForEspruino - transform code ready to be sent to Espruino * transformForEspruino - transform code ready to be sent to Espruino
* transformModuleForEspruino({code,name}) * transformModuleForEspruino({code,name})
@ -123,6 +124,7 @@ Espruino.Core.Status = {
hasProgress : function() { return false; }, hasProgress : function() { return false; },
incrementProgress : function(amt) {} incrementProgress : function(amt) {}
}; };
var acorn = (function(){ var exports={};var module={};
(function (global, factory) { (function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) : typeof define === 'function' && define.amd ? define(['exports'], factory) :
@ -3988,6 +3990,7 @@ exports.nonASCIIwhitespace = nonASCIIwhitespace;
Object.defineProperty(exports, '__esModule', { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
}))); })));
return exports;})();
/** /**
Copyright 2014 Gordon Williams (gw@pur3.co.uk) Copyright 2014 Gordon Williams (gw@pur3.co.uk)
@ -4507,8 +4510,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
fileLoader.click(); fileLoader.click();
} }
/* Save a file with a save file dialog. callback(savedFileName) only called in chrome app case when we knopw the filename*/ // Save a file with a save file dialog
function fileSaveDialog(data, filename, callback) { function fileSaveDialog(data, filename) {
function errorHandler() { function errorHandler() {
Espruino.Core.Notifications.error("Error Saving", true); Espruino.Core.Notifications.error("Error Saving", true);
} }
@ -4524,7 +4527,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
writer.onwriteend = function(e) { writer.onwriteend = function(e) {
writer.onwriteend = function(e) { writer.onwriteend = function(e) {
console.log('FileWriter: complete'); console.log('FileWriter: complete');
if (callback) callback(writableFileEntry.name);
}; };
console.log('FileWriter: writing'); console.log('FileWriter: writing');
writer.write(blob); writer.write(blob);
@ -4535,8 +4537,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
}, errorHandler); }, errorHandler);
}); });
} else { } else {
var rawdata = new Uint8Array(data.length);
for (var i=0;i<data.length;i++) rawdata[i]=data.charCodeAt(i);
var a = document.createElement("a"), var a = document.createElement("a"),
file = new Blob([data], {type: "text/plain"}); file = new Blob([rawdata.buffer], {type: "text/plain"});
var url = URL.createObjectURL(file); var url = URL.createObjectURL(file);
a.href = url; a.href = url;
a.download = filename; a.download = filename;
@ -4765,6 +4769,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
console.log("GET chrome.storage.sync = "+JSON.stringify(value)); console.log("GET chrome.storage.sync = "+JSON.stringify(value));
callback(value); callback(value);
}); });
} else if (typeof window !== 'undefined' && window.localStorage) {
var data = {};
var value = window.localStorage.getItem("CONFIG");
console.log("GET window.localStorage = "+JSON.stringify(value));
try {
data = JSON.parse(value);
} catch (e) {
console.log("Invalid config data");
}
callback(data);
} else if (typeof document != "undefined") { } else if (typeof document != "undefined") {
var data = {}; var data = {};
var cookie = document.cookie; var cookie = document.cookie;
@ -4786,8 +4800,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
function _set(data) { function _set(data) {
if (typeof chrome !== 'undefined' && chrome.storage) { if (typeof chrome !== 'undefined' && chrome.storage) {
console.log("SET chrome.storage.sync = "+JSON.stringify(data)); console.log("SET chrome.storage.sync = "+JSON.stringify(data,null,2));
chrome.storage.sync.set({ CONFIGS : data }); chrome.storage.sync.set({ CONFIGS : data });
} else if (typeof window !== 'undefined' && window.localStorage) {
console.log("SET window.localStorage = "+JSON.stringify(data,null,2));
window.localStorage.setItem("CONFIG",JSON.stringify(data));
} else if (typeof document != "undefined") { } else if (typeof document != "undefined") {
document.cookie = "CONFIG="+btoa(JSON.stringify(data)); document.cookie = "CONFIG="+btoa(JSON.stringify(data));
} }
@ -4811,7 +4828,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
addSection("General", { sortOrder:100, description: "General Web IDE Settings" }); addSection("General", { sortOrder:100, description: "General Web IDE Settings" });
addSection("Communications", { sortOrder:200, description: "Settings for communicating with the Espruino Board" }); addSection("Communications", { sortOrder:200, description: "Settings for communicating with the Espruino Board" });
addSection("Board", { sortOrder:300, description: "Settings for the Espruino Board itself" }); addSection("Board", { sortOrder:300, description: "Settings for the Espruino Board itself" });
} }
function add(name, options) { function add(name, options) {
@ -5076,6 +5092,7 @@ To add a new serial device, you must add an object to
} }
} }
var portInfo = { port:serialPort };
connectionInfo = undefined; connectionInfo = undefined;
flowControlXOFF = false; flowControlXOFF = false;
currentDevice = portToDevice[serialPort]; currentDevice = portToDevice[serialPort];
@ -5088,7 +5105,6 @@ To add a new serial device, you must add an object to
connectionInfo = cInfo; connectionInfo = cInfo;
connectedPort = serialPort; connectedPort = serialPort;
console.log("Connected", cInfo); console.log("Connected", cInfo);
var portInfo = { port:serialPort };
if (connectionInfo.portName) if (connectionInfo.portName)
portInfo.portName = connectionInfo.portName; portInfo.portName = connectionInfo.portName;
Espruino.callProcessor("connected", portInfo, function() { Espruino.callProcessor("connected", portInfo, function() {
@ -5127,8 +5143,8 @@ To add a new serial device, you must add an object to
sendingBinary = false; sendingBinary = false;
flowControlXOFF = false; flowControlXOFF = false;
Espruino.callProcessor("disconnected", undefined, function() { Espruino.callProcessor("disconnected", portInfo, function() {
disconnectCallback(); disconnectCallback(portInfo);
}); });
}); });
}; };
@ -5608,9 +5624,6 @@ To add a new serial device, you must add an object to
// When code is sent to Espruino, search it for modules and add extra code required to load them // When code is sent to Espruino, search it for modules and add extra code required to load them
Espruino.addProcessor("transformForEspruino", function(code, callback) { Espruino.addProcessor("transformForEspruino", function(code, callback) {
if (Espruino.Config.ROLLUP) {
return loadModulesRollup(code, callback);
}
loadModules(code, callback); loadModules(code, callback);
}); });
@ -5787,24 +5800,8 @@ To add a new serial device, you must add an object to
callback(loadedModuleData.join("\n") + "\n" + code); callback(loadedModuleData.join("\n") + "\n" + code);
}); });
} }
} };
function loadModulesRollup(code, callback) {
rollupTools.loadModulesRollup(code)
.then(generated => {
const minified = generated.code;
console.log('rollup: '+minified.length+' bytes');
// FIXME: needs warnings?
Espruino.Core.Notifications.info('Rollup no errors. Bundling ' + code.length + ' bytes to ' + minified.length + ' bytes');
callback(minified);
})
.catch(err => {
console.log('rollup:error', err);
Espruino.Core.Notifications.error("Rollup errors - Bundling failed: " + String(err).trim());
callback(code);
});
}
Espruino.Core.Modules = { Espruino.Core.Modules = {
init : init init : init
@ -6484,6 +6481,7 @@ To add a new serial device, you must add an object to
(function(){ (function(){
if (typeof acorn == "undefined") { if (typeof acorn == "undefined") {
console.log("pretokenise: needs acorn, disabling."); console.log("pretokenise: needs acorn, disabling.");
return;
} }
function init() { function init() {
@ -6587,11 +6585,12 @@ To add a new serial device, you must add an object to
var tp = "?"; var tp = "?";
if (tk.type.label=="template" || tk.type.label=="string") tp="STRING"; if (tk.type.label=="template" || tk.type.label=="string") tp="STRING";
if (tk.type.label=="num") tp="NUMBER"; if (tk.type.label=="num") tp="NUMBER";
if (tk.type.keyword) tp="ID"; if (tk.type.keyword || tk.type.label=="name") tp="ID";
if (tp=="?" && tk.start+1==tk.end) tp="CHAR";
return { return {
startIdx : tk.start, startIdx : tk.start,
endIdx : tk.end, endIdx : tk.end,
str : code.substr(tk.start, tk.end), str : code.substring(tk.start, tk.end),
type : tp type : tp
}; };
}}; }};
@ -6802,5 +6801,7 @@ Espruino.transform = function(code, options) {
}); });
}; };
if ("undefined"==typeof document) Espruino.init();
if ("undefined"!=typeof module) if ("undefined"!=typeof module)
module.exports = Espruino; module.exports = Espruino;

View File

@ -1,5 +1,6 @@
var appJSON = []; // List of apps and info from apps.json var appJSON = []; // List of apps and info from apps.json
var appsInstalled = []; // list of app JSON var appsInstalled = []; // list of app JSON
var appSortInfo = {}; // list of data to sort by, from appdates.csv { created, modified }
var files = []; // list of files on Bangle var files = []; // list of files on Bangle
var DEFAULTSETTINGS = { var DEFAULTSETTINGS = {
pretokenise : true, pretokenise : true,
@ -19,6 +20,19 @@ httpGet("apps.json").then(apps=>{
refreshFilter(); refreshFilter();
}); });
httpGet("appdates.csv").then(csv=>{
document.querySelector(".sort-nav").classList.remove("hidden");
csv.split("\n").forEach(line=>{
var l = line.split(",");
appSortInfo[l[0]] = {
created : Date.parse(l[1]),
modified : Date.parse(l[2])
};
});
}).catch(err=>{
console.log("No recent.csv - app sort disabled");
});
// =========================================== Top Navigation // =========================================== Top Navigation
function showChangeLog(appid) { function showChangeLog(appid) {
var app = appNameToApp(appid); var app = appNameToApp(appid);
@ -182,11 +196,12 @@ function showTab(tabname) {
// =========================================== Library // =========================================== Library
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value); var chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.attributes.filterid.value);
var hash = window.location.hash ? window.location.hash.slice(1) : ''; var hash = window.location.hash ? window.location.hash.slice(1) : '';
var activeFilter = !!~chips.indexOf(hash) ? hash : ''; var activeFilter = !!~chips.indexOf(hash) ? hash : '';
var currentSearch = ''; var activeSort = '';
var currentSearch = activeFilter ? '' : hash;
function refreshFilter(){ function refreshFilter(){
var filtersContainer = document.querySelector("#librarycontainer .filter-nav"); var filtersContainer = document.querySelector("#librarycontainer .filter-nav");
@ -194,6 +209,12 @@ function refreshFilter(){
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active'); if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active');
else filtersContainer.querySelector('.chip[filterid]').classList.add('active'); else filtersContainer.querySelector('.chip[filterid]').classList.add('active');
} }
function refreshSort(){
var sortContainer = document.querySelector("#librarycontainer .sort-nav");
sortContainer.querySelector('.active').classList.remove('active');
if(activeSort) sortContainer.querySelector('.chip[sortid="'+activeSort+'"]').classList.add('active');
else sortContainer.querySelector('.chip[sortid]').classList.add('active');
}
function refreshLibrary() { function refreshLibrary() {
var panelbody = document.querySelector("#librarycontainer .panel-body"); var panelbody = document.querySelector("#librarycontainer .panel-body");
var visibleApps = appJSON; var visibleApps = appJSON;
@ -211,6 +232,13 @@ function refreshLibrary() {
visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch)); visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch));
} }
if (activeSort) {
visibleApps = visibleApps.slice(); // clone the array so sort doesn't mess with original
if (activeSort=="created" || activeSort=="modified") {
visibleApps = visibleApps.sort((a,b) => appSortInfo[b.id][activeSort] - appSortInfo[a.id][activeSort]);
} else throw new Error("Unknown sort type "+activeSort);
}
panelbody.innerHTML = visibleApps.map((app,idx) => { panelbody.innerHTML = visibleApps.map((app,idx) => {
var appInstalled = appsInstalled.find(a=>a.id==app.id); var appInstalled = appsInstalled.find(a=>a.id==app.id);
var version = getVersionInfo(app, appInstalled); var version = getVersionInfo(app, appInstalled);
@ -580,12 +608,22 @@ filtersContainer.addEventListener('click', ({ target }) => {
}); });
var librarySearchInput = document.querySelector("#searchform input"); var librarySearchInput = document.querySelector("#searchform input");
librarySearchInput.value = currentSearch;
librarySearchInput.addEventListener('input', evt => { librarySearchInput.addEventListener('input', evt => {
currentSearch = evt.target.value.toLowerCase(); currentSearch = evt.target.value.toLowerCase();
refreshLibrary(); refreshLibrary();
}); });
var sortContainer = document.querySelector("#librarycontainer .sort-nav");
sortContainer.addEventListener('click', ({ target }) => {
if (target.classList.contains('active')) return;
activeSort = target.getAttribute('sortid') || '';
refreshSort();
refreshLibrary();
window.location.hash = activeFilter;
});
// =========================================== About // =========================================== About
if (window.location.host=="banglejs.com") { if (window.location.host=="banglejs.com") {

View File

@ -37,7 +37,10 @@ function httpGet(url) {
}); });
oReq.addEventListener("error", () => reject()); oReq.addEventListener("error", () => reject());
oReq.addEventListener("abort", () => reject()); oReq.addEventListener("abort", () => reject());
oReq.open("GET", url); oReq.open("GET", url, true);
oReq.onerror = function () {
reject("HTTP Request failed");
};
oReq.send(); oReq.send();
}); });
} }