From 5dad6caa1d4765d755f23f7731b5f86e45ae7a04 Mon Sep 17 00:00:00 2001 From: Eric Woodward Date: Sat, 5 Jun 2021 16:39:30 -0400 Subject: [PATCH] Add Mystic Dock v1.00 --- apps.json | 15 ++ apps/mysticdock/ChangeLog | 1 + apps/mysticdock/README.md | 43 +++++ apps/mysticdock/mystic-dock-app.js | 247 ++++++++++++++++++++++++ apps/mysticdock/mystic-dock-boot.js | 1 + apps/mysticdock/mystic-dock-icon.js | 1 + apps/mysticdock/mystic-dock-settings.js | 48 +++++ apps/mysticdock/mystic-dock.png | Bin 0 -> 1823 bytes 8 files changed, 356 insertions(+) create mode 100644 apps/mysticdock/ChangeLog create mode 100644 apps/mysticdock/README.md create mode 100644 apps/mysticdock/mystic-dock-app.js create mode 100644 apps/mysticdock/mystic-dock-boot.js create mode 100644 apps/mysticdock/mystic-dock-icon.js create mode 100644 apps/mysticdock/mystic-dock-settings.js create mode 100644 apps/mysticdock/mystic-dock.png diff --git a/apps.json b/apps.json index 89be44fcf..01fa1c5e2 100644 --- a/apps.json +++ b/apps.json @@ -3258,5 +3258,20 @@ {"name":"gbtwist.app.js","url":"app.js"}, {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "mysticdock", + "name": "Mystic Dock", + "icon": "mystic-dock.png", + "version":"1.0", + "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", + "tags": "dock", + "type":"dock", + "readme": "README.md", + "storage": [ + {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, + {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, + {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, + {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} + ] } ] diff --git a/apps/mysticdock/ChangeLog b/apps/mysticdock/ChangeLog new file mode 100644 index 000000000..34fe53627 --- /dev/null +++ b/apps/mysticdock/ChangeLog @@ -0,0 +1 @@ +1.00: First published version. diff --git a/apps/mysticdock/README.md b/apps/mysticdock/README.md new file mode 100644 index 000000000..09e81ba09 --- /dev/null +++ b/apps/mysticdock/README.md @@ -0,0 +1,43 @@ +# Mystic Dock for Bangle.js + +A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line. + +## Features + +- Screensaver-like dock mode while charging (displays the current time for 8 seconds and a blank screen for 2, changing text placement with each draw) +- 24 or 12-hour time (adjustable via the Settings menu) +- Variable colors (also in the Settings) +- Interactive watchface display (use upper and lower watch-buttons to activate it and rotate between values at the bottom) +- International localization of watchface date (which can be disabled via the Settings if memory becomes an issue) +- Automatic watchface reload when unplugged (toggleable via the Settings menu) +- Rotates display 90 degrees if it detects it is sideways (for use in a charging dock) + +When in interactive display mode, the bottom line rotates between the following items: + +- Current time zone +- Battery charge level +- Device ID (derived from the last 4 of the MAC) +- Memory usage +- Firmware version + + +## Inspirations + +- [Bluetooth Dock](https://github.com/espruino/BangleApps/tree/master/apps/bluetoothdock) +- [CLI Clock](https://github.com/espruino/BangleApps/tree/master/apps/cliock) +- [Dev Clock](https://github.com/espruino/BangleApps/tree/master/apps/dclock) +- [Digital Clock](https://github.com/espruino/BangleApps/tree/master/apps/digiclock) +- [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock) +- [Simplest Clock](https://github.com/espruino/BangleApps/tree/master/apps/simplest) + +Icon adapted from [this one](https://publicdomainvectors.org/en/free-clipart/Digital-clock-display-vector-image/10845.html) and [this one](https://publicdomainvectors.org/en/free-clipart/Vector-image-of-power-manager-icon/20141.html) from [Public Domain Vectors](https://publicdomainvectors.org). + + +## Changelog + +- 1.00: First published version. (June 2021) + + +## Author + +Eric Wooodward https://itsericwoodward.com/ diff --git a/apps/mysticdock/mystic-dock-app.js b/apps/mysticdock/mystic-dock-app.js new file mode 100644 index 000000000..2e6fdafc5 --- /dev/null +++ b/apps/mysticdock/mystic-dock-app.js @@ -0,0 +1,247 @@ +/** + * Mystic Dock for Bangle.js + * + * + Original Author: Eric Wooodward https://itsericwoodward.com/ + * + see README.md for details + */ + +/* jshint esversion: 6 */ + +const timeFontSize = 6; +const dataFontSize = 2; +const font = "6x8"; + +const xyCenter = g.getWidth() / 2; + +const ypos = [ + 45, // Time + 105, // Date + 145, // Symbol + 210 // Info +]; + +const settings = require('Storage').readJSON('mysticdock.json', 1) || + require('Storage').readJSON('mysticclock.json', 1) || {}; +const colors = ['white', 'blue', 'green', 'purple', 'red', 'teal', 'other']; +const color = settings.color ? colors[settings.color] : 0; + +const yposMax = 190; +const yposMin = 60; +let y = yposMax; + +let lastButtonPressTime; +let wasInActiveMode = false; + + +const infoData = { + '*GMT_MODE': { + calc: () => (new Date()).toString().split(" ")[5], + }, + BATT_MODE: { + calc: () => `BATT: ${E.getBattery()}%`, + }, + ID_MODE: { + calc: () => { + const val = NRF.getAddress().split(":"); + return `ID: ${val[4]}${val[5]}`; + }, + }, + MEM_MODE: { + calc: () => { + const val = process.memory(); + return `MEM: ${Math.round(val.usage * 100 / val.total)}%`; + }, + }, + VER_MODE: { + calc: () => `FW: ${process.env.VERSION}`, + }, +}; +const infoList = Object.keys(infoData).sort(); +let infoMode = infoList[0]; + + +function setColor() { + const colorCommands = { + white: () => g.setColor(1, 1, 1), + blue: () => g.setColor(0, 0, 1), + green: () => g.setColor(0, 1, 0), + purple: () => g.setColor(1, 0, 1), + red: () => g.setColor(1, 0, 0), + teal: () => g.setColor(0, 1, 1), + other: () => g.setColor(1, 1, 0) + }; + + // default if value unknown + if (!color || !colorCommands[color]) return colorCommands.white(); + return colorCommands[color](); +} + + +function drawInfo() { + if (infoData[infoMode] && infoData[infoMode].calc) { + // clear info + g.setColor(0, 0, 0); + g.fillRect(0, ypos[3] - 8, 239, ypos[3] + 25); + + // draw info + g.setFont(font, dataFontSize); + setColor(); + g.drawString((infoData[infoMode].calc()), xyCenter, ypos[3], true); + } +} + +function drawImage() { + setColor(); + g.drawPoly([xyCenter - 100, ypos[2], xyCenter + 100, ypos[2], xyCenter, ypos[2] + 30], true); +} + +function drawClock() { + + // default draw styles + g.reset(); + + // get date + const d = new Date(); + const dLocal = d.toString().split(" "); + + const minutes = (`0${d.getMinutes()}`).substr(-2); + const seconds = (`0${d.getSeconds()}`).substr(-2); + + const useLocale = !settings.useLocale; + + let hours = (`0${d.getHours()}`).substr(-2); + let meridian = ""; + + if (d.getSeconds() % 10 === 0) { + y = Math.floor(Math.random() * (yposMax - yposMin)) + yposMin; + } + + // drawSting centered + g.setFontAlign(0, 0); + + // setup color + setColor(); + + if (settings.use12Hour) { + hours = parseInt(hours, 10); + meridian = 'AM'; + if (hours === 0) { + hours = 12; + } + else if (hours >= 12) { + meridian = 'PM'; + if (hours > 12) hours -= 12; + } + hours = (' ' + hours).substr(-2); + } + + g.setFont(font, timeFontSize); + + if (lastButtonPressTime && ((d.getTime() - lastButtonPressTime) / 1000) < 5) { + + // clear screen when switching modes + if (!wasInActiveMode) { + g.clear(); + wasInActiveMode = true; + } + + // draw clock in center w/ seconds + // show date (locale'd, based on settings) + // show info line below it + g.drawString(`${hours}${(d.getSeconds() % 2) ? ' ' : ':'}${minutes}`, xyCenter - 15, ypos[0], true); + g.setFont(font, dataFontSize); + + if (settings.use12Hour) { + g.drawString(seconds, xyCenter + 97, ypos[0] - 10, true); + g.drawString(meridian, xyCenter + 97, ypos[0] + 10, true); + } + else { + g.drawString(seconds, xyCenter + 97, ypos[0] + 10, true); + } + + // draw DoW, name of month, date, year + g.setFont(font, dataFontSize); + g.drawString([ + useLocale ? require('locale').dow(d, 1) : dLocal[0], + useLocale ? require('locale').month(d, 1) : dLocal[1], + d.getDate(), + d.getFullYear() + ].join(' '), xyCenter, ypos[1], true); + + drawInfo(); + drawImage(); + } + else if (d.getSeconds() % 10 === 8) { + g.clear(); + wasInActiveMode = false; + } + else if (d.getSeconds() % 10 !== 9) { + // clear screen when switching modes + if (wasInActiveMode) { + g.clear(); + wasInActiveMode = false; + } + g.drawString(`${hours}${(d.getSeconds() % 2) ? ' ' : ':'}${minutes}`, xyCenter - (settings.use12Hour ? 15 : 0), y, true); + g.setFont(font, dataFontSize); + if (settings.use12Hour) { + g.drawString(meridian, xyCenter + 97, y + 10, true); + } + g.drawString(`BATT: ${E.getBattery() === 100 ? '100' : ('0' + E.getBattery()).substr(-2)}%`, xyCenter, y + 35, true); + } + + g.flip(); +} + + +function nextInfo() { + lastButtonPressTime = Date.now(); + let idx = infoList.indexOf(infoMode); + + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } +} + + +function prevInfo() { + lastButtonPressTime = Date.now(); + let idx = infoList.indexOf(infoMode); + + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } +} + + +if (Bangle.getAccel().x < -0.7) { + g.setRotation(3); // assume watch in charge cradle +} + +g.clear(); + +setInterval(drawClock, 1000); +drawClock(); + +if (Bangle.isCharging()) { + Bangle.on("charging", isCharging => { + const reloadOnUplug = !settings.reloadOnUplug; + + if (!isCharging && reloadOnUplug) load(); + }); +} + +// show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); + +// change to "active mode" and rotate through info when the buttons are pressed +setWatch(() => { + nextInfo(); + drawClock(); +}, BTN3, { repeat: true }); + +setWatch(() => { + prevInfo(); + drawClock(); +}, BTN1, { repeat: true }); diff --git a/apps/mysticdock/mystic-dock-boot.js b/apps/mysticdock/mystic-dock-boot.js new file mode 100644 index 000000000..7cb7fa8a4 --- /dev/null +++ b/apps/mysticdock/mystic-dock-boot.js @@ -0,0 +1 @@ +Bangle.on("charging", isCharging => { if (isCharging) load("mysticdock.app.js"); }); diff --git a/apps/mysticdock/mystic-dock-icon.js b/apps/mysticdock/mystic-dock-icon.js new file mode 100644 index 000000000..527825dd7 --- /dev/null +++ b/apps/mysticdock/mystic-dock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBIf4A6g93u9gs4DCBBIAFu9ms9wAYYIJAAt2FAN2BYMHEwIIIAAkGBQV3AYNns1mBAwXGg4KCIgYTEBAZ2JCYQABBBIXJQoRcCBA0GDQpPCBAUGuwTBBAwfCUwgTDMoVmBA8GQIIXGWoJ9DBA4vHAAIOBcoYIHC4xqCCQR2BBBEGJAKSGAH4Adb4SIBDCYXCUwQwVDCjJCXYS/CDh4SCAAoxPDA72CPaQdCTB57CLgQYCGCIdFJJ4QFTIQXUGwpHQJAapQI4qQPCIqtDVCQECMCR5BJgN2bSArCuACCbSIRCIobZQOgZMCgx4OJIjvCCyAYCCYJMBYB4zHC6oA/AE4=")) diff --git a/apps/mysticdock/mystic-dock-settings.js b/apps/mysticdock/mystic-dock-settings.js new file mode 100644 index 000000000..7bfda1c0f --- /dev/null +++ b/apps/mysticdock/mystic-dock-settings.js @@ -0,0 +1,48 @@ +// make sure to enclose the function in parentheses +(function (back) { + + const settings = require('Storage').readJSON('mysticdock.json',1)||{}; + const colors = ['White', 'Blue', 'Green', 'Purple', 'Red', 'Teal', 'Yellow']; + const offon = ['Off','On']; + const onoff = ['On','Off']; + + function save(key, value) { + settings[key] = value; + require('Storage').writeJSON('mysticdock.json',settings); + } + + const appMenu = { + '': {'title': 'Dock Settings'}, + '< Back': back, + 'Color': { + value: 0|settings['color'], + min:0, + max:6, + format: m => colors[m], + onchange: m => {save('color', m)} + }, + '12 Hour Clock': { + value: 0|settings['use12Hour'], + min:0, + max:1, + format: m => offon[m], + onchange: m => {save('use12Hour', m)} + }, + 'Reload on Unplug': { + value: 0|settings['reloadOnUplug'], + min:0, + max:1, + format: m => onoff[m], + onchange: m => {save('reloadOnUplug', m)} + }, + 'Use Locale': { + value: 0|settings['useLocale'], + min:0, + max:1, + format: m => onoff[m], + onchange: m => {save('useLocale', m)} + }, + }; + E.showMenu(appMenu) + +}) diff --git a/apps/mysticdock/mystic-dock.png b/apps/mysticdock/mystic-dock.png new file mode 100644 index 0000000000000000000000000000000000000000..4c0dce770cbb8f4a948f5899d1683f3cd8d92aeb GIT binary patch literal 1823 zcmV+)2jKXLP)17B88s4*dWC02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00xIiL_t(&-tC!xj9pa`$3HXY zzWw1AREp4oK-mJZKhy#?#qzUlO6yiykVt?KO-zx0s_=(IqXBIb6QiV2i#8T2H8Caz zL#a~R(q9w|*jmM`rnQ!~v{XuwE)Cm2*}Z4RKknOo_rAV%*WIwMO?Hx--22YG-*dim zX6DSy6KTddgGD*RWtc#{CFzsiIk{&8I&#m=Kuhkq3wY-P#Hl6HjMuzAiH9}88{RAR zUZu{G>jRRFe;!YECWY~2U*a;O3g^>!SX+hjPRaG_Ihj__8SsOS-4QD|&nNg1$2aG2CJTC{lFm#?wwH*Hr*I^P zb63FkJ9cLrpb0gDT&~!X6#r?3pKeh6HAm(N(hUA{hu3r8JSf<@+@viamkahxEaGfZ zY`-IO1scJBQHAqO!B)qCJF(m*NVDLpa+?!C#wzv?N3Ij>U~W=?nSz`x__L0DS>Ohc z+XZP7d}R)&0i;#&GLBc{d`^%Ng0J&I5V&6OHw<>>e!Lds9KqKca)sir6eo~rf**F+ z3gO!ca;2baG_&^CXf=(}MMsgayP~e!sOpsQ= z|LujZWxT*{gQm>fXgU>+LmSALli=*i9A3U8$W(<0bUK_X*!U30=~og-T|Gh_?uo?Jiuo_Ml1GQB-|gcJzm&inXULyhd)7F z65&$0Ghnaaa6M8Rw!L&dZL?H~263xuDauf-_36Wf5PT!%@e7;t)Ku{~cZo?SKpy zG{Xdj3z}2Z3JV08B((=S!4@05R$l_AcH)nLKB9zMk}O52&Y50!;?gvW9DX_y=#e=> zSQyapH7w4+wngH1BfN0B5-59%{6Go!1mq(pnw1}O?5T)-%d$*#o}hP^PtXzI?}G1j z?AHdXybc6vIVJukh4*T_a2(=qQ@U^IqI+Z)-E+H$Psc3b z1LRUAS(YFRr9L}Qkl7F(PS8o(TXm%mb>oko2rcs^D>+A1lDN1bV)w>Ehhu0DvbEsb zE19Bgi8U7w;Ey}$t=(BxS}tX+LgPEU!yObaFz7KJ*d z5)HZg!~J;Qz!q3WqDKkmN&N_TuGcA_>gPhP0sUe{>$ctMNZ`fFzsuJd;41)X4`rAwHH?3i2BQ$@&Cl z*Kqjzgm8QLn2r?Bh7}32u>Z?Cp(Y-byaaxhqgN^;bo_107StlpDCks0CWCKr!iofK)n4t}vxfiB zu?>b?CCCK=0yr}86JeC26UzSbVL@jFJQzITuwy8bx&Fu{*&ipq+hKKX0rv=DQ6;&vb|YSs z4skX*SeXFf#{v0-5}%0p!*+%d=v@u}cXQzM>I?aR;>2)gZkWJe