Merge pull request #3467 from jeonlab/master

New app for playing golf, Golf GPS

With removed messagegui change
pull/3499/head^2
Rob Pilling 2024-07-08 21:14:45 +01:00
commit bd8c2313c2
25 changed files with 492 additions and 34 deletions

2
apps/golf-gps/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: First release

61
apps/golf-gps/README.md Normal file
View File

@ -0,0 +1,61 @@
# Golf GPS
I have made a few watches for golfing. See this [LINK](https://jeonlab.wordpress.com/category/golf-gps-watch/) if you are interested.
Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfing. For my previous watches I have used TFT LCD or OLED displays and they all draw a lot of current and display information only when I press a button to wake up from the black screen. One of the best feature of the Bangle.js 2, I think, is the memory LCD which consumes very small power and it is always on!
## Features
- Play or view previously played scores
- Save your favourite course data (see below instruction)
- In play mode
- Hole number
- Par as background color of the hole number (red: 3, green: 4, blue: 5)
- Distance to the center of the green (coordinates you saved)
- Distance from the last shot (where you swiped up to add a shot)
- Number of shots on current hole
- Total number of shots
- Clock
![](playScreen.png)
- How to change holes and add/subtract shots
- Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake)
- Swipe up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots.
- After the game
- Press the button to either finish the game or go back to the play screen (if you pressed the button by accident).
- If you choose to finish the game, it will show the summary of the score with 3x6 matix, shots - par. For example, -1 is for birdie and 0 is for par, and +2 is for double bogey. It also shows the total shots and total par as well.
## Course data
Before you run Golf GPS, you need to save your favourite golf course data into the storage area.
Download `storage-write.js` and open it from Espruino Web IDE (https://www.espruino.com/ide/) and enter your course data and run in RAM mode.
With this script, you can create, overwrite or append golf course GPS data.
```
f = require("Storage").open("course-data","a"); // "w" to create or overwrite, "a" to append
```
There must be other ways to get the GPS coordinates for your course, but I use Google Maps. In satellite mode, click on the middle of each green will show you the coordinate, latitude and longitude. copy and paste it into the script for the holes 1 through 18 and add par per each hole. You will need 6 decimal places for the coordinate. Here is a format of the course data to put in the script.
```
"course name\n"+
"00.000000,00.000000,0\n"+
.
.
"00.000000,00.000000,0\n"+
"next course name\n"+
.
.
```
You can put any number of course data. Once the data file is created in the storage area, you can add more data to the same file using append ("a" ) mode with the same script. Just replace the data and change the mode to "a" and run in RAM mode.
One tip to wrap each line with `"----\n"+` is using the built-in edit feature of the Web IDE. In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all lines including the course name, and hit HOME and type `"` and hit END and type `\n"+`.
## Screenshots
![](startUp.png)
![](fixGPS.png)
![](par3.png)
![](par4.png)
![](par5.png)
![](finishGame.png)
![](scoreView.png)
## Creator
Written by [JeonLab](https://jeonlab.wordpress.com)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
apps/golf-gps/fixGPS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AHeB1nOBhMsAAQve1gAB1WjvIuIhEIlYuc1YvC0ejlcsmVWBYNVFgIADMLgvC1XM4wKEqtVlkrp8rGAQvb1Wr1X+43NBYtUFoIwCYLvN5wsC5owFv1PAAIxBhFVY4MymQvdAgIxD415vNVp9OqgYFHAMyfKYoBFIYFCGwfG0YxBvwcKGYIvW44AB5uiy/B4PGfIowadYIBB5vH3e7y4AGF74wBF4XBFwedF8oxDLg4vQAC+dAAIvsL1yOvF/6+/d34v/F/4v+uYuHuYvlhErp9PuYACp8rF8wAGlksF9oABF1wvug8HR9guBF88sRISPpF+QwBF+AwqFwKQDAoQGDL8woCGFAvGAwQwmEwwwDYchWHNAdPq1PSERVGGAWXq0yuYvgAAYJFle7F4OXMEK2IF4ZfgYAIuBlYKGR4MsYEOXudzy6FGucyF8X+3YuBF4zxBNI4AllZdiAD4A=="))

309
apps/golf-gps/golf-gps.js Normal file
View File

@ -0,0 +1,309 @@
/*
Golf-GPS app v0.01
written by JeonLab (https://jeonlab.wordpress.com)
6/26/2024
*/
var currentHole = 1,
totalShots = 0,
courseName = "",
latLast,
lonLast,
W = g.getWidth(),
H = g.getHeight(),
lat = Array(19).fill(0),
lon = Array(19).fill(0),
par = Array(19).fill(0),
score = Array(19).fill(0),
playON = false;
var fileIndx;
var scoreFiles;
require("Font7x11Numeric7Seg").add(Graphics);
Graphics.prototype.setFontDroidSansMono52 = function() {
// Actual height 52 (53 - 2)
// 1 BPP
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
46,
atob("HCQnHCYmKCYmJyYmGw=="),
70 | 65536
);
};
Graphics.prototype.setFontDroidSansMono35 = function() {
// Actual height 35 (37 - 3)
// 1 BPP
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('AH/wAgcB/AFDgf8ArYjFF4oAehAFEnAFELIoFEj4FEv4FDgP/8AFCh//wAFCn/+Cwf/LAcH//AE4f/E5EDE5UfE4kfQAkfE4YFBMIkYRjo8BIQd//49COoIABJIJTBAAI+BNQIABDAIcBAAJQBh4IBg4FBj4aBcYRTDAoMeAoV4AqQdDAoopCF4kPHwQCCI4hTFL4rQBAALcD//8YwivEAEVgAoj6DAoxiCIAb1Eg7JDAoJLEh59BAAUfMoSJCArgAbZYMDNAhTCNAQFCgbRCAoMHAoRfBj5BCYIQFEv4pBjwFB/wFDgP8AoYoBAocH+AFDh/gAoIjBn/AAod/wAFC4b6BAoMP//+Aog/BAoMH/7ABNYX/YAIFBgAcBAoZ9EApIAQgIrBTYUBG4MHRIIFCSoSnCeoYFHNYM8HYQFFQYIFRvgFOFIKPBGoWBAonH95TD//v44FD56dCUIPHToShBw//NAX+A4JiCOwKpCABxIBAAf8AgcDAokHAokfAok/Aon/PwI6C/wFDg/4AocP+AFDn/AAod/wAFCDgKiCH4YFCDgIFDj56BAof/AAKbCAqwACIIYFZAC8BLgJsB8DvCI4PwAosAKYYDBAoYLBvAFGj0AAsY1DAo/8JoQFBv7CCAoX/MoIFBn4FEJ4PAewf/wAFCg7rBADR4CFAYjDHQIvDdIQ7BgIFCI4MDAoQeBg/8nkHAoJiBvEBOIMP4B3Dh+AWYiPEAoqnPUJUDAoabBUJoABUI6DDLAQAYF4IFZhAFEnAFEMoaqBAokfAol/AobHBO4ZlBNYJ3CcYQFB/5rCj0HQQceQQJHDv/8RoiTFf4QFBE4QFCHAIFDUgbjCAD1+QAZrBHoMD/0DNYReBw5PCAoPPPoR7BAov/wJ4Bj4eBU4UfgIFBvCJCO4IFDB4IFDAYIRJDoYjDFIWDUIIFBHYpHFLIYFDYAYiBNYYsCWokAnifan7GEHIQABGYJLBuBBCNYZTBNYYFFj+An4FDAIQFDh6JEApo3BAoodCgJJBFIUDAoWAn0PRIICBvl/d4YABfYYABR4JlBAAJxDMwR9CPAkHMoIA/ABUPMYKJBPwPAgJgCAq4jFAAg'))),
46,
atob("FBkbFBsaHBobGxsbEw=="),
48 | 65536
);
};
var courseData = require("Storage").open("course-data", "r");
var lastFix = {
lat: 0,
lon: 0,
alt: 0, // altitude in m
speed: 0, // km/h
course: 0, // heading in degrees
time: 0,
satellites: 0,
fix: 0,
hdop: 0, // x5 ~ meter accuracy
};
const zeroPad = (num, places) => String(num).padStart(places, '0');
function onGPS(fix) {
Object.assign(lastFix, fix);
if (lastFix.fix && playON) showPlayData();
}
function radians(degrees) {
return degrees * Math.PI / 180;
}
function readOneCourseData() {
// Clear previous data
lat = [];
lon = [];
par = [];
let l = courseData.readLine();
courseName = l;
// Read one course data & initialize score
for (let i = 1; i < 19; i++) {
l = courseData.readLine();
if (l !== undefined) {
const parts = l.split(',');
lat[i] = parseFloat(parts[0]);
lon[i] = parseFloat(parts[1]);
par[i] = parseInt(parts[2]);
score[i] = 0;
}
}
}
function distanceCalc(lat1, long1, lat2, long2) {
let delLat = Math.abs(lat1 - lat2) * 111194.9; // 111194.9 = (2*6371000*pi)/360, 6371000 ~ Earth's average radius
let delLong = Math.abs(long1 - long2) * 111194.9 * Math.cos(radians((lat1+lat2)/2));
return Math.sqrt(delLat * delLat + delLong * delLong)/0.9144; // in yards
}
function mainMenu() {
E.showPrompt("Play now or\n\nView scores?", {
title: "Golf GPS",
buttons: {
"PLAY": 1,
"VIEW": 2
}
}).then(choice => {
if (choice === 1) fixGPS();
else browseScore();
});
}
function browseScore() {
scoreFiles = require("Storage").list(/^Scorecard-/);
if (scoreFiles.length === 0) {
E.showPrompt("No score file found.\n\nYou need to play at least a game.", {
title: `Error`,
buttons: {
"END": 1
}
}).then(choice => {
if (choice === 1) load();
});
}
fileIndx = scoreFiles.length - 1;
browseFiles();
}
function browseFiles() {
const browsefile = require("Storage").open(scoreFiles[fileIndx].substring(0, 18), "r");
const l = browsefile.read(80);
E.showPrompt(l, {
buttons: {
"<<": 1,
">>": 2,
"End": 3
}
}).then(choice => {
if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length;
else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length;
else if (choice === 3) load();
browseFiles();
});
}
function fixGPS() {
Bangle.on('GPS', onGPS);
Bangle.setGPSPower(1, "golf-gps");
E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n ");
let fixInterval = setInterval(() => {
let date = new Date();
g.clearRect(0, 150, W, H);
g.setFontAlign(1, 1).setFont('6x8:3').drawString(lastFix.hdop, W, H);
g.setFontAlign(-1, 1).setFont('6x8:3').drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), 2, H);
if (lastFix.fix && lastFix.hdop <= 5) {
clearInterval(fixInterval);
searchCourse();
}
}, 1000); // Check every second
}
function searchCourse() {
readOneCourseData();
if (!courseName) {
courseData = require("Storage").open("course-data", "r");
E.showPrompt("Scan again\nor quit?", {
title: "No course found",
buttons: {
"SCAN": 1,
"QUIT": 2
}
}).then(choice => {
if (choice === 1) searchCourse();
else load();
});
} else if (distanceCalc(lastFix.lat, lastFix.lon, lat[1], lon[1]) < 1000) {
Bangle.buzz();
E.showPrompt(courseName + "\nIs this correct?", {
buttons: {
" YES ": 1,
" NO ": 2
}
}).then(choice => {
if (choice == 1) {
E.showPrompt("Front or Back", {
title: courseName,
buttons: {
"FRONT": 1,
"BACK": 2
}
}).then(choice => {
currentHole = (choice === 1) ? 1 : 10;
playON = true;
showPlayData();
});
} else searchCourse();
});
} else searchCourse();
}
function showPlayData() {
let date = new Date();
let distanceToHole = distanceCalc(lastFix.lat, lastFix.lon, lat[currentHole], lon[currentHole]);
let distanceFromLast = distanceCalc(lastFix.lat, lastFix.lon, latLast, lonLast) || 0;
g.reset().clearRect(Bangle.appRect);
// distance to hole in yards
g.setColor(g.theme.fg).setFontAlign(1, -1).setFontDroidSansMono52();
g.drawString(distanceToHole > 1000 ? "000" : distanceToHole.toFixed(0), W - 2, 1);
// distance from last shot in yards
g.setFontDroidSansMono35().setColor('#00f').drawString(distanceFromLast.toFixed(0), W - 2, 70);
// hole number and par color(red:3, green:4, blue:5)
g.setColor(par[currentHole] == 3 ? '#f00' : (par[currentHole] == 4 ? '#0f0' : '#00f'));
g.fillRect(3, 3, 47, 47);
g.setColor(par[currentHole] == 4 ? '#000' : '#fff').setFontAlign(0, -1).drawString(currentHole, 24, 5);
// total shots and current shots
g.setColor(g.theme.fg).setFontAlign(-1, -1);
g.drawString(totalShots, 3, 70); // total shots
g.drawString(score[currentHole], 3, 128); // current shots
g.setFont("6x8:2").drawString('TOTAL', 3, 54);
g.drawString('THIS', 3, 112);
// clock
g.setFontAlign(1, 1).setFont("7x11Numeric7Seg:4").drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 2, H - 4);
g.drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 1, H - 3);
// battery level bar
g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4);
g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5);
g.setColor(E.getBattery() > 15 ? '#03f' : '#f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 60), H * 2 / 3 + 3);
// hdop level indicator
if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5);
if (lastFix.hdop < 2) g.fillRect(100, H / 3, 136, H / 3 + 5);
if (lastFix.hdop < 1) g.fillRect(140, H / 3, W, H / 3 + 5);
}
function finishGame() {
let date = new Date();
let saveFileContents = ' ';
let parTotal = 0;
Bangle.setGPSPower(0, "golf-gps");
const saveFilename = `Scorecard-${date.getFullYear()}${zeroPad(date.getMonth() + 1, 2)}${zeroPad(date.getDate(), 2)}`;
const saveFile = require("Storage").open(saveFilename, "w");
for (let i = 1; i < 19; i++) {
saveFileContents += String(score[i] - par[i]).padStart(2, ' ');
saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : ''));
parTotal += par[i];
}
saveFile.write(`${date.getFullYear()}_${zeroPad(date.getMonth() + 1, 2)}_${zeroPad(date.getDate(), 2)}\n${courseName}${totalShots}/${parTotal}\n${saveFileContents}`);
E.showPrompt(saveFileContents, {
title: `${totalShots}/${parTotal}`,
buttons: {
"END": 1
}
}).then(choice => {
if (choice === 1) load();
});
}
setWatch(() => {
playON = false;
E.showPrompt("Finish game?", {
title: courseName,
buttons: {
" YES ": 1,
" NO ": 2
}
}).then(choice => {
if (choice === 1) {
finishGame();
} else {
playON = true;
showPlayData();
}
});
}, (process.env.HWVERSION === 2) ? BTN1 : BTN2, {
repeat: true,
edge: "falling"
});
Bangle.on('swipe', function(directionLR, directionUD) {
if (playON) {
currentHole = (currentHole - directionLR) % 18 || 18;
score[currentHole] = Math.max(0, score[currentHole] - directionUD);
totalShots = Math.max(0, totalShots - directionUD);
if (directionUD === -1) {
latLast = lastFix.lat;
lonLast = lastFix.lon;
}
}
});
Bangle.loadWidgets();
require("widget_utils").hide();
// keep unlocked
Bangle.setLCDTimeout(0);
Bangle.setLocked(false);
// keep backlight off
Bangle.setLCDBrightness(0);
mainMenu();

BIN
apps/golf-gps/golf-gps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,15 @@
{
"id": "golf-gps",
"name": "Golf GPS",
"version": "0.01",
"description": "Golf GPS for Banglejs 2, using manually saved golf course data (latitudes and longitudes of holes, see README for instructions), provides distance to center of green and distance from previous shot, keeps score",
"icon": "golf-gps.png",
"type": "app",
"tags": "gps,outdoors,tools",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"golf-gps.app.js","url":"golf-gps.js"},
{"name":"golf-gps.img","url":"golf-gps-icon.js","evaluate":true}
],
"readme":"README.md"
}

BIN
apps/golf-gps/par3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
apps/golf-gps/par4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
apps/golf-gps/par5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
apps/golf-gps/scoreView.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
apps/golf-gps/startUp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,56 @@
/*
Before you run Golf GPS, you need to save your favourite golf course data into the storage area.
Download this script and open it from Espruino Web IDE (https://www.espruino.com/ide/) and enter your course data and run in RAM mode.
With this script, you can create, overwrite or append golf course GPS data.
You can put any number of course data.
Once the data file is created in the storage area, you can add more data to the same file using append ("a" ) mode with the same script.
Just replace the data and change the mode to "a" and run in RAM mode.
One tip to wrap each line with "----\n"+ is using the built-in edit feature of the Web IDE.
In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all lines including the course name,
and hit HOME and type " and hit END and type \n"+.
*/
const Storage = require("Storage");
const f = Storage.open("course-data", "a"); // "w" to create or overwrite, "a" to append
f.write(
"course name\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"course name\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"+
"00.000000,00.000000,0\n"
);

View File

@ -6,7 +6,7 @@
"description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial",
"icon": "app.png",
"tags": "outdoors",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"interface": "interface.html",
"storage": [
{"name":"gpspoilog.app.js","url":"app.js"},

View File

@ -6,7 +6,7 @@
"description": "Configure the GPS power options and store them in the GPS nvram",
"icon": "gpssetup.png",
"tags": "gps,tools,outdoors",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"gpssetup","url":"gpssetup.js"},

View File

@ -1,2 +1,3 @@
0.01: Created
0.02: Changed side bar color to blue for better clarity when it's locked
0.03: Minor updates on font size/color and Bluetooth indicator

View File

@ -18,8 +18,8 @@ I have used Rebble clock since I bought my Banglejs 2, and wanted to make my own
- Update time and status every 1 minute
## Screenshots
![](jclock_screenshot_no_BT.png)
![](jclock_screenshot_BT.png)
![](screenshot_noBT.png)
![](screenshot_BT.png)
## Creator

View File

@ -17,7 +17,7 @@ var drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = setTimeout(() => {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
@ -26,43 +26,56 @@ function queueDraw() {
const zeroPad = (num, places) => String(num).padStart(places, '0');
function draw() {
let barWidth = 64;
const barWidth = 64;
let date = new Date();
let battColor = '#0ff';
// queue next draw in one minute
queueDraw();
// clean screen
g.reset().clearRect(Bangle.appRect);
// draw side bar in blue
g.setColor('#00f');
g.fillRect(0, 0, barWidth, g.getHeight());
g.setColor('#000').fillRect(0, 0, barWidth, g.getHeight());
// show time on the right
g.setColor(g.theme.fg);
g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getHours(),2), 120, 10);
g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getMinutes(),2), 120, g.getHeight()/2+10);
// show date
g.setFont('Vector', 20).setFontAlign(0, -1).setColor('#fff');
g.drawString(require("date_utils").dow(date.getDay(),1).toUpperCase(), barWidth/2, 3);
g.drawString(date.getDate(), barWidth/2, 28);
g.drawString(require("date_utils").month(date.getMonth()+1,1).toUpperCase(), barWidth/2, 53);
// divider, place holder for any other info
g.drawString('=====', barWidth/2, 78);
g.setColor(g.theme.fg).setFontAlign(0, 0).setFontKdamThmor();
g.setColor('#11f').drawString(zeroPad(date.getHours(), 2), 120, g.getHeight() / 4 + 12);
g.setColor('#000').drawString(zeroPad(date.getMinutes(), 2), 120, g.getHeight() * 3 / 4 + 12);
// day of week
g.setColor('#fff').setFontAlign(0, -1).setFont('Vector', 28).drawString(require("date_utils").dow(date.getDay(), 1).toUpperCase(), barWidth / 2 + 1, 3);
//g.setFont('Vector', 30).drawString(twoCharsDayOfWeek[date.getDay()], barWidth/2, 3);
// month
g.drawString(require("date_utils").month(date.getMonth() + 1, 1).toUpperCase(), barWidth / 2 + 1, 84);
// date
g.setColor('#0ff').setFont('Vector', 48).drawString(date.getDate(), barWidth / 2 + 5, 36);
// show daily steps
g.drawString(Bangle.getHealthStatus("day").steps, barWidth/2, 103);
g.setFontAlign(1, -1).setColor('#fff').setFont('Vector', 24).drawString((Bangle.getHealthStatus("day").steps / 1000).toFixed(1) + 'k', barWidth, 125);
// Bluetooth/GPS/Compass connection status
if (NRF.getSecurityStatus().connected) g.setColor('#0ff').fillRect(5, 115, barWidth - 5, 120);
// show battery remaining percentage
g.drawString(E.getBattery() + '%', barWidth/2, 153);
// Bluetooth connection status
if (NRF.getSecurityStatus().connected) g.drawString('>BT<', barWidth/2, 128);
battColor = Bangle.isCharging() ? '#f00' : (E.getBattery() < 30 ? '#ff0' : '#0f0');
g.setColor(battColor).drawString(E.getBattery() + '%', barWidth, 153);
}
draw();
function handleEvent() {
draw();
}
Bangle.on('charging', handleEvent);
NRF.on('connect', handleEvent);
NRF.on('disconnect', handleEvent);
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
require("widget_utils").hide();
draw();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -2,12 +2,12 @@
"name":"jclock",
"shortName":"jclock",
"icon":"app.png",
"version":"0.02",
"version":"0.03",
"description":"Similar layout to Rebble clock, but much simpler features with switched time and the feature window. The time is on the right side. This is my first Bangle app.",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"jclock_screenshot_no_BT.png"},{"url":"jclock_screenshot_BT.png"}],
"screenshots": [{"url":"screenshot_noBT.png"},{"url":"screenshot_BT.png"}],
"storage": [
{"name":"jclock.app.js","url":"app.js"},
{"name":"jclock.img","url":"app-icon.js","evaluate":true}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB