forked from FOSS/BangleApps
243 lines
9.9 KiB
JavaScript
243 lines
9.9 KiB
JavaScript
/*
|
|
* SnepWatch - Bangle JS 2 Port
|
|
* JoppyFurr 2023
|
|
*/
|
|
|
|
{
|
|
Graphics.prototype.setFontTerminus_14 = function (scale) {
|
|
g.setFontCustom (atob ("AB/oJhGIZBf4AAAAAgUB/8AQBAAAGDgWCYRiFwQAABhoBiGIYhd4AAABgKBIIhCP/AAAPiiGIYhiGHgAAA/khiGIYhB4AAAgCAIHhiYOAAAAHeiGIYhiF3gAAB4IRhGEYSfwAEAoOjEyMXBQCAA="), 48, 8, 10);
|
|
};
|
|
|
|
Graphics.prototype.setFontTerminus_18 = function (scale) {
|
|
/* TODO: Strip out unused characters - Eg, encode zero-width */
|
|
g.setFontCustom (E.toString (require ("heatshrink").decompress (atob ("AH4A/AH4AnoEHBpcgB4MAwEBgECgEEgEIItf/4UEokQxFBiMCkkEn/wCY0CgMIgU//hHCiAQGh0GkEUoFIwMRgkiiFD4GACQkwmGAiMIBwNEkA7Bh98EoqPBiUAog6BgkQiEgv/4CIvjhFBiGCkMEoUIwkRg/ACQv8IoIODHYVAj43FuC4EgfEjEI4EeOA334RZEHYc9SosD8EIkMQoUgwlBhGCkH/QQoA/AH6VD/+IkDHBkGAoMAwUA//gEq8/+GAiMAkUAokAxEBhkMCIkB//iBwkQgUQhEfG7Ef/0hgFCgGEgMIgUAgip/AAM4gEIgEQQwVH/4HBAFEH/+EgEGcwIDCmEAwDtB4CVERgIBGDoOAcB8P/jgHhEEbYMgiFAkGAnylrnkwwkRhEiiFEkGIoMMvhKFgD+Dh//XYMggFAJVd//AxBgOAhkAjEAAYMEn6XFAH4A/AE8PgMhgVCgmEhMIiUQj/gCIkf/0EgkIhEQiEgkFAoF/EgsH+AOFwGBgMBggSFj/ABwkCgQsBfwQAEv8BiMCkUEokIxERiEeoBvXJQUSkEkoFIwGRgMj/+ACIcB//gFoOAgMAgUALQMHJQwACLIM3/kAhC6bO4I0DhUAkUAwQsBCpBEBx//CwQAagbgBLAUQgEggB3Bv6CEcBh1BAAk//jgIDoJKZQQJMBJQpJFACodCh/+JQbRHUQLwFQ4U/8BKFH4PAgFggEwgOAhkACIoAmJQUggVAgmAhMAiU//AiVO4MEiEEoEFgFEoFBAYMAqCOCgE+gC6BAIkH/Ef8E/wAOBAAI="))), 32, 10, 15);
|
|
};
|
|
|
|
Graphics.prototype.setFontDigits = function (scale) {
|
|
g.setFontCustom (E.toString (require ("heatshrink").decompress (atob ("AEE//4AC4EDAof8Bid8EQMB+ED8/AAwMfgE+BisD8AMEnwIBBisHBAQMCCoYMUh+ABgn4AoIMUTbIMGAH4AhdoKOC8DtBAwTYBBiQHBBgbtCBi1/VAf/84FE/wMSFYZNDAAY4EBmoA7n7PCg/+gf/BQV/8AMSvgLCh+D8/AAwX4nwMWj6OBBgUB+EDBiysBBgasCBiwIDAYMHCoQMTRwP4dIc//+ABiz/8AAbpD//AewcP/gMTdIaOEAAT2EBiLCdBn7pDeQKOC/+D/6bDBiIA/YwbyCAATyDBikH4IMEvkfBi0PwAME/EBBiwDBBgYDCBi0+BAZrB8AMXv7tBAAXnAon+BiQrBAH0HIYP8AgPAJoXAbAIMTvgDCSYPnCASTCBn4M2gE/RQIQDS4QMSf/gAEgJLBeQcfAoRNBBiUD8BwD8E+RwgMSdP4MhUgTpFewYMVAHprCAAZrCBnf//wMDh//WoQMTn/waYn/GYgMRh7GDBgLTDBid/FQQMC/4qBBioA/QoX/wf/OAKSB/AIBOAIMSRwKxDZ4SxCnwM/BmibZBgwA/JoREDJoJRBBiqFDRwkD8CbDBn4MWeQIMEvj6DBh6OCAAOARwQAB/CbDBiAA/AH4ACh/8gEH/wGB/6xBv7YBBiYA/AH4A=="))), 48, 40, 52);
|
|
};
|
|
|
|
Graphics.prototype.setFontOutline = function (scale) {
|
|
g.setFontCustom (E.toString (require ("heatshrink").decompress (atob ("ADMD/4AC/kAoALDkEEAocCBhkH4AGDn2DEgP+h/8jEgC4WAoOCC4UIggMJnwMEgkfgAMDoIIBBgcQBhV8BgkInwMECoYMCEQQMI/AMEiF8BgcBOwQMDQYQMHn/wv59B4IdCVoXgVDEAn7JDVogA/ACEB+AECXwLtBRwUQfQIMCbAIMLSwIMEoImDoL6CBgg4DBg0X4KbD4IkCAAUYKQoMSi4lDEwILEHAhnDBmKoBAGUD/4ECv/goDPChEBgkASAWAiAMLi/ABgX4gODEwUQvkYkCzCglBwQyCiFEBhMB+AMEkH4BgcChEEBgmABhMD8AMEoPwBgasCBggIBBhEHfQIMDwfgBgcICoQMCEQYMGn//Bgd8EwPAF4VAgABBBgKbBAIIMPEwKvCnz/vAAcDbgUP/hLBdIMBW4L2DiECBhh4BBgc+wYmEbgabEAATpEBgibDWpoM/BhrcBPoP/4N8M4cD8D0BogZBTYUEBAKoCBhYmEEQIA1n/waYjyCAAT/CBh59BBgcInwMEwUgBgkiBhX4BgkQvgMDgMEgQMEoAMJgPwBgkg/AMDAYQMEwAMJa4IMEoKFBBgUEiAVDBgIiDBg0H4LaB8H//yQEgEYAokBBiUXEoIAC/z/ygF/G4PAcoKQDbAKdEbAIMSn2D//4BgN8jAQCUAQDBDoKgBBn4MqPoP/4IMBg6oBgfoCASXCDoVAS4IdDBhAdDZYYAzj44B//8c4QACKQIBBeQYMMnwMDn0Ej/4n4mBiFBTYkEiB9DBg18BgkYEwLp/BjWDUQPBvkAcofgCwL2DSQT2CAoQMMgYmBEQIAyLQIADNYQADPAQM0h//9AMCgIBBBgcQgC1CBhibBBglAEwi1BwAzDoEIcAgMEXIIMDiAmCBgI3BHgQMCaYYMGn/wBgYoBcAYoCOgYMBAIIMOg//+D/zAAZ4B/E///8SQMAogLBkCSBggIBOAIMLg54BvgMBn2DEwjPCXwVBAYK+DBn4MoPAJ9B//BQoKKCgfgVDEAEwgiBAGUDG4pEDAAJNBKIIMOPAkAnyFDTYkEiCbDAYIM/Bh18BgkInwMEwT6DBgMgBgzcB/kf/EgvitDAgKbBTIabCUwYMMEwIAB/D/wAH4AG//AgF/8EAiECgEIgMAAIK+BwDYBBiQmGAH4A5"))), 48, 40, 52);
|
|
};
|
|
|
|
var snepwatch_tick_timeout;
|
|
var snepwatch_hrm_timeout;
|
|
var snepwatch_hrm_show_timeout;
|
|
var heart_rate = 0;
|
|
var heart_rate_time = 0;
|
|
|
|
/* Load settings */
|
|
var settings = Object.assign ({
|
|
/* Default Values */
|
|
outline_r: 1,
|
|
outline_g: 0,
|
|
outline_b: 0,
|
|
fill_r: 0.5,
|
|
fill_g: 0,
|
|
fill_b: 0,
|
|
bg_r: 0,
|
|
bg_g: 0,
|
|
bg_b: 0,
|
|
text: 1,
|
|
}, require ('Storage').readJSON ("snepwatch.json", true) || {});
|
|
|
|
/*
|
|
* Tick once per minute.
|
|
*/
|
|
let snepwatch_tick_queue = function () {
|
|
if (snepwatch_tick_timeout) {
|
|
clearTimeout (snepwatch_tick_timeout);
|
|
}
|
|
|
|
snepwatch_tick_timeout = setTimeout (function () {
|
|
snepwatch_tick_timeout = undefined;
|
|
snepwatch_tick ();
|
|
}, 60000 - (Date.now () % 60000));
|
|
};
|
|
|
|
|
|
/*
|
|
* Draw the heart rate sensor reading.
|
|
* The reading is only shown if it is from within the last 10 seconds.
|
|
* Assumes the Terminus_18 font is already selected.
|
|
*/
|
|
let draw_heart_rate = function () {
|
|
let heart_rate_string = "--";
|
|
let hrm_show = false;
|
|
|
|
/* As we are about to show the heart rate,
|
|
* previously set timers are considered invalid */
|
|
if (snepwatch_hrm_show_timeout) {
|
|
clearTimeout (snepwatch_hrm_show_timeout);
|
|
}
|
|
|
|
/* Only show the heart rate if the measurement is recent */
|
|
if (heart_rate_time > Date.now () - 10000) {
|
|
hrm_show = true;
|
|
heart_rate_string = "" + heart_rate;
|
|
}
|
|
|
|
g.clearRect (17, 160, 88, 175);
|
|
g.setColor (0 + settings.text, 0 + settings.text, 0 + settings.text);
|
|
g.drawString (heart_rate_string, 17, 160);
|
|
|
|
/* If the heart rate was shown, check back when the reading
|
|
* would become stale so that it can be cleared. */
|
|
if (hrm_show) {
|
|
snepwatch_hrm_show_timeout = setTimeout (function () {
|
|
snepwatch_hrm_show_timeout = undefined;
|
|
draw_heart_rate ();
|
|
}, heart_rate_time + 10000 - Date.now ());
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
* Called once per minute.
|
|
*
|
|
* Updates the time, date, and battery level.
|
|
*/
|
|
let snepwatch_tick = function () {
|
|
/* Data */
|
|
let days = [ "Sun ", "Mon ", "Tue ", "Wed ", "Thu ", "Fri ", "Sat " ];
|
|
let months = [ " Jan", " Feb", " Mar", " Apr", " May", " June", " July", " Aug", " Snep", " Oct", " Nov", " Dec"];
|
|
let date = new Date ();
|
|
let charge_level = E.getBattery ();
|
|
|
|
/* Clear */
|
|
g.reset ();
|
|
g.setBgColor (settings.bg_r, settings.bg_g, settings.bg_b);
|
|
g.clear ();
|
|
|
|
/* Battery level - Note, '%' is encoded as ':' */
|
|
let battery_text = ((charge_level < 10) ? "0" : "") + charge_level + ":";
|
|
if (charge_level <= 16) {
|
|
g.setColor (1, 0, 0);
|
|
} else {
|
|
g.setColor (0, 0 + settings.text, 1);
|
|
}
|
|
g.setFont ("Terminus_14");
|
|
g.drawString (battery_text, 2, 2);
|
|
|
|
/* Date */
|
|
let day = days [ date.getDay () ];
|
|
let dd = date.getDate ();
|
|
dd = ((dd < 10) ? "0" : "") + dd;
|
|
let month = months [ date.getMonth () ];
|
|
|
|
let date_text = day + dd + month;
|
|
if (date_text.length < 11) {
|
|
date_text = " " + date_text;
|
|
}
|
|
g.setColor (0 + settings.text, 0 + settings.text, 0 + settings.text);
|
|
g.setFont ("Terminus_18");
|
|
g.drawString (date_text, 65, 2);
|
|
|
|
/* Time */
|
|
let hours = date.getHours ();
|
|
let minutes = date.getMinutes ();
|
|
let time_hh = ((hours < 10) ? "0" : "") + hours;
|
|
let time_mm = ((minutes < 10) ? "0" : "") + minutes;
|
|
g.setColor (settings.fill_r, settings.fill_g, settings.fill_b);
|
|
g.setFont ("Digits");
|
|
g.drawString (time_hh, -2, 60);
|
|
g.drawString (":", 71, 55);
|
|
g.drawString (time_mm, 98, 60);
|
|
|
|
g.setColor (settings.outline_r, settings.outline_g, settings.outline_b);
|
|
g.setFont ("Outline");
|
|
g.drawString (time_hh, -2, 60);
|
|
g.drawString (":", 71, 55);
|
|
g.drawString (time_mm, 98, 60);
|
|
|
|
|
|
/* Steps so far for the day */
|
|
let steps = Bangle.getHealthStatus ('day').steps;
|
|
let steps_string = "" + steps;
|
|
if (steps >= 1000) {
|
|
steps_string = steps_string.slice (0, -3) + "," + steps_string.slice (-3);
|
|
}
|
|
|
|
g.setFont("Terminus_18");
|
|
/* With dark text, use blue for the step symbol.
|
|
With light text, use green for the step symbol. */
|
|
g.setColor (0, 0 + settings.text, 1 - settings.text);
|
|
g.drawString ("{", 2, 144); /* Arrows */
|
|
g.setColor (1, 0, 0);
|
|
g.drawString ("|", 2, 160); /* Heart */
|
|
g.setColor (0 + settings.text, 0 + settings.text, 0 + settings.text);
|
|
g.drawString (steps_string, 17, 144);
|
|
draw_heart_rate ();
|
|
|
|
/* Queue up the next tick */
|
|
snepwatch_tick_queue ();
|
|
};
|
|
|
|
|
|
/* Callback for when the backlight state changes */
|
|
let display_cb = lock => {
|
|
if (lock) {
|
|
/* The backlight may not run for long enough to get a good reading.
|
|
Wait 15 seconds with the backlight off before disabling the sensor. */
|
|
snepwatch_hrm_timeout = setTimeout (function () {
|
|
snepwatch_hrm_timeout = undefined;
|
|
Bangle.setHRMPower (false, "snepwatch");
|
|
}, 15000);
|
|
} else {
|
|
if (snepwatch_hrm_timeout) {
|
|
clearTimeout (snepwatch_hrm_timeout);
|
|
snepwatch_hrm_timeout = undefined;
|
|
}
|
|
Bangle.setHRMPower (true, "snepwatch");
|
|
}
|
|
};
|
|
|
|
/* Callback for the heart rate monitor */
|
|
let heart_rate_cb = hrm => {
|
|
if (hrm.bpm > 0 && hrm.confidence > 50) {
|
|
heart_rate = hrm.bpm;
|
|
heart_rate_time = Date.now ();
|
|
}
|
|
|
|
g.setFont("Terminus_18");
|
|
draw_heart_rate ();
|
|
};
|
|
|
|
let previous_theme = g.theme;
|
|
g.setTheme ( { bg:"#000", fg:"#fff", dark:true } );
|
|
|
|
/* Initial call, will tick once per minute */
|
|
snepwatch_tick ();
|
|
Bangle.on ('lock', display_cb);
|
|
Bangle.on ('HRM', heart_rate_cb);
|
|
|
|
/* Use a swipe to show the widgets */
|
|
Bangle.loadWidgets ();
|
|
require ("widget_utils").swipeOn ();
|
|
|
|
/* Allow for Fast Loading */
|
|
Bangle.setUI ( { mode:"clock", remove:function () {
|
|
if (snepwatch_tick_timeout) {
|
|
if (snepwatch_tick_timeout) {
|
|
clearTimeout (snepwatch_tick_timeout);
|
|
}
|
|
if (snepwatch_hrm_timeout) {
|
|
clearTimeout (snepwatch_hrm_timeout);
|
|
}
|
|
if (snepwatch_hrm_show_timeout) {
|
|
clearTimeout (snepwatch_hrm_show_timeout);
|
|
}
|
|
Bangle.removeListener('lcdPower', display_cb);
|
|
Bangle.removeListener('HRM', heart_rate_cb);
|
|
Bangle.setHRMPower (false, "snepwatch");
|
|
delete Graphics.prototype.setFontTerminus_14;
|
|
delete Graphics.prototype.setFontTerminus_18;
|
|
delete Graphics.prototype.setFontDigits;
|
|
delete Graphics.prototype.setFontOutline;
|
|
g.setTheme (previous_theme);
|
|
require ("widget_utils").show();
|
|
}
|
|
} } );
|
|
}
|