Merge branch 'master' of git://github.com/espruino/BangleApps into espruino-master

pull/421/head
Johan Bové 2020-05-15 13:32:50 +02:00
commit ba4c2493c0
14 changed files with 418 additions and 18 deletions

View File

@ -120,7 +120,7 @@
"id": "welcome",
"name": "Welcome",
"icon": "app.png",
"version":"0.08",
"version":"0.09",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator": true,
@ -859,7 +859,7 @@
"id": "ncstart",
"name": "NCEU Startup",
"icon": "start.png",
"version":"0.05",
"version":"0.06",
"description": "NodeConfEU 2019 'First Start' Sequence",
"tags": "start,welcome",
"storage": [
@ -2376,5 +2376,19 @@
"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}
]
}
]

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

@ -6,3 +6,4 @@
Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings
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() {
let s = require('Storage').readJSON('ncstart.json', 1)
|| require('Storage').readJSON('setting.json', 1)
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('ncstart.app.js')) {
let s = require('Storage').readJSON('ncstart.json', 1) || {};
if (!s.welcomed) {
setTimeout(() => {
s.welcomed = true
require('Storage').write('ncstart.json', s)
require('Storage').write('ncstart.json', {welcomed: true})
load('ncstart.app.js')
})
}

View File

@ -8,3 +8,6 @@
Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings
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) {
load();
}
}, BTN2, {repeat:true,edge:"rising"});
}, BTN2, {repeat:true,edge:"falling"});
setWatch(()=>move(-1), BTN1, {repeat:true});
(function migrateSettings(){

View File

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

View File

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

View File

@ -124,7 +124,7 @@ Espruino.Core.Status = {
hasProgress : function() { return false; },
incrementProgress : function(amt) {}
};
var acorn = (function(){ var exports={};
var acorn = (function(){ var exports={};var module={};
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :