From d49a22a990a33c531e08dfd8603d9f85db3aa563 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Sun, 30 May 2021 20:40:56 +0200 Subject: [PATCH 001/193] trex: enabling move left and right --- apps/trex/trex.js | 4 ++-- core | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/trex/trex.js b/apps/trex/trex.js index fe84cb31a..642c10054 100644 --- a/apps/trex/trex.js +++ b/apps/trex/trex.js @@ -9,8 +9,8 @@ g.flip = function() { },0,(240-128)/2,{scale:2}); }; var W = g.getWidth(); -var BTNL = BTN4; -var BTNR = BTN5; +var BTNL = BTN2; +var BTNR = BTN3; var BTNU = BTN1; // Images can be added like this in Espruino v2.00 diff --git a/core b/core index 3f2ff467f..0ba4c2bd5 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 3f2ff467f22b746da94160e59ff89b621601b261 +Subproject commit 0ba4c2bd5503264279222b04a41505471c6622ff From f7dd6badd325cc1dbdecf3a154d98c6ed276da0a Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 2 Jun 2021 23:30:42 +0100 Subject: [PATCH 002/193] kitchen detect missing waypoint.json file --- apps.json | 2 +- apps/arrow/README.md | 4 ++++ apps/kitchen/ChangeLog | 1 + apps/kitchen/README.md | 3 +++ apps/kitchen/annex.js | 29 +++++++++++++++++++++++++++++ apps/kitchen/compass.kit.js | 17 +++-------------- apps/kitchen/kitchen.app.js | 36 +++++++++++++++++------------------- 7 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 apps/kitchen/annex.js diff --git a/apps.json b/apps.json index e4da83e7f..ade2f6502 100644 --- a/apps.json +++ b/apps.json @@ -3085,7 +3085,7 @@ { "id": "kitchen", "name": "Kitchen Combo", "icon": "kitchen.png", - "version":"0.10", + "version":"0.11", "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later", "tags": "tool,outdoors,gps", "type":"clock", diff --git a/apps/arrow/README.md b/apps/arrow/README.md index 3b439711c..4b77dbc42 100644 --- a/apps/arrow/README.md +++ b/apps/arrow/README.md @@ -36,6 +36,10 @@ charge. *BTN3* - invokes calibration ( can be cancelled if pressed accidentally) +## Issues +* detect when calibration data is missing + ## Acknowledgement This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev) + diff --git a/apps/kitchen/ChangeLog b/apps/kitchen/ChangeLog index 71548ec30..ea42a3f11 100644 --- a/apps/kitchen/ChangeLog +++ b/apps/kitchen/ChangeLog @@ -8,3 +8,4 @@ 0.08: Improved error handling for missing firmware features, added template app.kit.js 0.09: Added heart rate monitor app 0.10: Converted Stepo to use direct screen writes, added a Trip Counter feature to stepo +0.11: Detect when waypoints.json is not present, error E-WPT diff --git a/apps/kitchen/README.md b/apps/kitchen/README.md index a829a39b0..d847adabc 100644 --- a/apps/kitchen/README.md +++ b/apps/kitchen/README.md @@ -244,6 +244,7 @@ The following error codes will be displayed if one of the dependancies is not me * E-STEPS - no pedomintor widget has been installed, please install the widpedom or the activepedom widgets * E-CALIB - no compass calibration data was found, see 'Compass Calibration' * E-FW - require firmware 2v08.187 or later to detect gps and compass power status +* E-WPT - missing waypoints.json file ### Issues / Future enhancements @@ -254,3 +255,5 @@ The following error codes will be displayed if one of the dependancies is not me seconds after the LCD goes off. At present I just rely on using the GPSSetup app and set the GPS power mode that I want. * Add a small graph to the heart rate monitor app +* Add a facility to call the Arrow calibration process +* Maybe create waypoints.json file if missing diff --git a/apps/kitchen/annex.js b/apps/kitchen/annex.js new file mode 100644 index 000000000..d789f5d0c --- /dev/null +++ b/apps/kitchen/annex.js @@ -0,0 +1,29 @@ +// annexed code that might be worth keeping + +/***************************************************************************** + +Screen Buffer Object that can be shared between faces + +Making into a Class like this means we allocate the memory once +and avoid fragmenting the memory when we switch in and out of faces + +******************************************************************************/ + +function BUF() { + this.pal4color = new Uint16Array([0x0000,0xFFFF,0x7BEF,0xAFE5],0,2); // b,w,grey,greenyellow + this.pal4red = new Uint16Array([0x0000,0xFFFF,0xF800,0xAFE5],0,2); // b,w,red,greenyellow + this.buf = Graphics.createArrayBuffer(120,120,2,{msb:true}); +} + +BUF.prototype.flip = function(x,y) { + g.drawImage({width:120,height:120,bpp:2, buffer:this.buf.buffer, palette:this.pal4color}, x, y); + this.buf.clear(); +} + +BUF.prototype.flip_red = function(x,y) { + g.drawImage({width:120,height:120,bpp:2, buffer:this.buf.buffer, palette:this.pal4red}, x, y); + this.buf.clear(); +} + +let bufObj = new BUF(); + diff --git a/apps/kitchen/compass.kit.js b/apps/kitchen/compass.kit.js index 530ba021c..b20cdce2c 100644 --- a/apps/kitchen/compass.kit.js +++ b/apps/kitchen/compass.kit.js @@ -17,13 +17,12 @@ } function init(gps,sw, hrm) { - showMem("compass init() START"); gpsObject = gps; intervalRefSec = undefined; bearing = 0; // always point north if GPS is off heading = 0; oldHeading = 0; - previous = {hding:"-", bs:"-", dst:"-", wp_name:"-", course:999}; + resetPrevious(); loc = require("locale"); CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; getWaypoint(); @@ -34,12 +33,9 @@ */ if (!Bangle.isCompassOn()) Bangle.setCompassPower(1); gps.determineGPSState(); - - showMem("compass init() END"); } function freeResources() { - showMem("compass freeResources() START"); gpsObject = undefined; intervalRefSec = undefined; previous = undefined; @@ -50,7 +46,6 @@ CALIBDATA = undefined; wp = undefined; if (Bangle.isCompassOn !== undefined && Bangle.isCompassOn()) Bangle.setCompassPower(0); - showMem("compass freeResources() END"); } function startTimer() { @@ -67,12 +62,6 @@ if (Bangle.isCompassOn !== undefined && Bangle.isCompassOn()) Bangle.setCompassPower(0); } - function showMem(msg) { - var val = process.memory(); - var str = msg + " " + Math.round(val.usage*100/val.total) + "%"; - log_debug(str); - } - function onButtonShort(btn) { log_debug("onButtonShort()"); if (gpsObject.getState() !== gpsObject.GPS_RUNNING) return; @@ -206,12 +195,12 @@ drawCompass(dir, 0xFFC0); // yellow oldHeading = dir; } - + if (gpsObject.getState() === gpsObject.GPS_RUNNING) { drawGPSData(); } else { drawCompassHeading(); - } + } } // only used when acting as compass with GPS off diff --git a/apps/kitchen/kitchen.app.js b/apps/kitchen/kitchen.app.js index c3f7bd74d..fcaa048bb 100644 --- a/apps/kitchen/kitchen.app.js +++ b/apps/kitchen/kitchen.app.js @@ -33,10 +33,10 @@ function nextFace(){ // when you feel the buzzer you know you have done a long press function longPressCheck() { Bangle.buzz(); - debug_log("long PressCheck() buzz"); + debug_log("BUZZ, long press"); if (pressTimer) { clearInterval(pressTimer); - debug_log("clear pressTimer 2"); + debug_log("CLEAR pressTimer 2"); pressTimer = undefined; } } @@ -48,10 +48,10 @@ function buttonPressed(btn) { } else { firstPress = getTime(); if (pressTimer) { - debug_log("clear pressTimer 1"); + debug_log("CLEAR pressTimer 1"); clearInterval(pressTimer); } - debug_log("set pressTimer 1"); + debug_log("SET pressTimer 1"); pressTimer = setInterval(longPressCheck, 1500); } } @@ -60,7 +60,7 @@ function buttonPressed(btn) { function buttonReleased(btn) { var dur = getTime() - firstPress; if (pressTimer) { - debug_log("clear pressTimer 3"); + debug_log("CLEAR pressTimer 3"); clearInterval(pressTimer); pressTimer = undefined; } @@ -256,7 +256,7 @@ GPS.prototype.processFix = function(fix) { this.gpsState = this.GPS_RUNNING; if (!this.last_fix.fix && !(require("Storage").readJSON("setting.json", 1) || {}).quiet) { Bangle.buzz(); // buzz on first position - debug_log("GPS fix buzz"); + debug_log("BUZZ - gps fix"); } this.last_fix = fix; } @@ -303,7 +303,7 @@ GPS.prototype.getWPdistance = function() { //log_debug(this.last_fix); //log_debug(this.wp_current); - if (this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) + if (this.wp_current.name === "E-WPT" || this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) return 0; else return this.calcDistance(this.last_fix, this.wp_current); @@ -313,14 +313,14 @@ GPS.prototype.getWPbearing = function() { //log_debug(this.last_fix); //log_debug(this.wp_current); - if (this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) + if (this.wp_current.name === "E-WPT" || this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) return 0; else return this.calcBearing(this.last_fix, this.wp_current); } GPS.prototype.loadFirstWaypoint = function() { - var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; + var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"E-WPT"}]; this.wp_index = 0; this.wp_current = waypoints[this.wp_index]; log_debug(this.wp_current); @@ -332,7 +332,7 @@ GPS.prototype.getCurrentWaypoint = function() { } GPS.prototype.waypointHasLocation = function() { - if (this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) + if (this.wp_current.name === "E-WPT" || this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) return false; else return true; @@ -340,12 +340,12 @@ GPS.prototype.waypointHasLocation = function() { GPS.prototype.markWaypoint = function() { - if(this.wp_current.name === "NONE") + if(this.wp_current.name === "E-WPT" || this.wp_current.name === "NONE") return; log_debug("GPS::markWaypoint()"); - var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; + var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"E-WPT"}]; this.wp_current = waypoints[this.wp_index]; if (this.waypointHasLocation()) { @@ -360,7 +360,7 @@ GPS.prototype.markWaypoint = function() { } GPS.prototype.nextWaypoint = function(inc) { - var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; + var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"E-WPT"}]; this.wp_index+=inc; if (this.wp_index>=waypoints.length) this.wp_index=0; if (this.wp_index<0) this.wp_index = waypoints.length-1; @@ -731,14 +731,14 @@ function TRIP() { TRIP.prototype.resetTrip = function(steps) { this.tripStart = (0 + steps); - console.log("resetTrip starting=" + this.tripStart); + log_debug("resetTrip starting=" + this.tripStart); } TRIP.prototype.getTrip = function(steps) { let tripSteps = (0 + steps) - this.tripStart; - console.log("getTrip steps=" + steps); - console.log("getTrip tripStart=" + this.tripStart); - console.log("getTrip=" + tripSteps); + log_debug("getTrip steps=" + steps); + log_debug("getTrip tripStart=" + this.tripStart); + log_debug("getTrip=" + tripSteps); return tripSteps; } @@ -758,7 +758,6 @@ Debug Object ******************************************************************************/ -/* function DEBUG() { this.logfile = require("Storage").open("debug.log","a"); } @@ -770,7 +769,6 @@ DEBUG.prototype.log = function(msg) { } debugObj = new DEBUG(); -*/ function debug_log(m) { //debugObj.log(m); From d68a7c66303451b8c6a24cef804ac6cd9d4464e0 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 2 Jun 2021 23:42:22 +0100 Subject: [PATCH 003/193] kitchen remove heart.kit.js from the install config in apps.json --- apps.json | 1 - apps/kitchen/README.md | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index c1e727abd..8e3446543 100644 --- a/apps.json +++ b/apps.json @@ -3146,7 +3146,6 @@ {"name":"stepo.kit.js","url":"stepo.kit.js"}, {"name":"gps.kit.js","url":"gps.kit.js"}, {"name":"digi.kit.js","url":"digi.kit.js"}, - {"name":"heart.kit.js","url":"heart.kit.js"}, {"name":"swatch.kit.js","url":"swatch.kit.js"}, {"name":"compass.kit.js","url":"compass.kit.js"}, {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} diff --git a/apps/kitchen/README.md b/apps/kitchen/README.md index d847adabc..ef8ee826f 100644 --- a/apps/kitchen/README.md +++ b/apps/kitchen/README.md @@ -76,6 +76,9 @@ The following buttons depend on which face is currently in use ## Heart ![](screenshot_heart.jpg) - A simple heart rate monitor, at present the app is just showing the raw value from HRM.bpm +- This is an experimental app and not installed by default. The + heart.kit.js file can be uploaded via the Espruino IDE if you want + to try it out. Then reload the App. - BTN1, long press, turn heart rate monitor on / off ## Waypointer From 5055333b30c7e67b97b600adbb5c7230eb74d1e0 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Fri, 4 Jun 2021 22:47:32 +0100 Subject: [PATCH 004/193] kitchen - corrections to README file --- apps/kitchen/README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/kitchen/README.md b/apps/kitchen/README.md index ef8ee826f..502ec1a7f 100644 --- a/apps/kitchen/README.md +++ b/apps/kitchen/README.md @@ -229,12 +229,12 @@ I have settled on directly writing to the screen using the Graphics object (g.) for the compass App. This creates a bit of flicker when the arrow moves but is more reliable than using the ArrayBuffer. -v0.09: Since adding the heart rate monitor I have noticed that I can -sometimes can a memory error when switch through the Apps back to the -Stepo App. I think this can be cured by statically allocating the -ArrayBuffer for stepo rather than using new everytime you switch back -into the stepo watch face. The problem is that the bangle memory -management / defragmentation is quite slow to run. +v0.09: Since adding the heart rate monitor I have sometimes observed +a low memory error when switching through the Apps back to the Stepo +App. I think this can be cured by statically allocating the +ArrayBuffer for stepo rather than using 'new' everytime you switch +back into the stepo watch face. The problem is that the bangle +memory management / defragmentation is quite slow to run. v0.10: Revisited having a display buffer for the stepo part of the App. Now use direct screen writing as it means less memory allocation and @@ -244,10 +244,13 @@ reduces chance of getting a memory error on switching watch faces. The following error codes will be displayed if one of the dependancies is not met. -* E-STEPS - no pedomintor widget has been installed, please install the widpedom or the activepedom widgets -* E-CALIB - no compass calibration data was found, see 'Compass Calibration' -* E-FW - require firmware 2v08.187 or later to detect gps and compass power status -* E-WPT - missing waypoints.json file +* E-STEPS - no pedomintor widget has been installed, please install + the widpedom or the activepedom widgets +* E-CALIB - no compass calibration data was found, see 'Compass + Calibration' +* E-FW - require firmware 2v08.187 or later to detect gps and compass + power status +* E-WPT - missing waypoints.json file ### Issues / Future enhancements From 5dad6caa1d4765d755f23f7731b5f86e45ae7a04 Mon Sep 17 00:00:00 2001 From: Eric Woodward Date: Sat, 5 Jun 2021 16:39:30 -0400 Subject: [PATCH 005/193] 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 Date: Sat, 5 Jun 2021 16:47:18 -0400 Subject: [PATCH 006/193] Add Mystic Clock v1.00 --- apps.json | 15 ++ apps/mysticclock/ChangeLog | 1 + apps/mysticclock/README.md | 40 ++++ apps/mysticclock/mystic-clock-app.js | 215 ++++++++++++++++++++++ apps/mysticclock/mystic-clock-icon.js | 1 + apps/mysticclock/mystic-clock-settings.js | 41 +++++ apps/mysticclock/mystic-clock.png | Bin 0 -> 981 bytes 7 files changed, 313 insertions(+) create mode 100644 apps/mysticclock/ChangeLog create mode 100644 apps/mysticclock/README.md create mode 100644 apps/mysticclock/mystic-clock-app.js create mode 100644 apps/mysticclock/mystic-clock-icon.js create mode 100644 apps/mysticclock/mystic-clock-settings.js create mode 100644 apps/mysticclock/mystic-clock.png diff --git a/apps.json b/apps.json index 89be44fcf..93d82c5b9 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": "mysticclock", + "name": "Mystic Clock", + "icon": "mystic-clock.png", + "version":"1.00", + "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", + "tags": "clock", + "type":"clock", + "readme": "README.md", + "allow_emulator":true, + "storage": [ + {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, + {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, + {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} + ] } ] diff --git a/apps/mysticclock/ChangeLog b/apps/mysticclock/ChangeLog new file mode 100644 index 000000000..34fe53627 --- /dev/null +++ b/apps/mysticclock/ChangeLog @@ -0,0 +1 @@ +1.00: First published version. diff --git a/apps/mysticclock/README.md b/apps/mysticclock/README.md new file mode 100644 index 000000000..fd5bbb431 --- /dev/null +++ b/apps/mysticclock/README.md @@ -0,0 +1,40 @@ +# Mystic Clock for Bangle.js + +A retro-inspired watchface featuring time, date, and an interactive data display line. + +## Features + +- 24 or 12-hour time (adjustable via the Settings menu) +- Variable colors (also in the Settings) +- Interactive data display line (use upper and lower watch-buttons to rotate between values) +- Cover watch screen with your hand to put it to sleep (the watch, not your hand) +- International localization of date (which can be disabled via the Settings if memory becomes an issue) + +The interactive 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 + +- [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 [Public Domain Vectors](https://publicdomainvectors.org/en/free-clipart/Digital-clock-display-vector-image/10845.html). + + +## Changelog + +- 1.00: First published version. (June 2021) + + +## Author + +Eric Wooodward https://itsericwoodward.com/ diff --git a/apps/mysticclock/mystic-clock-app.js b/apps/mysticclock/mystic-clock-app.js new file mode 100644 index 000000000..22c2c8982 --- /dev/null +++ b/apps/mysticclock/mystic-clock-app.js @@ -0,0 +1,215 @@ +/** + * Mystic Clock 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 yposTime = 75; +const yposDate = 125; +const yposSymbol = 160; +const yposInfo = 220; + +const settings = require('Storage').readJSON('mysticclock.json', 1) || {}; +const colors = ['white', 'blue', 'green', 'purple', 'red', 'teal', 'other']; +const color = settings.color ? colors[settings.color] : 0; + +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 getLocale() { + return require('locale'); +} + +function drawClock() { + + // default draw styles + g.reset(); + + // drawSting centered + g.setFontAlign(0, 0); + + // setup color + setColor(); + + // get date + const d = new Date(); + const dLocal = d.toString().split(" "); + + const useLocale = !settings.useLocale; + + const minutes = (`0${d.getMinutes()}`).substr(-2); + const seconds = (`0${d.getSeconds()}`).substr(-2); + + let hours = (`0${d.getHours()}`).substr(-2); + let meridian = ""; + + 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); + g.drawString(`${hours}${(d.getSeconds() % 2) ? ' ' : ':'}${minutes}`, xyCenter - 15, yposTime, true); + g.setFont(font, dataFontSize); + + if (settings.use12Hour) { + g.drawString(seconds, xyCenter + 97, yposTime - 10, true); + g.drawString(meridian, xyCenter + 97, yposTime + 10, true); + } + else { + g.drawString(seconds, xyCenter + 97, yposTime + 10, true); + } + + // draw DoW, name of month, date, year + g.setFont(font, dataFontSize); + g.drawString([ + useLocale ? getLocale().dow(d, 1) : dLocal[0], + useLocale ? getLocale().month(d, 1) : dLocal[1], + d.getDate(), + d.getFullYear() + ].join(" "), xyCenter, yposDate, true); + +} + +function drawInfo() { + if (infoData[infoMode] && infoData[infoMode].calc) { + // clear info + g.setColor(0, 0, 0); + g.fillRect(0, yposInfo - 8, 239, yposInfo + 25); + + // draw info + g.setFont(font, dataFontSize); + setColor(); + g.drawString((infoData[infoMode].calc()), xyCenter, yposInfo, true); + } +} + +function drawImage() { + setColor(); + g.drawPoly([xyCenter - 100, yposSymbol, xyCenter + 100, yposSymbol, xyCenter, yposSymbol + 30], true); +} + +function drawAll() { + drawClock(); + drawInfo(); + drawImage(); +} + +function nextInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } +} + +function prevInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } +} + + +let secondInterval; + +// handle LCD power state change +Bangle.on('lcdPower', on => { + + // stop running when screen turns off + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + + // start running + if (on) { + secondInterval = setInterval(drawAll, 1000); + drawAll(); // draw immediately + } +}); + +// cover screen to put it to sleep +Bangle.on('touch', (button) => { + if (button === 3 && Bangle.isLCDOn()) Bangle.setLCDPower(false); +}); + +// clean app screen +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// if screen already active, draw now and start interval +if (Bangle.isLCDOn()) { + secondInterval = setInterval(drawAll, 1000); + drawAll(); // draw immediately +} + +// show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); + +// rotate through info when the buttons are pressed +setWatch(() => { + nextInfo(); + drawAll(); +}, BTN3, { repeat: true }); + +setWatch(() => { + prevInfo(); + drawAll(); +}, BTN1, { repeat: true }); diff --git a/apps/mysticclock/mystic-clock-icon.js b/apps/mysticclock/mystic-clock-icon.js new file mode 100644 index 000000000..7415fccd5 --- /dev/null +++ b/apps/mysticclock/mystic-clock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBIf4A/AH4A/AH4A/AH4ALs1msADCA4MGAgQDBBYIAGg93u92s4DBuEAAYN3swDCC5AhBuwMBg4XBuwEBs4dCC49nHgNwCQREBCYNnEYYXHHQQvBAAJZBAgRPEC5IOCu0GM4YLCuCGDAAREBHwtnJ41gDQIXEOAQvBDoZ7CuwjCWwimTJgLCFZojWEbwbWIAH4A/AH4A/AH4A/AH4AFA")) diff --git a/apps/mysticclock/mystic-clock-settings.js b/apps/mysticclock/mystic-clock-settings.js new file mode 100644 index 000000000..2fa0c49c5 --- /dev/null +++ b/apps/mysticclock/mystic-clock-settings.js @@ -0,0 +1,41 @@ +// make sure to enclose the function in parentheses +(function (back) { + + const settings = require('Storage').readJSON('mysticclock.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('mysticclock.json',settings); + } + + const appMenu = { + '': {'title': 'Clock 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)} + }, + '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/mysticclock/mystic-clock.png b/apps/mysticclock/mystic-clock.png new file mode 100644 index 0000000000000000000000000000000000000000..915e2ee32e296ea26a1e96f93813e19730c0002a GIT binary patch literal 981 zcmV;`11kK9P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00TWqL_t(&-tCx8h*nh`#(&TM zzB4)liT0(LA2<d!82Woq6va-?4G$z&UW?-t+O?^E>~KbMBP}FblW@3%q2243Ggb zKnBPF86X2>fDDiUG5}}+&3aMywl#A%*N5e>(7iRfapQNZ&g($Cq6r#vIlJm9!A0;#UEk8v zf+_H4Mt2B*lDY$yfbI-Dnus?8y)5*+padVPY3%G+!$*wPM{kXzY2E-W)Z72HR`^#F z2WoMClSrc_+$Wd-Z4}z5uJ4{UK4@45{>sF*z-LUnRa>VUMLL>b2>27UL1;rwjHnB;2+Q?p-lnj4gHSTig-|@v4nd}Rz3fXUJrVywz(MmL4xDepce%zYWZ@^`0y;5 zC7|5_eKn1P3BIW`UJ$ITX?*L3RT%{S?ZhJmPy&~@0zk|PQyj z(=`S#`F}2ykJA|-17v^ Date: Sat, 29 May 2021 13:04:11 +0200 Subject: [PATCH 007/193] trex: added highscore and setting (for highscore reset) --- apps.json | 8 ++++++-- apps/trex/ChangeLog | 1 + apps/trex/settings.js | 19 +++++++++++++++++++ apps/trex/trex.js | 26 ++++++++++++++++++++++++-- core | 2 +- 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 apps/trex/settings.js diff --git a/apps.json b/apps.json index 89be44fcf..cdfc14d24 100644 --- a/apps.json +++ b/apps.json @@ -362,13 +362,17 @@ { "id": "trex", "name": "T-Rex", "icon": "trex.png", - "version":"0.02", + "version":"0.03", "description": "T-Rex game in the style of Chrome's offline game", "tags": "game", "allow_emulator":true, "storage": [ {"name":"trex.app.js","url":"trex.js"}, - {"name":"trex.img","url":"trex-icon.js","evaluate":true} + {"name":"trex.img","url":"trex-icon.js","evaluate":true}, + {"name":"trex.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"trex.score", "storageFile": true} ] }, { "id": "astroid", diff --git a/apps/trex/ChangeLog b/apps/trex/ChangeLog index 42c1df403..587dec05b 100644 --- a/apps/trex/ChangeLog +++ b/apps/trex/ChangeLog @@ -1 +1,2 @@ 0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast +0.03: Enabled BTN2 and BTN3, added highscore (score is saved to storage and can be reset in app settings menu) diff --git a/apps/trex/settings.js b/apps/trex/settings.js new file mode 100644 index 000000000..67aa9a518 --- /dev/null +++ b/apps/trex/settings.js @@ -0,0 +1,19 @@ +(function (back) { + const menu = { + '': { 'title': 'T-Rex' }, + '< Back': back, + 'Reset Highscore': () => { + E.showPrompt('Reset Highscore?').then((v) => { + let delay = 50; + if (v) { + delay = 500; + E.showMessage('Resetting'); + var f = require('Storage').open('trex.score', 'w'); + f.write('0\n'); + } + setTimeout(() => E.showMenu(menu), delay); + }); + } + }; + E.showMenu(menu); +}); diff --git a/apps/trex/trex.js b/apps/trex/trex.js index 642c10054..0e36ec59a 100644 --- a/apps/trex/trex.js +++ b/apps/trex/trex.js @@ -1,3 +1,13 @@ +function loadHighScore() { + var f = require("Storage").open("trex.score", "r"); + return f.readLine() || 0; +} + +function saveHighScore(score) { + var f = require("Storage").open("trex.score", "w"); + f.write(score + "\n"); +} + greal = g; g.clear(); g = Graphics.createArrayBuffer(120,64,1,{msb:true}); @@ -134,6 +144,8 @@ var IMG = { IMG.rex.forEach(i=>i.transparent=0); IMG.cacti.forEach(i=>i.transparent=0); var cacti, rex, frame; +// displayedHighScore is not updated before restart +var highScore = loadHighScore(), displayedHighScore; function gameStart() { rex = { @@ -152,6 +164,7 @@ function gameStart() { } IMG.ground = { width: 128, height: 3, bpp : 1, buffer : random.buffer }; frame = 0; + displayedHighScore = highScore; setInterval(onFrame, 50); } function gameStop() { @@ -159,8 +172,13 @@ function gameStop() { rex.img = 2; // dead clearInterval(); setTimeout(function() { + // putting saveHighScore here to not delay the frame drawing + if (rex.score > highScore) { + highScore = rex.score; + saveHighScore(highScore); + } setWatch(gameStart, BTNU, {repeat:0,debounce:50,edge:"rising"}); - }, 1000); + }, 800); setTimeout(onFrame, 10); } @@ -190,6 +208,9 @@ function onFrame() { while (cacti.length && cacti[0].x<0) cacti.shift(); } else { g.drawString("Game Over!",(W-g.stringWidth("Game Over!"))/2,20); + if (rex.score > highScore) { + g.drawString("New Record!",(W-g.stringWidth("New Record!"))/2,28); + } } g.drawLine(0,60,239,60); cacti.forEach(c=>g.drawImage(IMG.cacti[c.img],c.x,60-IMG.cacti[c.img].height)); @@ -213,7 +234,8 @@ function onFrame() { var groundOffset = frame&127; g.drawImage(IMG.ground, -groundOffset, 61); g.drawImage(IMG.ground, 128-groundOffset, 61); - g.drawString(rex.score,(W-1)-g.stringWidth(rex.score)); + g.drawString(displayedHighScore,(W-1)-g.stringWidth(displayedHighScore), 0); + g.drawString(rex.score,(W-1)-g.stringWidth(rex.score), 8); g.flip(); } diff --git a/core b/core index 0ba4c2bd5..3f2ff467f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 0ba4c2bd5503264279222b04a41505471c6622ff +Subproject commit 3f2ff467f22b746da94160e59ff89b621601b261 From e409ada3471a8a9cefe389c909a837be56284ede Mon Sep 17 00:00:00 2001 From: Weiming Date: Mon, 7 Jun 2021 16:40:23 -0400 Subject: [PATCH 008/193] Update ChangeLog --- apps/gpsrec/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index 412dbe9d3..c71a29482 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -21,3 +21,4 @@ 0.17: Disable recording if storage is full (fix #574) 0.18: Period counter now uses GPS time rather than counting packets (allows use with GPS Setup) 0.19: Fix memory usage issues inside track viewer app +0.20: Add documentation to explain time needed for getting a time fix From 2dd8844a45f3de68745215e103873ec41bba4a77 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 8 Jun 2021 09:38:08 +0100 Subject: [PATCH 009/193] 0.26: Remove buzz in setUI polyfill (#750) --- apps.json | 2 +- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 19 ++++++++----------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps.json b/apps.json index fdf0b5871..f5b4c47a6 100644 --- a/apps.json +++ b/apps.json @@ -4,7 +4,7 @@ "tags": "tool,system", "type":"bootloader", "icon": "bootloader.png", - "version":"0.25", + "version":"0.26", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "storage": [ {"name":".boot0","url":"boot0.js"}, diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 48e1baa48..37ce0d0ac 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -24,3 +24,4 @@ 0.23: Move to a precalculated .boot0 file which should speed up load time 0.24: Add Bangle.setUI polyfill 0.25: Fix error in 'no clock app' message +0.26: Remove buzz in setUI polyfill (#750) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 9dc90cc9a..9dd49453e 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -97,25 +97,22 @@ if (Bangle.touchandler) { Bangle.removeListener("touch", Bangle.touchHandler); delete Bangle.touchHandler; } -function b() { - try{Bangle.buzz(20);}catch(e){} -} if (!mode) return; else if (mode=="updown") { Bangle.btnWatches = [ - setWatch(function() { b();cb(-1); }, BTN1, {repeat:1}), - setWatch(function() { b();cb(1); }, BTN3, {repeat:1}), - setWatch(function() { b();cb(); }, BTN2, {repeat:1}) + setWatch(function() { cb(-1); }, BTN1, {repeat:1}), + setWatch(function() { cb(1); }, BTN3, {repeat:1}), + setWatch(function() { cb(); }, BTN2, {repeat:1}) ]; } else if (mode=="leftright") { Bangle.btnWatches = [ - setWatch(function() { b();cb(-1); }, BTN1, {repeat:1}), - setWatch(function() { b();cb(1); }, BTN3, {repeat:1}), - setWatch(function() { b();cb(); }, BTN2, {repeat:1}) + setWatch(function() { cb(-1); }, BTN1, {repeat:1}), + setWatch(function() { cb(1); }, BTN3, {repeat:1}), + setWatch(function() { cb(); }, BTN2, {repeat:1}) ]; - Bangle.swipeHandler = d => {b();cb(d);}; + Bangle.swipeHandler = d => {cb(d);}; Bangle.on("swipe", Bangle.swipeHandler); - Bangle.touchHandler = d => {b();cb();}; + Bangle.touchHandler = d => {cb();}; Bangle.on("touch", Bangle.touchHandler); } else throw new Error("Unknown UI mode"); From 3df0e9d64c6eb917b638e1c622585bcad3510c19 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Thu, 10 Jun 2021 20:18:21 +0200 Subject: [PATCH 010/193] remove duplicate omnitrix "readme" key from apps.json (no code change) --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index f5b4c47a6..aee81676b 100644 --- a/apps.json +++ b/apps.json @@ -3212,7 +3212,6 @@ "readme": "README.md", "description": "An Omnitrix Showpiece", "tags": "game", - "readme": "README.md", "storage": [ {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} From 002f861038674221327a50c256bcea881d3e0ab0 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Thu, 10 Jun 2021 20:17:03 +0200 Subject: [PATCH 011/193] barclock: use timeout to tick exactly on the second, instead of interval --- apps.json | 2 +- apps/barclock/ChangeLog | 3 ++- apps/barclock/clock-bar.js | 18 +++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps.json b/apps.json index aee81676b..c10395020 100644 --- a/apps.json +++ b/apps.json @@ -1312,7 +1312,7 @@ { "id": "barclock", "name": "Bar Clock", "icon": "clock-bar.png", - "version":"0.05", + "version":"0.06", "description": "A simple digital clock showing seconds as a bar", "tags": "clock", "type":"clock", diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index 616ee66e9..551926191 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -2,4 +2,5 @@ 0.02: Apply locale, 12-hour setting 0.03: Fix dates drawing over each other at midnight 0.04: Small bugfix -0.05: Clock does not start if app Languages is not installed \ No newline at end of file +0.05: Clock does not start if app Languages is not installed +0.06: Improve accuracy \ No newline at end of file diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index 0f2609298..4bb6c048a 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -12,12 +12,12 @@ date.setMonth(1, 3) // februari: months are zero-indexed const localized = locale.date(date, true) locale.dayFirst = /3.*2/.test(localized) - + locale.hasMeridian = false if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed locale.hasMeridian = (locale.meridian(date) !== '') } - + } const screen = { width: g.getWidth(), @@ -124,7 +124,7 @@ g.fillRect(0, timeTop, screen.width, screen.height) } - let lastSeconds + let lastSeconds, tTick const tick = function () { g.reset() const date = new Date() @@ -136,20 +136,20 @@ } // the bar only gets larger, so drawing on top of the previous one is fine drawBar(date) - lastSeconds = seconds + // schedule next update + const millis = date.getMilliseconds() + tTick = setTimeout(tick, 1000-millis) } - let iTick const start = function () { lastSeconds = 99 // force redraw tick() - iTick = setInterval(tick, 1000) } const stop = function () { - if (iTick) { - clearInterval(iTick) - iTick = undefined + if (tTick) { + clearTimeout(tTick) + tTick = undefined } } From c6bafb6b0a1cd7f6efa71a800c41e5b67229c740 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Thu, 10 Jun 2021 20:46:02 +0200 Subject: [PATCH 012/193] barclock: add readme with screenshots --- apps.json | 1 + apps/barclock/README.md | 6 ++++++ apps/barclock/screenshot.png | Bin 0 -> 2764 bytes apps/barclock/screenshot_pm.png | Bin 0 -> 2595 bytes 4 files changed, 7 insertions(+) create mode 100644 apps/barclock/README.md create mode 100644 apps/barclock/screenshot.png create mode 100644 apps/barclock/screenshot_pm.png diff --git a/apps.json b/apps.json index c10395020..31c791c86 100644 --- a/apps.json +++ b/apps.json @@ -1316,6 +1316,7 @@ "description": "A simple digital clock showing seconds as a bar", "tags": "clock", "type":"clock", + "readme": "README.md", "allow_emulator":true, "storage": [ {"name":"barclock.app.js","url":"clock-bar.js"}, diff --git a/apps/barclock/README.md b/apps/barclock/README.md new file mode 100644 index 000000000..4b92313c5 --- /dev/null +++ b/apps/barclock/README.md @@ -0,0 +1,6 @@ +# Bar Clock +A simple digital clock showing seconds as a horizontal bar. + +| 24hr style | 12hr style | +| --- | --- | +| ![24-hour bar clock](screenshot.png) | ![12-hour bar clock with meridian](screenshot_pm.png) | diff --git a/apps/barclock/screenshot.png b/apps/barclock/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d37ee9cae2680e73889600e8eb29f87ba2956f87 GIT binary patch literal 2764 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 literal 0 HcmV?d00001 From c1358b51bcde602c6039cf8aed996d61b4d77bba Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 10 Jun 2021 23:49:44 +0100 Subject: [PATCH 013/193] updated README for gpsservice app, to state its superceded by gpssetup --- apps/gpsservice/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/gpsservice/README.md b/apps/gpsservice/README.md index b1e3e60d4..061f3ba3b 100644 --- a/apps/gpsservice/README.md +++ b/apps/gpsservice/README.md @@ -2,6 +2,9 @@ A configurable, low power GPS widget that runs in the background. +NOTE: This app has been superceded by [gpssetup](https://github.com/espruino/BangleApps/blob/master/apps/gpssetup/README.md) + + ## Goals To develop a low power GPS widget that runs in the background and to From ca12134ffe2a4ee3b464b49d1f497ca842a9e7af Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 11 Jun 2021 11:14:52 +0100 Subject: [PATCH 014/193] lock alignment --- apps/widlock/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widlock/widget.js b/apps/widlock/widget.js index b710de8c6..ea664a083 100644 --- a/apps/widlock/widget.js +++ b/apps/widlock/widget.js @@ -5,6 +5,6 @@ }); WIDGETS["lock"]={area:"tl",width:Bangle.isLCDOn()?0:16,draw:function(w) { if (!Bangle.isLCDOn()) - g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x, w.y); + g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x+1, w.y+4); }}; })() From 45b5ab231a5875e131a8f494f1701d0e39f84c6a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 11 Jun 2021 11:15:18 +0100 Subject: [PATCH 015/193] minor colour change to help with rounding on 3bpp --- apps/widid/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widid/widget.js b/apps/widid/widget.js index 68917c65a..d4f4d6386 100644 --- a/apps/widid/widget.js +++ b/apps/widid/widget.js @@ -2,7 +2,7 @@ (() => { function draw() { var id = NRF.getAddress().substr().substr(12).split(":"); - g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); + g.reset().setColor(0, 0.49, 1).setFont("6x8", 1); g.drawString(id[0], this.x+2, this.y+4, true); g.drawString(id[1], this.x+2, this.y+14, true); } From 7a287c497e17b8389ea683218de13edd22f6d822 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 11 Jun 2021 11:34:05 +0100 Subject: [PATCH 016/193] Add option for 3 bit maps, scrolling support --- apps.json | 2 +- apps/openstmap/ChangeLog | 1 + apps/openstmap/app.js | 16 +++++++++++++++- apps/openstmap/custom.html | 11 +++++++++++ apps/openstmap/openstmap.js | 15 ++++++++++----- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/apps.json b/apps.json index f5b4c47a6..a02aec925 100644 --- a/apps.json +++ b/apps.json @@ -1462,7 +1462,7 @@ "name": "OpenStreetMap", "shortName":"OpenStMap", "icon": "app.png", - "version":"0.05", + "version":"0.06", "description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are", "tags": "outdoors,gps", "custom": "custom.html", diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 64b39b509..4d9b61031 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -3,3 +3,4 @@ 0.03: Show widgets (mainly so we can use the GPS recorder widget) 0.04: Move map rendering to a module (fix #396) 0.05: Show currently active gpsrec GPS trace (fix #395) +0.06: Add support for scrolling, option for 3 bit maps diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 940557361..5a01a9059 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -34,7 +34,7 @@ Bangle.on('GPS',function(f) { g.drawString(txt,120,y1 + 4); drawMarker(); }); -Bangle.setGPSPower(1); +Bangle.setGPSPower(1, "app"); if (HASWIDGETS) { Bangle.loadWidgets(); @@ -55,3 +55,17 @@ setWatch(function() { m.lon = fix.lon; redraw(); }, BTN2, {repeat:true}); + +var hasScrolled = false; +E.on('touch',e=>{ + if (e.b) { + g.setClipRect(0,y1,g.getWidth()-1,y2); + g.scroll(e.dx,e.dy); + m.scroll(e.dx,e.dy); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); + hasScrolled = true; + } else if (hasScrolled) { + hasScrolled = false; + redraw(); + } +}); diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html index 81a55a4f8..95a4ff0a6 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/custom.html @@ -32,6 +32,7 @@
+

3 bit