2020-05-02 20:43:16 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
2020-05-15 10:23:52 +00:00
|
|
|
const timeout = settings.timeout || 20;
|
2020-05-14 18:46:34 +00:00
|
|
|
|
2020-05-02 20:43:16 +00:00
|
|
|
const font7x7 = {
|
2020-05-14 18:46:34 +00:00
|
|
|
"empty": "00000000",
|
2020-05-02 20:43:16 +00:00
|
|
|
"0": "3E61514945433E",
|
|
|
|
"1": "1808080808081C",
|
|
|
|
"2": "7E01013E40407F",
|
|
|
|
"3": "7E01013E01017E",
|
|
|
|
"4": "4141417F010101",
|
|
|
|
"5": "7F40407E01017E",
|
|
|
|
"6": "3E40407E41413E",
|
|
|
|
"7": "3F010202040408",
|
|
|
|
"8": "3E41413E41413E",
|
|
|
|
"9": "3E41413F01013E",
|
|
|
|
};
|
|
|
|
|
|
|
|
const font5x5 = {
|
2020-05-14 18:46:34 +00:00
|
|
|
"empty": "00000000",
|
2020-05-15 10:23:52 +00:00
|
|
|
"-": "0000FF0000",
|
2020-05-02 20:43:16 +00:00
|
|
|
"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 = {
|
2020-05-15 08:33:25 +00:00
|
|
|
blue: {
|
|
|
|
BG: "#0297fe",
|
|
|
|
DARK: "#3b3ce8",
|
|
|
|
LIGHT: "#E9ffff",
|
|
|
|
},
|
|
|
|
orange: {
|
|
|
|
BG: "#f7b336",
|
|
|
|
DARK: "#ac721e",
|
|
|
|
LIGHT: "#f6fc0f",
|
|
|
|
}
|
2020-05-02 20:43:16 +00:00
|
|
|
};
|
|
|
|
|
2020-05-15 08:33:25 +00:00
|
|
|
let selectedColor = "blue";
|
2020-05-15 10:23:52 +00:00
|
|
|
let displayTimeoutRef, sensorTimeoutRef;
|
2020-05-15 08:33:25 +00:00
|
|
|
|
2020-05-02 20:43:16 +00:00
|
|
|
// Example
|
|
|
|
// binToHex(["0111110", "1000000", "1000000", "1111110", "1000001", "1000001", "0111110"])
|
2020-05-15 08:46:21 +00:00
|
|
|
function binToHex(bins) {
|
2020-05-02 20:43:16 +00:00
|
|
|
return bins.map(bin => ("00" + (parseInt(bin, 2).toString(16))).substr(-2).toUpperCase()).join("");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Example
|
|
|
|
// hexToBin("3E40407E41413E")
|
|
|
|
function hexToBin(hexStr) {
|
2020-05-14 18:46:34 +00:00
|
|
|
const regEx = new RegExp("..", "g");
|
|
|
|
const bin = hexStr
|
2020-05-23 21:32:33 +00:00
|
|
|
.replace(regEx, el => el + '_')
|
|
|
|
.slice(0, -1)
|
|
|
|
.split('_')
|
|
|
|
.map(hex => ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8));
|
2020-05-14 18:46:34 +00:00
|
|
|
|
|
|
|
return bin;
|
2020-05-02 20:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function drawPixel(opts) {
|
|
|
|
g.setColor(opts.color);
|
|
|
|
g.fillRect(opts.x, opts.y, opts.x + opts.w, opts.y + opts.h);
|
|
|
|
}
|
|
|
|
|
2020-05-15 08:46:21 +00:00
|
|
|
function drawGrid(pos, dims, charAsBin, opts) {
|
2020-05-02 20:43:16 +00:00
|
|
|
const defaultOpts = {
|
2020-05-14 18:46:34 +00:00
|
|
|
pxlW: 5,
|
|
|
|
pxlH: 5,
|
2020-05-02 20:43:16 +00:00
|
|
|
gap: 1,
|
2020-05-15 08:33:25 +00:00
|
|
|
offColor: COLORS[selectedColor].DARK,
|
|
|
|
onColor: COLORS[selectedColor].LIGHT
|
2020-05-02 20:43:16 +00:00
|
|
|
};
|
|
|
|
const pxl = Object.assign({}, defaultOpts, opts);
|
|
|
|
|
2020-05-15 10:23:52 +00:00
|
|
|
for (let rowY = 0; rowY < dims.rows; rowY++) {
|
|
|
|
const y = pos.y + ((pxl.pxlH + pxl.gap) * rowY);
|
2020-05-14 18:46:34 +00:00
|
|
|
|
2020-05-15 10:23:52 +00:00
|
|
|
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;
|
2020-05-02 20:43:16 +00:00
|
|
|
|
|
|
|
drawPixel({
|
2020-05-14 18:46:34 +00:00
|
|
|
x: x,
|
|
|
|
y: y,
|
|
|
|
w: pxl.pxlW,
|
|
|
|
h: pxl.pxlH,
|
2020-05-15 08:46:21 +00:00
|
|
|
color: color,
|
2020-05-02 20:43:16 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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])?
|
2020-05-23 21:32:33 +00:00
|
|
|
hexToBin(fontMap[charArr[i]]):
|
|
|
|
fontMap.empty;
|
2020-05-02 20:43:16 +00:00
|
|
|
|
|
|
|
drawGrid(
|
|
|
|
{x: x + (i * gridWidthTotal), y: y},
|
|
|
|
{rows: rows, cols: cols},
|
2020-05-14 18:46:34 +00:00
|
|
|
charAsBin,
|
2020-05-02 20:43:16 +00:00
|
|
|
{pxlW: pxlW, pxlH: pxlH, gap: gap}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function drawTitles() {
|
|
|
|
g.setColor("#ffffff");
|
|
|
|
g.setFont("6x8");
|
2020-05-14 18:46:34 +00:00
|
|
|
g.drawString("COMPASS", 52, 49);
|
|
|
|
g.drawString("HEART", 122, 49);
|
|
|
|
g.drawString("TIME", 52, 94);
|
|
|
|
g.drawString("DATE", 52, 144);
|
2020-05-02 20:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function drawCompass(lastHeading) {
|
|
|
|
const directions = [
|
|
|
|
'N',
|
|
|
|
'NE',
|
|
|
|
'E',
|
|
|
|
'SE',
|
|
|
|
'S',
|
|
|
|
'SW',
|
|
|
|
'W',
|
|
|
|
'NW'
|
|
|
|
];
|
|
|
|
const cps = Bangle.getCompass();
|
2022-11-01 10:16:28 +00:00
|
|
|
let angle = 360-cps.heading;
|
2020-05-14 18:46:34 +00:00
|
|
|
let heading = angle?
|
2020-05-23 21:32:33 +00:00
|
|
|
directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]:
|
|
|
|
"-- ";
|
2020-05-02 20:43:16 +00:00
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
heading = (heading + " ").slice(0, 3);
|
|
|
|
if (lastHeading != heading) drawFont(heading, "5x5", 40, 67);
|
2020-05-02 20:43:16 +00:00
|
|
|
setTimeout(drawCompass.bind(null, heading), 1000 * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
function drawHeart(hrm) {
|
2020-05-15 10:23:52 +00:00
|
|
|
drawFont((" " + (hrm ? hrm.bpm : "---")).slice(-3), "5x5", 109, 67);
|
2020-05-02 20:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-05-14 18:46:34 +00:00
|
|
|
drawFont(hrs, "7x7", 48, 109);
|
2020-05-02 20:43:16 +00:00
|
|
|
}
|
|
|
|
if (lastMns != mns) {
|
2020-05-14 18:46:34 +00:00
|
|
|
drawFont(mns, "7x7", 124, 109);
|
2020-05-02 20:43:16 +00:00
|
|
|
}
|
|
|
|
|
2020-05-15 08:33:25 +00:00
|
|
|
const color = toggle? COLORS[selectedColor].LIGHT : COLORS[selectedColor].DARK;
|
2020-05-02 20:43:16 +00:00
|
|
|
|
|
|
|
// This should toggle on/off per second
|
|
|
|
drawPixel({
|
|
|
|
color: color,
|
2020-05-14 18:46:34 +00:00
|
|
|
x: 118, y: 118,
|
2020-05-02 20:43:16 +00:00
|
|
|
w: 2, h: 2,
|
|
|
|
});
|
|
|
|
drawPixel({
|
|
|
|
color: color,
|
2020-05-14 18:46:34 +00:00
|
|
|
x: 118, y: 125,
|
2020-05-02 20:43:16 +00:00
|
|
|
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);
|
2020-05-14 19:10:49 +00:00
|
|
|
const mon = locale.month(date).toUpperCase().slice(0, 3);
|
2020-05-02 20:43:16 +00:00
|
|
|
const yr = date.getFullYear().toString().slice(-2);
|
2020-05-14 18:46:34 +00:00
|
|
|
drawFont(dow + " " + dayNum, "5x5", 40, 159);
|
|
|
|
drawFont(mon + " " + yr, "5x5", 40, 189);
|
2020-05-02 20:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(drawDate.bind(null, date.toISOString().split('T')), 1000 * 60);
|
|
|
|
}
|
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
function setSensors(state) {
|
2020-05-15 10:23:52 +00:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
Bangle.setHRMPower(state);
|
|
|
|
Bangle.setCompassPower(state);
|
|
|
|
}
|
|
|
|
|
2020-05-15 08:33:25 +00:00
|
|
|
function drawScreen() {
|
|
|
|
g.setBgColor(COLORS[selectedColor].BG);
|
|
|
|
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
|
|
|
|
|
|
|
// Draw components
|
|
|
|
drawTitles();
|
|
|
|
drawCompass();
|
|
|
|
drawHeart();
|
|
|
|
drawTime();
|
|
|
|
drawDate();
|
|
|
|
}
|
|
|
|
|
2020-05-15 10:23:52 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
// Turn sensors on
|
|
|
|
setSensors(1);
|
|
|
|
|
|
|
|
// Reset screen
|
2020-05-02 20:43:16 +00:00
|
|
|
g.clear();
|
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
// Load and draw widgets
|
2020-05-02 20:43:16 +00:00
|
|
|
Bangle.loadWidgets();
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
// Draw screen
|
2020-05-15 08:33:25 +00:00
|
|
|
drawScreen();
|
2020-05-15 10:23:52 +00:00
|
|
|
resetDisplayTimeout();
|
2020-05-02 20:43:16 +00:00
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
// Setup callbacks
|
2020-05-15 08:33:25 +00:00
|
|
|
Bangle.on('swipe', (sDir) => {
|
|
|
|
selectedColor = selectedColor === "blue" ? "orange" : "blue";
|
2020-05-15 10:23:52 +00:00
|
|
|
resetDisplayTimeout();
|
2020-05-15 08:33:25 +00:00
|
|
|
drawScreen();
|
|
|
|
});
|
|
|
|
|
2020-05-02 20:43:16 +00:00
|
|
|
Bangle.on('HRM', drawHeart);
|
|
|
|
|
2020-05-15 10:23:52 +00:00
|
|
|
setWatch(() => {
|
|
|
|
setSensors(1);
|
|
|
|
resetDisplayTimeout();
|
|
|
|
}, BTN1, {repeat: true, edge: "falling"});
|
|
|
|
|
2020-05-14 18:46:34 +00:00
|
|
|
setWatch(() => {
|
|
|
|
setSensors(0);
|
2020-05-15 10:23:52 +00:00
|
|
|
clearTimers();
|
2020-05-14 18:46:34 +00:00
|
|
|
Bangle.setLCDMode();
|
|
|
|
Bangle.showLauncher();
|
|
|
|
}, BTN2, {repeat: false, edge: "falling"});
|
|
|
|
|
2020-05-15 10:23:52 +00:00
|
|
|
Bangle.on('lcdPower', (on) => {
|
|
|
|
if(on) {
|
|
|
|
resetDisplayTimeout();
|
|
|
|
} else {
|
|
|
|
clearTimers();
|
|
|
|
setSensors(0);
|
|
|
|
}
|
|
|
|
});
|
2020-05-14 18:46:34 +00:00
|
|
|
|
|
|
|
Bangle.on('faceUp', (up) => {
|
|
|
|
if (up && !Bangle.isLCDOn()) {
|
|
|
|
setSensors(1);
|
2020-05-15 10:23:52 +00:00
|
|
|
resetDisplayTimeout();
|
2020-05-14 18:46:34 +00:00
|
|
|
}
|
2022-11-01 10:16:28 +00:00
|
|
|
});
|