1
0
Fork 0

Merge branch 'master' into mystic-dock

master
Gordon Williams 2021-06-07 10:59:35 +01:00 committed by GitHub
commit ae6201f743
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 386 additions and 44 deletions

View File

@ -3135,7 +3135,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",
@ -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}
@ -3273,5 +3272,20 @@
{"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"},
{"name":"mysticdock.img","url":"mystic-dock-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}
]
}
]

View File

@ -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)

View File

@ -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

View File

@ -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
@ -226,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
@ -241,9 +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-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 +261,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

29
apps/kitchen/annex.js Normal file
View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -0,0 +1 @@
1.00: First published version.

View File

@ -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/

View File

@ -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 });

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBIf4A/AH4A/AH4A/AH4ALs1msADCA4MGAgQDBBYIAGg93u92s4DBuEAAYN3swDCC5AhBuwMBg4XBuwEBs4dCC49nHgNwCQREBCYNnEYYXHHQQvBAAJZBAgRPEC5IOCu0GM4YLCuCGDAAREBHwtnJ41gDQIXEOAQvBDoZ7CuwjCWwimTJgLCFZojWEbwbWIAH4A/AH4A/AH4A/AH4AFA"))

View File

@ -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)
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B