From 3edc3604e6d242998b6752793ed1903c4f143bb7 Mon Sep 17 00:00:00 2001 From: Joppy Furr Date: Mon, 24 Jul 2023 15:59:21 +1200 Subject: [PATCH] snepwatch: Snepwatch release 1.00 --- apps/snepwatch/ChangeLog | 1 + apps/snepwatch/README.md | 17 ++ apps/snepwatch/app.js | 242 ++++++++++++++++++++++++++++ apps/snepwatch/app.png | Bin 0 -> 4404 bytes apps/snepwatch/metadata.json | 18 +++ apps/snepwatch/screenshot-dark.png | Bin 0 -> 3057 bytes apps/snepwatch/screenshot-light.png | Bin 0 -> 2749 bytes apps/snepwatch/settings.js | 81 ++++++++++ apps/snepwatch/snepwatch.img | Bin 0 -> 296 bytes 9 files changed, 359 insertions(+) create mode 100644 apps/snepwatch/ChangeLog create mode 100644 apps/snepwatch/README.md create mode 100644 apps/snepwatch/app.js create mode 100644 apps/snepwatch/app.png create mode 100644 apps/snepwatch/metadata.json create mode 100644 apps/snepwatch/screenshot-dark.png create mode 100644 apps/snepwatch/screenshot-light.png create mode 100644 apps/snepwatch/settings.js create mode 100644 apps/snepwatch/snepwatch.img diff --git a/apps/snepwatch/ChangeLog b/apps/snepwatch/ChangeLog new file mode 100644 index 000000000..fa064b2c0 --- /dev/null +++ b/apps/snepwatch/ChangeLog @@ -0,0 +1 @@ +1.00: Initial release diff --git a/apps/snepwatch/README.md b/apps/snepwatch/README.md new file mode 100644 index 000000000..7bf5ec5cd --- /dev/null +++ b/apps/snepwatch/README.md @@ -0,0 +1,17 @@ +# Snepwatch +![](screenshot-dark.png) +![](screenshot-light.png) + +Features: + * This watch face uses the Terminus font. + * Background, digit-outline, and digit-fill colours are all customizable from the settings menu + * Text can be configured to either black or white + * Today's step count is always shown at the bottom + * Heart-rate is shown when we have a valid reading within the last ten seconds + * The heart rate monitor can be activated by another app running in the background (eg, Recorder) + * Or, the heart rate monitor is activated when you unlock the screen (eg, by pressing the button) + * Sometimes a single unlock-period is not long enough to achieve a usable reading, so a second press can be used + * The heart rate monitor will be returned to deactivated when the screen remains locked for 15 seconds. + * Hidden widget-bar. Swipe down to see the widgets. + * "Sneptember" instead of "September", because snow leopards are excellent :3 + * Fast Loading diff --git a/apps/snepwatch/app.js b/apps/snepwatch/app.js new file mode 100644 index 000000000..833285644 --- /dev/null +++ b/apps/snepwatch/app.js @@ -0,0 +1,242 @@ +/* + * 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(); + } + } } ); +} diff --git a/apps/snepwatch/app.png b/apps/snepwatch/app.png new file mode 100644 index 0000000000000000000000000000000000000000..50eb5c388f4f2d4c91d8bb5bb424753b78c1687a GIT binary patch literal 4404 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 literal 0 HcmV?d00001 diff --git a/apps/snepwatch/screenshot-light.png b/apps/snepwatch/screenshot-light.png new file mode 100644 index 0000000000000000000000000000000000000000..074747423b9d19b3e056620007098e299d32c5dd GIT binary patch literal 2749 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 literal 0 HcmV?d00001 diff --git a/apps/snepwatch/settings.js b/apps/snepwatch/settings.js new file mode 100644 index 000000000..57ae94d52 --- /dev/null +++ b/apps/snepwatch/settings.js @@ -0,0 +1,81 @@ +( + function(back) { + var FILE = "snepwatch.json"; + + /* 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 (FILE, true) || {}); + + function write_settings () { + require ('Storage').writeJSON (FILE, settings); + } + + /* Show the menu */ + var main_menu = { + "" : { "title": "Snepwatch", + back : function() { back (); }}, + "Outline Colour": function () { E.showMenu (outline_menu); }, + "Fill Colour": function () { E.showMenu (fill_menu); }, + "Background Colour": function () { E.showMenu (background_menu); }, + "Text": { value: (settings.text == 1), + format: v => v ? "Light" : "Dark", + onchange: v => { settings.text = v; write_settings (); }}, + }; + + var outline_menu = { + "": { title : "Outline Colour", + back : function() { E.showMenu (main_menu); } }, + "Red": { value: settings.outline_r, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.outline_r = v; write_settings (); }}, + "Green": { value: settings.outline_g, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.outline_g = v; write_settings (); }}, + "Blue": { value: settings.outline_b, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.outline_b = v; write_settings (); }}, + }; + + var fill_menu = { + "" : { title : "Fill Colour", + back : function() { E.showMenu (main_menu); } }, + "Red": { value: settings.fill_r, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.fill_r = v; write_settings (); }}, + "Green": { value: settings.fill_g, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.fill_g = v; write_settings (); }}, + "Blue": { value: settings.fill_b, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.fill_b = v; write_settings (); }}, + }; + + var background_menu = { + "" : { title : "Background Colour", + back : function() { E.showMenu (main_menu); } }, + "Red": { value: settings.bg_r, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.bg_r = v; write_settings (); }}, + "Green": { value: settings.bg_g, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.bg_g = v; write_settings (); }}, + "Blue": { value: settings.bg_b, + min: 0, max: 1, step: 0.5, wrap: true, + onchange: v => { settings.bg_b = v; write_settings (); }}, + }; + + E.showMenu (main_menu); + } +) diff --git a/apps/snepwatch/snepwatch.img b/apps/snepwatch/snepwatch.img new file mode 100644 index 0000000000000000000000000000000000000000..ae0527cbbcabbeea13f1cc9f096f20c538be38f4 GIT binary patch literal 296 zcmb8ou?>VU3jomr=F`1IT^_R`um1h=>S=P4GN;C3dt e{ISfdIl4)=M*R!4*Cds}6Zl_}`jV<$x9SI;lOj6+ literal 0 HcmV?d00001