2023-08-11 10:46:34 +00:00
|
|
|
{
|
|
|
|
/* Configuration
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
const settings = Object.assign({
|
|
|
|
buzzOnCharge: true,
|
|
|
|
monthFirst: true,
|
|
|
|
twentyFourH: true,
|
|
|
|
showAmPm: false,
|
2023-08-18 15:53:46 +00:00
|
|
|
showSeconds: true,
|
|
|
|
showWeather: false,
|
2023-08-11 10:46:34 +00:00
|
|
|
stepGoal: 10000,
|
|
|
|
stepBar: true,
|
|
|
|
weekBar: true,
|
|
|
|
mondayFirst: true,
|
|
|
|
dayBar: true,
|
|
|
|
}, require('Storage').readJSON('edgeclk.settings.json', true) || {});
|
|
|
|
|
|
|
|
/* Runtime Variables
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
let startTimeout;
|
|
|
|
let drawInterval;
|
|
|
|
|
|
|
|
let lcdPower = true;
|
|
|
|
let charging = Bangle.isCharging();
|
|
|
|
|
|
|
|
const font = atob('AA////wDwDwDwD////AAAAAAAAwAwA////AAAAAAAA8/8/wzwzwzwz/z/zAAAA4H4HwDxjxjxj////AAAA/w/wAwAwD/D/AwAwAAAA/j/jxjxjxjxjx/x/AAAA////xjxjxjxjx/x/AAAAwAwAwAwAwA////AAAAAA////xjxjxjxj////AAAA/j/jxjxjxjxj////AAAAAAAAAAMMMMAAAAAAAAAAAAAAABMOMMAAAAAAAAAABgBgDwDwGYGYMMMMAAAAAAGYGYGYGYGYGYAAAAAAMMMMGYGYDwDwBgBgAAAA4A4Ax7x7xgxg/g/gAAAA//gBv9shshv9gF/7AAAA////wwwwwwww////AAAA////xjxjxjxj////AAAA////wDwDwDwD4H4HAAAA////wDwDwD4Hf+P8AAAA////xjxjxjxjwDwDAAAA////xgxgxgxgwAwAAAAA////wDwDwzwz4/4/AAAA////BgBgBgBg////AAAAAAwDwD////wDwDAAAAAAAAwPwPwDwD////AAAAAA////DwH4OccO4HwDAAAA////ADADADADADADAAAA////YAGAGAYA////AAAA////MADAAwAM////AAAA////wDwDwDwD////AAAA////xgxgxgxg/g/gAAAA/+/+wGwOwOwO////AAAA////xgxgxwx8/v/jAAAA/j/jxjxjxjxjx/x/AAAAwAwAwA////wAwAwAAAAA////ADADADAD////AAAA/w/8AOAHAHAO/8/wAAAA////AGAYAYAG////AAAAwD4PecH4H4ec4PwDAAAAwA4AeBH/H/eA4AwAAAAAwPwfw7xzzj3D+D8DAAAAAAAAAAAA////wDAAAAAAAAAABgBgBgBgAAAAAAAAAAwD////AAAAAAAAAAAAAwDwPA8A8APADwAwAAAAAAAAAAAAAAAAAAAAAA');
|
|
|
|
|
|
|
|
const iconSize = [19, 26];
|
|
|
|
const plugIcon = atob('ExoBBxwA44AccAOOAHHAf/8P/+H//D//h//w//4P/4H/8B/8Af8ABwAA4AAcAAOAAHAADgABwAA4AAcAAOAAHAA=');
|
|
|
|
const stepIcon1 = atob('ExoBAfAAPgAHwAD4AB8AAAAB/wD/8D//Bn9wz+cZ/HM/hmfwAP4AAAAD+AD/gBxwB48A4OA8HgcBwcAcOAOGADA=');
|
|
|
|
const stepIcon2 = atob('ExoBAfAAPgMHwfD4dx8ccAcH/8B/8Af8AH8AD+AB/AA/gAfwAP4AAAAD+AD/gBxwB48A4OA8HgcBwcAcOAOGADA=');
|
|
|
|
|
|
|
|
|
|
|
|
/* Draw Functions
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
const drawAll = function () {
|
|
|
|
const date = new Date();
|
|
|
|
|
|
|
|
drawDate(date);
|
|
|
|
if (settings.showSeconds) drawSecs(date);
|
|
|
|
drawTime(date);
|
|
|
|
drawLower();
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawLower = function (stepsOnlyCount) {
|
|
|
|
if (charging) {
|
|
|
|
drawCharge();
|
|
|
|
} else {
|
|
|
|
drawSteps(stepsOnlyCount);
|
|
|
|
}
|
2023-08-18 15:51:14 +00:00
|
|
|
|
|
|
|
drawWeather();
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawWeather = function () {
|
|
|
|
if (!settings.showWeather){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
|
|
|
g.setFontAlign(1, 1); // right bottom
|
|
|
|
|
|
|
|
try{
|
|
|
|
const weather = require('weather');
|
|
|
|
const w = weather.get();
|
|
|
|
let temp = parseInt(w.temp-273.15);
|
|
|
|
temp = temp < 0 ? '\\' + String(temp*-1) : String(temp);
|
|
|
|
|
|
|
|
g.drawString(temp, g.getWidth()-40, g.getHeight() - 1, true);
|
2023-08-19 05:18:53 +00:00
|
|
|
|
|
|
|
// clear icon area in case weather condition changed
|
|
|
|
g.clearRect(g.getWidth()-40, g.getHeight()-30, g.getWidth(), g.getHeight());
|
2023-08-19 05:21:12 +00:00
|
|
|
weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 14);
|
2023-08-18 15:51:14 +00:00
|
|
|
|
|
|
|
} catch(e) {
|
2023-08-19 05:22:56 +00:00
|
|
|
g.drawString("???", g.getWidth()-3, g.getHeight() - 1, true);
|
2023-08-18 15:51:14 +00:00
|
|
|
}
|
2023-08-11 10:46:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const drawDate = function (date) {
|
|
|
|
const top = 30;
|
|
|
|
g.reset();
|
|
|
|
|
|
|
|
// weekday
|
|
|
|
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
|
|
|
g.setFontAlign(-1, -1); // left top
|
|
|
|
g.drawString(date.toString().slice(0,3).toUpperCase(), 0, top + 12, true);
|
|
|
|
|
|
|
|
// date
|
|
|
|
g.setFontAlign(1, -1); // right top
|
|
|
|
// Note: to save space first and last two lines of ASCII are left out.
|
|
|
|
// That is why '-' is assigned to '\' and ' ' (space) to '_'.
|
|
|
|
if (settings.monthFirst) {
|
|
|
|
g.drawString((date.getMonth()+1).toString().padStart(2, '_')
|
|
|
|
+ '\\'
|
|
|
|
+ date.getDate().toString().padStart(2, 0),
|
|
|
|
g.getWidth(), top + 12, true);
|
|
|
|
} else {
|
|
|
|
g.drawString('_'
|
|
|
|
+ date.getDate().toString().padStart(2, 0)
|
|
|
|
+ '\\'
|
|
|
|
+ (date.getMonth()+1).toString(),
|
|
|
|
g.getWidth(), top + 12, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// line/progress bar
|
|
|
|
if (settings.weekBar) {
|
|
|
|
let weekday = date.getDay();
|
|
|
|
if (settings.mondayFirst) {
|
|
|
|
if (weekday === 0) { weekday = 7; }
|
|
|
|
} else {
|
|
|
|
weekday += 1;
|
|
|
|
}
|
|
|
|
drawBar(top, weekday/7);
|
|
|
|
} else {
|
|
|
|
drawLine(top);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawTime = function (date) {
|
|
|
|
const top = 72;
|
|
|
|
g.reset();
|
|
|
|
|
|
|
|
const h = date.getHours();
|
|
|
|
g.setFontCustom(font, 48, 10, 1024 + 12); // triple size (2<<9)
|
|
|
|
g.setFontAlign(-1, -1); // left top
|
|
|
|
g.drawString((settings.twentyFourH ? h : (h % 12 || 12)).toString().padStart(2, 0),
|
|
|
|
0, top+12, true);
|
|
|
|
g.setFontAlign(0, -1); // center top
|
|
|
|
g.drawString(':', g.getWidth()/2, top+12, false);
|
|
|
|
const m = date.getMinutes();
|
|
|
|
g.setFontAlign(1, -1); // right top
|
|
|
|
g.drawString(m.toString().padStart(2, 0),
|
|
|
|
g.getWidth(), top+12, true);
|
|
|
|
|
|
|
|
if (settings.showAmPm) {
|
|
|
|
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
|
|
|
g.setFontAlign(1, 1); // right bottom
|
|
|
|
g.drawString(h < 12 ? 'AM' : 'PM', g.getWidth(), g.getHeight() - 1, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.dayBar) {
|
|
|
|
drawBar(top, (h*60+m)/1440);
|
|
|
|
} else {
|
|
|
|
drawLine(top);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawSecs = function (date) {
|
|
|
|
g.reset();
|
|
|
|
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
|
|
|
g.setFontAlign(1, 1); // right bottom
|
|
|
|
g.drawString(date.getSeconds().toString().padStart(2, 0), g.getWidth(), g.getHeight() - 1, true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawSteps = function (onlyCount) {
|
|
|
|
g.reset();
|
|
|
|
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
|
|
|
g.setFontAlign(-1, 1); // left bottom
|
|
|
|
|
|
|
|
const steps = Bangle.getHealthStatus('day').steps;
|
2023-08-18 15:51:14 +00:00
|
|
|
const toKSteps = settings.showWeather ? 1000 : 100000;
|
|
|
|
g.drawString((steps < toKSteps ? steps.toString() : ((steps / 1000).toFixed(0) + 'K')).padEnd(5, '_'),
|
2023-08-12 08:34:47 +00:00
|
|
|
iconSize[0] + 6, g.getHeight() - 1, true);
|
2023-08-11 10:46:34 +00:00
|
|
|
|
|
|
|
if (onlyCount === true) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const progress = steps / settings.stepGoal;
|
|
|
|
if (settings.stepBar) {
|
|
|
|
drawBar(g.getHeight() - 38, progress);
|
|
|
|
} else {
|
|
|
|
drawLine(g.getHeight() - 38);
|
|
|
|
}
|
|
|
|
|
|
|
|
// icon
|
|
|
|
if (progress < 1) {
|
|
|
|
g.drawImage(stepIcon1, 0, g.getHeight() - iconSize[1]);
|
|
|
|
} else {
|
|
|
|
g.drawImage(stepIcon2, 0, g.getHeight() - iconSize[1]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawCharge = function () {
|
|
|
|
g.reset();
|
|
|
|
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
|
|
|
g.setFontAlign(-1, 1); // left bottom
|
|
|
|
|
|
|
|
const charge = E.getBattery();
|
|
|
|
g.drawString(charge.toString().padEnd(5, '_'), iconSize[0] + 6, g.getHeight() - 1, true);
|
|
|
|
|
|
|
|
drawBar(g.getHeight() - 38, charge / 100);
|
|
|
|
g.drawImage(plugIcon, 0, g.getHeight() - 26);
|
|
|
|
};
|
|
|
|
|
|
|
|
const drawBar = function (top, progress) {
|
2023-08-12 08:34:47 +00:00
|
|
|
// draw frame
|
|
|
|
g.drawRect(0, top, g.getWidth() - 1, top + 5);
|
|
|
|
g.drawRect(1, top + 1, g.getWidth() - 2, top + 4);
|
|
|
|
// clear bar area
|
|
|
|
g.clearRect(2, top + 2, g.getWidth() - 3, top + 3);
|
|
|
|
// draw bar
|
|
|
|
const barLen = progress >= 1 ? g.getWidth() : (g.getWidth() - 4) * progress;
|
|
|
|
if (barLen < 1) return;
|
|
|
|
g.drawLine(2, top + 2, barLen + 2, top + 2);
|
|
|
|
g.drawLine(2, top + 3, barLen + 2, top + 3);
|
2023-08-11 10:46:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const drawLine = function (top) {
|
|
|
|
const width = g.getWidth();
|
2023-08-12 08:34:47 +00:00
|
|
|
g.drawLine(0, top + 2, width, top + 2);
|
|
|
|
g.drawLine(0, top + 3, width, top + 3);
|
2023-08-11 10:46:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Event Handlers
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
const onSecondInterval = function () {
|
|
|
|
const date = new Date();
|
|
|
|
drawSecs(date);
|
|
|
|
if (date.getSeconds() === 0) {
|
|
|
|
onMinuteInterval();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onMinuteInterval = function () {
|
|
|
|
const date = new Date();
|
|
|
|
drawTime(date);
|
|
|
|
drawLower(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onMinuteIntervalStarter = function () {
|
|
|
|
drawInterval = setInterval(onMinuteInterval, 60000);
|
|
|
|
startTimeout = null;
|
|
|
|
onMinuteInterval();
|
|
|
|
};
|
|
|
|
|
|
|
|
const onLcdPower = function (on) {
|
|
|
|
lcdPower = on;
|
|
|
|
if (on) {
|
|
|
|
drawAll();
|
|
|
|
startTimers();
|
|
|
|
} else {
|
|
|
|
stopTimers();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onMidnight = function () {
|
|
|
|
if (!lcdPower) return;
|
|
|
|
drawDate(new Date());
|
|
|
|
// Lower part (steps/charge) will be updated every minute.
|
|
|
|
// However, to save power while on battery only step count will get updated.
|
|
|
|
// This will update icon and progress bar as well:
|
|
|
|
if (!charging) drawSteps();
|
2023-08-18 15:51:14 +00:00
|
|
|
drawWeather();
|
2023-08-11 10:46:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const onHealth = function () {
|
|
|
|
if (!lcdPower || charging) return;
|
|
|
|
// This will update progress bar and icon:
|
|
|
|
drawSteps();
|
2023-08-18 15:51:14 +00:00
|
|
|
drawWeather();
|
2023-08-11 10:46:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const onLock = function (locked) {
|
|
|
|
if (locked) return;
|
|
|
|
drawLower();
|
|
|
|
};
|
|
|
|
|
|
|
|
const onCharging = function (isCharging) {
|
|
|
|
charging = isCharging;
|
|
|
|
if (isCharging && settings.buzzOnCharge) Bangle.buzz();
|
|
|
|
if (!lcdPower) return;
|
|
|
|
drawLower();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Lifecycle Functions
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
const registerEvents = function () {
|
|
|
|
// This is for original Bangle.js; version two has always-on display:
|
|
|
|
Bangle.on('lcdPower', onLcdPower);
|
|
|
|
|
2023-08-12 08:34:47 +00:00
|
|
|
// Midnight event is triggered when health data is reset and a new day begins:
|
2023-08-11 10:46:34 +00:00
|
|
|
Bangle.on('midnight', onMidnight);
|
|
|
|
|
|
|
|
// Health data is published via 10 mins interval:
|
|
|
|
Bangle.on('health', onHealth);
|
|
|
|
|
|
|
|
// Lock event signals screen (un)lock:
|
|
|
|
Bangle.on('lock', onLock);
|
|
|
|
|
|
|
|
// Charging event signals when charging status changes:
|
|
|
|
Bangle.on('charging', onCharging);
|
|
|
|
};
|
|
|
|
|
|
|
|
const deregisterEvents = function () {
|
|
|
|
Bangle.removeListener('lcdPower', onLcdPower);
|
|
|
|
Bangle.removeListener('midnight', onMidnight);
|
|
|
|
Bangle.removeListener('health', onHealth);
|
|
|
|
Bangle.removeListener('lock', onLock);
|
|
|
|
Bangle.removeListener('charging', onCharging);
|
|
|
|
};
|
|
|
|
|
|
|
|
const startTimers = function () {
|
|
|
|
if (drawInterval) return;
|
|
|
|
if (settings.showSeconds) {
|
|
|
|
drawInterval = setInterval( onSecondInterval, 1000);
|
|
|
|
} else {
|
|
|
|
startTimeout = setTimeout(onMinuteIntervalStarter, (60 - new Date().getSeconds()) * 1000);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const stopTimers = function () {
|
|
|
|
if (startTimeout) clearTimeout(startTimeout);
|
|
|
|
if (!drawInterval) return;
|
|
|
|
clearInterval(drawInterval);
|
|
|
|
drawInterval = null;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Startup Process
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
g.clear();
|
|
|
|
drawAll();
|
|
|
|
startTimers();
|
|
|
|
registerEvents();
|
|
|
|
|
|
|
|
Bangle.setUI({mode: 'clock', remove: function() {
|
|
|
|
stopTimers();
|
|
|
|
deregisterEvents();
|
|
|
|
}});
|
|
|
|
Bangle.loadWidgets();
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
}
|