some fixes

master
Bryan 2024-02-04 08:32:05 -06:00
commit e606f85d45
122 changed files with 2612 additions and 536 deletions

5
.gitignore vendored
View File

@ -16,3 +16,8 @@ _site
Desktop.ini
.sync_*.db*
*.swp
*_BACKUP_*
*_BASE_*
*_LOCAL_*
*_REMOTE_*
*.orig

View File

@ -21,7 +21,6 @@
'< Back': back,
'Full Screen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();

View File

@ -10,8 +10,8 @@
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"agenda.app.js","url":"agenda.js"},
{"name":"agenda.settings.js","url":"settings.js"},
{"name":"agenda.app.js","url":"agenda.app.js"},
{"name":"agenda.settings.js","url":"agenda.settings.js"},
{"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"},
{"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
],

View File

@ -32,3 +32,4 @@
Allow alarm enable/disable
0.31: Implement API for activity fetching
0.32: Added support for loyalty cards from gadgetbridge
0.33: Fix alarms created in Gadgetbridge not repeating

View File

@ -81,7 +81,12 @@
for (var j = 0; j < event.d.length; j++) {
// prevents all alarms from going off at once??
var dow = event.d[j].rep;
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
var rp = false;
if (!dow) {
dow = 127; //if no DOW selected, set alarm to all DOW
} else {
rp = true;
}
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
var a = require("sched").newDefaultAlarm();
a.id = "gb"+j;
@ -89,6 +94,7 @@
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
a.rp = rp;
a.last = last;
alarms.push(a);
}

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.32",
"version": "0.33",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

1
apps/angles/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

49
apps/angles/app.js Normal file
View File

@ -0,0 +1,49 @@
g.clear().setRotation(1);
// g.setRotation ALSO changes accelerometer axes
var avrAngle = undefined;
var history = [];
var R = Bangle.appRect;
var W = g.getWidth();
var H = g.getHeight();
var relativeTo = undefined;
function draw(v) {
if (v===undefined) v = Bangle.getAccel();
// current angle
var d = Math.sqrt(v.y*v.y + v.z*v.z);
var ang = Math.atan2(-v.x, d)*180/Math.PI;
// Median filter
if (history.length > 10) history.shift(); // pull old reading off the start
history.push(ang);
avrAngle = history.slice().sort()[(history.length-1)>>1]; // median filter
// Render
var x = R.x + R.w/2;
var y = R.y + R.h/2;
g.reset().clearRect(R).setFontAlign(0,0);
var displayAngle = avrAngle;
g.setFont("6x15").drawString("ANGLE (DEGREES)", x, R.y2-8);
if (relativeTo!==undefined) {
g.drawString("RELATIVE TO", x,y-50);
g.setFont("Vector:30").drawString(relativeTo.toFixed(1),x,y-30);
y += 20;
displayAngle = displayAngle-relativeTo;
}
g.setFont("Vector:60").drawString(displayAngle.toFixed(1),x,y);
}
draw();
Bangle.on('accel',draw);
// Pressing the button turns relative angle on/off
Bangle.setUI({
mode : "custom",
btn : function(n) {
if (relativeTo===undefined)
relativeTo = avrAngle;
else
relativeTo = undefined;
draw();
}
});

1
apps/angles/icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA"))

BIN
apps/angles/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

15
apps/angles/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{
"id": "angles",
"name": "Angles (Spirit Level)",
"shortName": "Angles",
"version": "0.01",
"description": "Shows Angle or Relative angle in degrees (Digital Protractor/Inclinometer). Place Bangle sideways against a surface with the button facing away for best readings.",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
"tags": "tool",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"angles.app.js","url":"app.js"},
{"name":"angles.img","url":"icon.js","evaluate":true}
]
}

BIN
apps/angles/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

1
apps/aviatorclk/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
aviatorclk.json

View File

@ -0,0 +1 @@
1.00: initial release

36
apps/aviatorclk/README.md Normal file
View File

@ -0,0 +1,36 @@
# Aviator Clock
A clock for aviators, with local time and UTC - and the latest METAR
(Meteorological Aerodrome Report) for the nearest airport
![](screenshot.png)
![](screenshot2.png)
This app depends on the [AVWX module](?id=avwx). Make sure to configure that
module after installing this app.
## Features
- Local time (with optional seconds)
- UTC / Zulu time
- Weekday and day of the month
- Latest METAR for the nearest airport (scrollable)
Tap the screen in the top or bottom half to scroll the METAR text (in case not
the whole report fits on the screen).
The colour of the METAR text will change to orange if the report is more than
1h old, and red if it's older than 1.5h.
## Settings
- **Show Seconds**: to conserve battery power, you can turn the seconds display off
- **Invert Scrolling**: swaps the METAR scrolling direction of the top and bottom taps
## Author
Flaparoo [github](https://github.com/flaparoo)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwg96iIACCqMBCwYABiAWQiUiAAUhDBwWGDCAWHDAYuMCw4ABGBYWKGBYuLGBcBLpAXNFxhIKFxgwCIyhIJC58hC44WNC5B2NPBIXbBYIAHNgIXKCpAYEC5AhBII8SDAQXJMI5EEC6ZREC6EhFwkRO4zuCC46AFAgLYEC4YCBIoaADF4gXEKgYXDVBAcCXxBZDkcyDRAXHmILCif//4GEC5f/PQQWB//zbAX/C5gAKC78BC6K/In4WJ+YXW+QXHMAURl4XJeQYWEGALhBC4q+BYYLbDFwowCkLTCRIyNHGArNBC48SFxIXCMApHDOwQXIJAIQCAAaWCDYJGIDAipGFwQWKDAUSDAnzUoIWMDAcjn/zUgQWOPYYADOZJjKFqIAp"))

View File

@ -0,0 +1,283 @@
/*
* Aviator Clock - Bangle.js
*
*/
const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25)
const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5)
const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75)
const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0)
const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1)
const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0)
const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1)
const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0)
const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5)
const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0)
const APP_NAME = 'aviatorclk';
const horizontalCenter = g.getWidth()/2;
const mainTimeHeight = 38;
const secondaryFontHeight = 22;
const dateColour = ( g.theme.dark ? COLOUR_YELLOW : COLOUR_BLUE );
const UTCColour = ( g.theme.dark ? COLOUR_LIGHT_CYAN : COLOUR_DARK_CYAN );
const separatorColour = ( g.theme.dark ? COLOUR_LIGHT_GREY : COLOUR_DARK_GREY );
const avwx = require('avwx');
// read in the settings
var settings = Object.assign({
showSeconds: true,
invertScrolling: false,
}, require('Storage').readJSON(APP_NAME+'.json', true) || {});
// globals
var drawTimeout;
var secondsInterval;
var avwxTimeout;
var AVWXrequest;
var METAR = '';
var METARlinesCount = 0;
var METARscollLines = 0;
var METARts;
// date object to time string in format HH:MM[:SS]
// (with a leading 0 for hours if required, unlike the "locale" time() function)
function timeStr(date, seconds) {
let timeStr = date.getHours().toString();
if (timeStr.length == 1) timeStr = '0' + timeStr;
let minutes = date.getMinutes().toString();
if (minutes.length == 1) minutes = '0' + minutes;
timeStr += ':' + minutes;
if (seconds) {
let seconds = date.getSeconds().toString();
if (seconds.length == 1) seconds = '0' + seconds;
timeStr += ':' + seconds;
}
return timeStr;
}
// draw the METAR info
function drawAVWX() {
let now = new Date();
let METARage = 0; // in minutes
if (METARts) {
METARage = Math.floor((now - METARts) / 60000);
}
g.setBgColor(g.theme.bg);
let y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight + 4;
g.clearRect(0, y, g.getWidth(), y + (secondaryFontHeight * 4));
g.setFontAlign(0, -1).setFont("Vector", secondaryFontHeight);
if (METARage > 90) { // older than 1.5h
g.setColor(COLOUR_RED);
} else if (METARage > 60) { // older than 1h
g.setColor( g.theme.dark ? COLOUR_ORANGE : COLOUR_DARK_YELLOW );
} else {
g.setColor(g.theme.fg);
}
let METARlines = g.wrapString(METAR, g.getWidth());
METARlinesCount = METARlines.length;
METARlines.splice(0, METARscollLines);
g.drawString(METARlines.join("\n"), horizontalCenter, y, true);
if (! avwxTimeout) { avwxTimeout = setTimeout(updateAVWX, 5 * 60000); }
}
// update the METAR info
function updateAVWX() {
if (avwxTimeout) clearTimeout(avwxTimeout);
avwxTimeout = undefined;
METAR = '\nGetting GPS fix';
METARlinesCount = 0; METARscollLines = 0;
METARts = undefined;
drawAVWX();
Bangle.setGPSPower(true, APP_NAME);
Bangle.on('GPS', fix => {
// prevent multiple, simultaneous requests
if (AVWXrequest) { return; }
if ('fix' in fix && fix.fix != 0 && fix.satellites >= 4) {
Bangle.setGPSPower(false, APP_NAME);
let lat = fix.lat;
let lon = fix.lon;
METAR = '\nRequesting METAR';
METARlinesCount = 0; METARscollLines = 0;
METARts = undefined;
drawAVWX();
// get latest METAR from nearest airport (via AVWX API)
AVWXrequest = avwx.request('metar/'+lat+','+lon, 'onfail=nearest', data => {
if (avwxTimeout) clearTimeout(avwxTimeout);
avwxTimeout = undefined;
let METARjson = JSON.parse(data.resp);
if ('sanitized' in METARjson) {
METAR = METARjson.sanitized;
} else {
METAR = 'No "sanitized" METAR data found!';
}
METARlinesCount = 0; METARscollLines = 0;
if ('time' in METARjson) {
METARts = new Date(METARjson.time.dt);
let now = new Date();
let METARage = Math.floor((now - METARts) / 60000); // in minutes
if (METARage <= 30) {
// some METARs update every 30 min -> attempt to update after METAR is 35min old
avwxTimeout = setTimeout(updateAVWX, (35 - METARage) * 60000);
} else if (METARage <= 60) {
// otherwise, attempt METAR update after it's 65min old
avwxTimeout = setTimeout(updateAVWX, (65 - METARage) * 60000);
}
} else {
METARts = undefined;
}
drawAVWX();
AVWXrequest = undefined;
}, error => {
// AVWX API request failed
console.log(error);
METAR = 'ERR: ' + error;
METARlinesCount = 0; METARscollLines = 0;
METARts = undefined;
drawAVWX();
AVWXrequest = undefined;
});
}
});
}
// draw only the seconds part of the main clock
function drawSeconds() {
let now = new Date();
let seconds = now.getSeconds().toString();
if (seconds.length == 1) seconds = '0' + seconds;
let y = Bangle.appRect.y + mainTimeHeight - 3;
g.setFontAlign(-1, 1).setFont("Vector", secondaryFontHeight).setColor(COLOUR_GREY);
g.drawString(seconds, horizontalCenter + 54, y, true);
}
// sync seconds update
function syncSecondsUpdate() {
drawSeconds();
setTimeout(function() {
drawSeconds();
secondsInterval = setInterval(drawSeconds, 1000);
}, 1000 - (Date.now() % 1000));
}
// set timeout for per-minute updates
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
if (METARts) {
let now = new Date();
let METARage = Math.floor((now - METARts) / 60000);
if (METARage > 60) {
// the METAR colour might have to be updated:
drawAVWX();
}
}
draw();
}, 60000 - (Date.now() % 60000));
}
// draw top part of clock (main time, date and UTC)
function draw() {
let now = new Date();
let nowUTC = new Date(now + (now.getTimezoneOffset() * 1000 * 60));
// prepare main clock area
let y = Bangle.appRect.y;
g.setBgColor(g.theme.bg);
// main time display
g.setFontAlign(0, -1).setFont("Vector", mainTimeHeight).setColor(g.theme.fg);
g.drawString(timeStr(now, false), horizontalCenter, y, true);
// prepare second line (UTC and date)
y += mainTimeHeight;
g.clearRect(0, y, g.getWidth(), y + secondaryFontHeight - 1);
// weekday and day of the month
g.setFontAlign(-1, -1).setFont("Vector", secondaryFontHeight).setColor(dateColour);
g.drawString(require("locale").dow(now, 1).toUpperCase() + ' ' + now.getDate(), 0, y, false);
// UTC
g.setFontAlign(1, -1).setFont("Vector", secondaryFontHeight).setColor(UTCColour);
g.drawString(timeStr(nowUTC, false) + "Z", g.getWidth(), y, false);
queueDraw();
}
// initialise
g.clear(true);
// scroll METAR lines on taps
Bangle.setUI("clockupdown", action => {
switch (action) {
case -1: // top tap
if (settings.invertScrolling) {
if (METARscollLines > 0)
METARscollLines--;
} else {
if (METARscollLines < METARlinesCount - 4)
METARscollLines++;
}
break;
case 1: // bottom tap
if (settings.invertScrolling) {
if (METARscollLines < METARlinesCount - 4)
METARscollLines++;
} else {
if (METARscollLines > 0)
METARscollLines--;
}
break;
default:
// ignore
}
drawAVWX();
});
// load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// draw static separator line
y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight;
g.setColor(separatorColour);
g.drawLine(0, y, g.getWidth(), y);
// draw times and request METAR
draw();
if (settings.showSeconds)
syncSecondsUpdate();
updateAVWX();
// TMP for debugging:
//METAR = 'YAAA 011100Z 21014KT CAVOK 23/08 Q1018 RMK RF000/0000';
//METAR = 'YAAA 150900Z 14012KT 9999 SCT045 BKN064 26/14 Q1012 RMK RF000/0000 DL-W/DL-NW';
//METAR = 'YAAA 020030Z VRB CAVOK';
//METARts = new Date(Date.now() - 61 * 60000); // 61 to trigger warning, 91 to trigger alert

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,35 @@
(function(back) {
var FILE = "aviatorclk.json";
// Load settings
var settings = Object.assign({
showSeconds: true,
invertScrolling: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
// Show the menu
E.showMenu({
"" : { "title" : "AV8R Clock" },
"< Back" : () => back(),
'Show Seconds': {
value: !!settings.showSeconds, // !! converts undefined to false
format: v => v ? "On" : "Off",
onchange: v => {
settings.showSeconds = v;
writeSettings();
}
},
'Invert Scrolling': {
value: !!settings.invertScrolling, // !! converts undefined to false
format: v => v ? "On" : "Off",
onchange: v => {
settings.invertScrolling = v;
writeSettings();
}
},
});
})

View File

@ -0,0 +1,20 @@
{
"id": "aviatorclk",
"name": "Aviator Clock",
"shortName":"AV8R Clock",
"version":"1.00",
"description": "A clock for aviators, with local time and UTC - and the latest METAR for the nearest airport",
"icon": "aviatorclk.png",
"screenshots": [{ "url": "screenshot.png" }, { "url": "screenshot2.png" }],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"dependencies" : { "avwx": "module" },
"readme": "README.md",
"storage": [
{ "name":"aviatorclk.app.js", "url":"aviatorclk.app.js" },
{ "name":"aviatorclk.settings.js", "url":"aviatorclk.settings.js" },
{ "name":"aviatorclk.img", "url":"aviatorclk-icon.js", "evaluate":true }
],
"data": [{ "name":"aviatorclk.json" }]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

1
apps/avwx/ChangeLog Normal file
View File

@ -0,0 +1 @@
1.00: initial release

41
apps/avwx/README.md Normal file
View File

@ -0,0 +1,41 @@
# AVWX Module
This is a module/library to use the [AVWX](https://account.avwx.rest/) Aviation
Weather API. It doesn't include an app.
## Configuration
You will need an AVWX account (see above for link) and generate an API token.
The free "Hobby" plan is normally sufficient, but please consider supporting
the AVWX project.
After installing the module on your Bangle, use the "interface" page (floppy
disk icon) in the App Loader to set the API token.
## Usage
Include the module in your app with:
const avwx = require('avwx');
Then use the exported function, for example to get the "sanitized" METAR from
the nearest station to a lat/lon coordinate pair:
reqID = avwx.request('metar/'+lat+','+lon,
'filter=sanitized&onfail=nearest',
data => { console.log(data); },
error => { console.log(error); });
The returned reqID can be useful to track whether a request has already been
made (ie. the app is still waiting on a response).
Please consult the [AVWX documentation](https://avwx.docs.apiary.io/) for
information about the available end-points and request parameters.
## Author
Flaparoo [github](https://github.com/flaparoo)

47
apps/avwx/avwx.js Normal file
View File

@ -0,0 +1,47 @@
/*
* AVWX Bangle Module
*
* AVWX doco: https://avwx.docs.apiary.io/
* test AVWX API request with eg.: curl -X GET 'https://avwx.rest/api/metar/43.9844,-88.5570?token=...'
*
*/
const AVWX_BASE_URL = 'https://avwx.rest/api/'; // must end with a slash
const AVWX_CONFIG_FILE = 'avwx.json';
// read in the settings
var AVWXsettings = Object.assign({
AVWXtoken: '',
}, require('Storage').readJSON(AVWX_CONFIG_FILE, true) || {});
/**
* Make an AVWX API request
*
* @param {string} requestPath API path (after /api/), eg. 'meta/KOSH'
* @param {string} params optional request parameters, eg. 'onfail=nearest' (use '&' in the string to combine multiple params)
* @param {function} successCB callback if the API request was successful - will supply the returned data: successCB(data)
* @param {function} failCB callback in case the API request failed - will supply the error: failCB(error)
*
* @returns {number} the HTTP request ID
*
* Example:
* reqID = avwx.request('metar/'+lat+','+lon,
* 'filter=sanitized&onfail=nearest',
* data => { console.log(data); },
* error => { console.log(error); });
*
*/
exports.request = function(requestPath, optParams, successCB, failCB) {
if (! AVWXsettings.AVWXtoken) {
failCB('No AVWX API Token defined!');
return undefined;
}
let params = 'token='+AVWXsettings.AVWXtoken;
if (optParams)
params += '&'+optParams;
return Bangle.http(AVWX_BASE_URL+requestPath+'?'+params).then(successCB).catch(failCB);
};

BIN
apps/avwx/avwx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

47
apps/avwx/interface.html Normal file
View File

@ -0,0 +1,47 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p>To use the <a href="https://account.avwx.rest/">AVWX</a> API, you need an account and generate an API token. The free "Hobby" plan is sufficient, but please consider supporting the AVWX project.</p>
<p>
<label class="form-label" for="AVWXtoken">AVWX API Token:</label>
<input class="form-input" type="text" id="AVWXtoken" placeholder="Your personal AVWX API Token" />
</p>
<p>
<button id="upload" class="btn btn-primary">Configure</button>
</p>
<p><div id="status"></div></p>
<script src="../../core/lib/interface.js"></script>
<script>
var AVWXsettings = {};
function onInit() {
// read in existing settings to preserve them during an update
try {
Util.readStorageJSON('avwx.json', currentSettings => {
if (currentSettings) {
AVWXsettings = currentSettings;
if ('AVWXtoken' in AVWXsettings) {
document.getElementById('AVWXtoken').value = AVWXsettings.AVWXtoken;
}
}
});
} catch (e) {
console.log("Failed to read existing settings: "+e);
}
}
document.getElementById("upload").addEventListener("click", function() {
AVWXsettings.AVWXtoken = document.getElementById('AVWXtoken').value;
Util.writeStorage('avwx.json', JSON.stringify(AVWXsettings), () => {
document.getElementById("status").innerHTML = 'AVWX configuration successfully uploaded to Bangle!';
});
});
</script>
</body>
</html>

18
apps/avwx/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "avwx",
"name": "AVWX Module",
"shortName":"AVWX",
"version":"1.00",
"description": "Module/library for the AVWX API",
"icon": "avwx.png",
"type": "module",
"tags": "outdoors",
"supports": ["BANGLEJS2"],
"provides_modules": ["avwx"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{ "name":"avwx", "url":"avwx.js" }
],
"data": [{ "name":"avwx.json" }]
}

View File

@ -11,7 +11,6 @@
'< Back': back,
'Buzz': {
value: "buzz" in settings ? settings.buzz : false,
format: () => (settings.buzz ? 'Yes' : 'No'),
onchange: () => {
settings.buzz = !settings.buzz;
save('buzz', settings.buzz);

View File

@ -1,2 +1,3 @@
0.01: Added app
0.02: Removed unneeded squares
0.03: Added settings with fullscreen option

View File

@ -1,3 +1,7 @@
var settings = Object.assign({
fullscreen: false,
}, require('Storage').readJSON("binaryclk.json", true) || {});
function draw() {
var dt = new Date();
var h = dt.getHours(), m = dt.getMinutes();
@ -11,10 +15,14 @@ function draw() {
g.clearRect(Bangle.appRect);
let i = 0;
var gap = 8;
var mgn = 20;
if (settings.fullscreen) {
gap = 12;
mgn = 0;
}
const sq = 29;
const gap = 8;
const mgn = 20;
const pos = sq + gap;
var pos = sq + gap;
for (let r = 3; r >= 0; r--) {
for (let c = 0; c < 4; c++) {
@ -26,14 +34,15 @@ function draw() {
}
i++;
}
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
}
g.clear();
draw();
var secondInterval = setInterval(draw, 60000);
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
if (!settings.fullscreen) {
Bangle.loadWidgets();
Bangle.drawWidgets();
}

View File

@ -1,7 +1,7 @@
{
"id": "binaryclk",
"name": "Bin Clock",
"version": "0.02",
"version": "0.03",
"description": "Clock face to show binary time in 24 hr format",
"icon": "app-icon.png",
"screenshots": [{"url":"screenshot.png"}],
@ -11,6 +11,8 @@
"allow_emulator": true,
"storage": [
{"name":"binaryclk.app.js","url":"app.js"},
{"name":"binaryclk.settings.js","url":"settings.js"},
{"name":"binaryclk.img","url":"app-icon.js","evaluate":true}
]
],
"data": [{"name":"binaryclk.json"}]
}

View File

@ -0,0 +1,22 @@
(function(back) {
var FILE = "binaryclk.json";
var settings = Object.assign({
fullscreen: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
E.showMenu({
"" : { "title" : "Bin Clock" },
"< Back" : () => back(),
'Fullscreen': {
value: settings.fullscreen,
onchange: v => {
settings.fullscreen = v;
writeSettings();
}
},
});
})

View File

@ -32,7 +32,6 @@
},
'Show Lock': {
value: settings.showLock,
format: () => (settings.showLock ? 'Yes' : 'No'),
onchange: () => {
settings.showLock = !settings.showLock;
save();
@ -40,7 +39,6 @@
},
'Hide Colon': {
value: settings.hideColon,
format: () => (settings.hideColon ? 'Yes' : 'No'),
onchange: () => {
settings.hideColon = !settings.hideColon;
save();

View File

@ -32,7 +32,6 @@
},
'Show Lock': {
value: settings.showLock,
format: () => (settings.showLock ? 'Yes' : 'No'),
onchange: () => {
settings.showLock = !settings.showLock;
save();
@ -40,7 +39,6 @@
},
'Hide Colon': {
value: settings.hideColon,
format: () => (settings.hideColon ? 'Yes' : 'No'),
onchange: () => {
settings.hideColon = !settings.hideColon;
save();

View File

@ -17,3 +17,4 @@
0.15: Edit holidays on device in settings
0.16: Add menu to fast open settings to edit holidays
Display Widgets in menus
0.17: Load holidays before events so the latter is not overpainted

View File

@ -43,24 +43,24 @@ const dowLbls = function() {
}();
const loadEvents = () => {
// add holidays & other events
events = (require("Storage").readJSON("calendar.days.json",1) || []).map(d => {
const date = new Date(d.date);
const o = {date: date, msg: d.name, type: d.type};
if (d.repeat) {
o.repeat = d.repeat;
}
return o;
});
// all alarms that run on a specific date
events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
events = events.concat((require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
const date = new Date(a.date);
const time = timeutils.decodeTime(a.t);
date.setHours(time.h);
date.setMinutes(time.m);
date.setSeconds(time.s);
return {date: date, msg: a.msg, type: "e"};
});
// add holidays & other events
(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
const date = new Date(d.date);
const o = {date: date, msg: d.name, type: d.type};
if (d.repeat) {
o.repeat = d.repeat;
}
events.push(o);
});
}));
};
const loadSettings = () => {
@ -280,14 +280,12 @@ const showMenu = function() {
setUI();
},
/*LANG*/"Exit": () => load(),
/*LANG*/"Settings": () => {
const appSettings = eval(require('Storage').read('calendar.settings.js'));
appSettings(() => {
/*LANG*/"Settings": () =>
eval(require('Storage').read('calendar.settings.js'))(() => {
loadSettings();
loadEvents();
showMenu();
});
},
}),
};
if (require("Storage").read("alarm.app.js")) {
menu[/*LANG*/"Launch Alarms"] = () => {

View File

@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.16",
"version": "0.17",
"description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],

View File

@ -30,7 +30,6 @@
},
/*LANG*/'show widgets': {
value: !!settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: x => save('showWidgets', x),
},
/*LANG*/'update interval': {
@ -45,7 +44,6 @@
},
/*LANG*/'show big weather': {
value: !!settings.showBigWeather,
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
onchange: x => save('showBigWeather', x),
},
/*LANG*/'colorize icons': ()=>showCircleMenus()
@ -87,8 +85,7 @@
const colorizeIconKey = circleName + "colorizeIcon";
menu[/*LANG*/'circle ' + circleId] = {
value: settings[colorizeIconKey] || false,
format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
onchange: x => save(colorizeIconKey, x),
onchange: x => save(colorizeIconKey, x),
};
}
E.showMenu(menu);

View File

@ -9,7 +9,6 @@
'': { 'title': 'CLI complete clk' },
'Show battery': {
value: "battery" in settings ? settings.battery : false,
format: () => (settings.battery ? 'Yes' : 'No'),
onchange: () => {
settings.battery = !settings.battery;
save('battery', settings.battery);
@ -27,7 +26,6 @@
},
'Show weather': {
value: "weather" in settings ? settings.weather : false,
format: () => (settings.weather ? 'Yes' : 'No'),
onchange: () => {
settings.weather = !settings.weather;
save('weather', settings.weather);
@ -35,7 +33,6 @@
},
'Show steps': {
value: "steps" in settings ? settings.steps : false,
format: () => (settings.steps ? 'Yes' : 'No'),
onchange: () => {
settings.steps = !settings.steps;
save('steps', settings.steps);
@ -43,7 +40,6 @@
},
'Show heartrate': {
value: "heartrate" in settings ? settings.heartrate : false,
format: () => (settings.heartrate ? 'Yes' : 'No'),
onchange: () => {
settings.heartrate = !settings.heartrate;
save('heartrate', settings.heartrate);

View File

@ -8,3 +8,4 @@
0.08: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.
0.09: Fix colors settings, where color was stored as string instead of the expected int.
0.10: Fix touch region for letters

View File

@ -107,7 +107,7 @@ exports.input = function(options) {
"ram";
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Choose character by draging along red rectangle at bottom of screen
if (event.y >= ( (R.y+R.h) - 12 )) {
if (event.y >= ( (R.y+R.h) - 26 )) {
// Translate x-position to character
if (event.x < ABCPADDING) { abcHL = 0; }
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
@ -139,7 +139,7 @@ exports.input = function(options) {
// 12345678901234567890
// Choose number or puctuation by draging on green rectangle
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
else if ((event.y < ( (R.y+R.h) - 26 )) && (event.y > ( (R.y+R.h) - 52 ))) {
// Translate x-position to character
if (event.x < NUMPADDING) { numHL = 0; }
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }

View File

@ -1,6 +1,6 @@
{ "id": "dragboard",
"name": "Dragboard",
"version":"0.09",
"version":"0.10",
"description": "A library for text input via swiping keyboard",
"icon": "app.png",
"type":"textinput",

View File

@ -2,3 +2,4 @@
0.02: Allow boot exceptions, e.g. to load DST
0.03: Permit exceptions to load in low-power mode, e.g. daylight saving time.
Also avoid polluting global scope.
0.04: Enhance menu: enable bluetooth, visit settings & visit recovery

View File

@ -61,14 +61,13 @@ var reload = function () {
nextDraw = undefined;
},
btn: function () {
E.showPrompt("Restore watch to full power?").then(function (v) {
if (v) {
drainedRestore();
}
else {
reload();
}
});
var menu = {
"Restore to full power": drainedRestore,
"Enable BLE": function () { return NRF.wake(); },
"Settings": function () { return load("setting.app.js"); },
"Recovery": function () { return Bangle.showRecoveryMenu(); },
};
E.showMenu(menu);
}
});
Bangle.CLOCK = 1;

View File

@ -79,13 +79,13 @@ const reload = () => {
nextDraw = undefined;
},
btn: () => {
E.showPrompt("Restore watch to full power?").then(v => {
if(v){
drainedRestore();
}else{
reload();
}
})
const menu = {
"Restore to full power": drainedRestore,
"Enable BLE": () => NRF.wake(),
"Settings": () => load("setting.app.js"),
"Recovery": () => Bangle.showRecoveryMenu(),
};
E.showMenu(menu);
}
});
Bangle.CLOCK=1;

View File

@ -1,12 +1,10 @@
{
"id": "drained",
"name": "Drained",
"version": "0.03",
"version": "0.04",
"description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals",
"readme": "README.md",
"icon": "icon.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [

View File

@ -1 +1,2 @@
0.01: attempt to import
0.02: Make it possible for Fastload Utils to fastload into this app.

View File

@ -1,5 +1,7 @@
// App Forge
"Bangle.loadWidgets()"; // Facilitates fastloading to this app via Fastload Utils, while still not loading widgets on standard `load` calls.
st = require('Storage');
l = /^a\..*\.js$/;

View File

@ -1,6 +1,6 @@
{ "id": "forge",
"name": "App Forge",
"version":"0.01",
"version":"0.02",
"description": "Easy way to run development versions of your apps",
"icon": "app.png",
"readme": "README.md",

View File

@ -23,7 +23,6 @@
'< Back': back,
'Show Widgets': {
value: settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: () => {
settings.showWidgets = !settings.showWidgets;
save();

View File

@ -88,29 +88,34 @@ function onInit(device) {
document.getElementById("fw-unknown").style = "display:none";
document.getElementById("fw-ok").style = "";
}
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
console.log("DFU CRC = "+crc);
var version = `unknown (CRC ${crc})`;
Puck.eval("[E.CRC32(E.memoryArea(0xF7000,0x6000)),E.CRC32(E.memoryArea(0xF7000,0x7000))]", crcs => {
console.log("DFU CRC (6 pages) = "+crcs[0]);
console.log("DFU CRC (7 pages) = "+crcs[1]);
var version = `unknown (CRC ${crcs[1]})`;
var ok = true;
if (crc==1339551013) { version = "2v10.219"; ok = false; }
if (crc==1207580954) { version = "2v10.236"; ok = false; }
if (crc==3435933210) version = "2v11.52";
if (crc==46757280) version = "2v11.58";
if (crc==3508163280 || crc==1418074094) version = "2v12";
if (crc==4056371285) version = "2v13";
if (crc==1038322422) version = "2v14";
if (crc==2560806221) version = "2v15";
if (crc==2886730689) version = "2v16";
if (crc==156320890) version = "2v17";
if (crc==4012421318) version = "2v18";
if (crc==1856454048) version = "2v19";
if (crc==2893810756 || crc==1273571156) version = "2v20";
if (crcs[0] == 1787004733) { // check 6 page CRC - the 7th page isn't used in 2v20
version = "2v20";
} else { // for other versions all 7 pages are used, check those
var crc = crcs[1];
if (crc==1339551013) { version = "2v10.219"; ok = false; }
if (crc==1207580954) { version = "2v10.236"; ok = false; }
if (crc==3435933210) version = "2v11.52";
if (crc==46757280) version = "2v11.58";
if (crc==3508163280 || crc==1418074094) version = "2v12";
if (crc==4056371285) version = "2v13";
if (crc==1038322422) version = "2v14";
if (crc==2560806221) version = "2v15";
if (crc==2886730689) version = "2v16";
if (crc==156320890) version = "2v17";
if (crc==4012421318) version = "2v18";
if (crc==1856454048) version = "2v19";
}
if (!ok) {
version += `(&#9888; update required)`;
}
document.getElementById("boot-version").innerHTML = version;
var versionNumber = parseFloat(version.replace(".","").replace("v","."));
if (versionNumber>=2.15)
if (versionNumber>=2.20)
document.getElementById("fw-old-bootloader-msg").style.display = "none";
});
}
@ -426,7 +431,7 @@ function handleUpload() {
storage:[
{name:"RAM", content:hexJS},
]
});
}, { noFinish: true });
}
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);

View File

@ -43,7 +43,6 @@
/*LANG*/"Step Goal Notification": {
value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false,
format: () => (settings.stepGoalNotification ? 'Yes' : 'No'),
onchange: () => {
settings.stepGoalNotification = !settings.stepGoalNotification;
setSettings();

2
apps/intervals/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: First Release
0.02: Changing resolution to seconds instead of 5 seconds

View File

@ -149,9 +149,9 @@ function showMenu()
"START" : function() { startSession(); },
"Sets" : { value : settings.sets,min:0,max:20,step:1,onchange : v => { settings.sets=v; } },
"Work minutes" : { value : settings.workmin,min:0,max:59,step:1,onchange : v => { settings.workmin=v; } },
"Work seconds" : { value : settings.workseg,min:0,max:59,step:5,onchange : v => { settings.workseg=v; } },
"Work seconds" : { value : settings.workseg,min:0,max:59,step:1,onchange : v => { settings.workseg=v; } },
"Rest minutes" : { value : settings.restmin,min:0,max:59,step:1,onchange : v => { settings.restmin=v; } },
"Rest seconds" : { value : settings.restseg,min:0,max:59,step:5,onchange : v => { settings.restseg=v; } },
"Rest seconds" : { value : settings.restseg,min:0,max:59,step:1,onchange : v => { settings.restseg=v; } },
"Signal type" : { value : settings.buzz,format : v => v?"Buzz":"Beep",onchange : v => { settings.buzz=v; }}
};

View File

@ -2,7 +2,7 @@
"id": "intervals",
"name": "Intervals App",
"shortName": "Intervals",
"version": "0.01",
"version": "0.02",
"description": "Intervals for training. It is possible to configure work time and rest time and number of sets.",
"icon": "intervals.png",
"tags": "",

View File

@ -76,7 +76,6 @@ var bg_code = [
},
'Full Screen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
@ -120,7 +119,6 @@ var bg_code = [
},
'Disable alarm functionality': {
value: settings.disableAlarms,
format: () => (settings.disableAlarms ? 'Yes' : 'No'),
onchange: () => {
settings.disableAlarms = !settings.disableAlarms;
save();
@ -128,7 +126,6 @@ var bg_code = [
},
'Disable data pages functionality': {
value: settings.disableData,
format: () => (settings.disableData ? 'Yes' : 'No'),
onchange: () => {
settings.disableData = !settings.disableData;
save();
@ -136,7 +133,6 @@ var bg_code = [
},
'Random colors on open': {
value: settings.randomColors,
format: () => (settings.randomColors ? 'Yes' : 'No'),
onchange: () => {
settings.randomColors = !settings.randomColors;
save();

View File

@ -27,13 +27,12 @@
}
var font_options = ["Limelight","GochiHand","Grenadier","Monoton"];
E.showMenu({
'': { 'title': 'Limelight Clock' },
'< Back': back,
'Full Screen': {
value: s.fullscreen,
format: () => (s.fullscreen ? 'Yes' : 'No'),
onchange: () => {
s.fullscreen = !s.fullscreen;
save();
@ -50,7 +49,6 @@
},
'Vector Font': {
value: s.vector,
format: () => (s.vector ? 'Yes' : 'No'),
onchange: () => {
s.vector = !s.vector;
save();
@ -68,7 +66,6 @@
},
'Second Hand': {
value: s.secondhand,
format: () => (s.secondhand ? 'Yes' : 'No'),
onchange: () => {
s.secondhand = !s.secondhand;
save();

View File

@ -0,0 +1 @@
0.1: init app

21
apps/line_clock/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Paul Spenke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
apps/line_clock/README.md Normal file
View File

@ -0,0 +1,11 @@
# Line Clock
This app displays a simple, different looking, analog clock. It considers the
currently configured "theme" (and may therefore look different than shown in
the screenshot on your watch depending on which theme you prefer).
![](app-screenshot.png)
## License
[MIT License](LICENSE)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgYMJh/4AgUD+AeKgIRDj/+n41O/4RQABcfIJYAEKZgAkL4U/8ARNBwIRP/+AGx6YBPSH/4ASPh/A/hfDAAZAHg/8gP/LguSoARHEwIRFiVJkDCFjgRHgEJkg4CcwQjIAAMEHAUDCoIRB46kIHAkH//xLIw4I8eAnCNKHAYAO/xxEABg4ByASPHAkBKAbUE/5xGhP//wRFv4RDOIYIB//ACQr1FHAIRJAA0TCAP/ZwIALgYRJVowRCj/4BIkBLIgABgRHC/KqFaI4RC5MkJBlPR4UECJizJJwoAKCKImVQAwAJv0HL5S6CbwIjLCKMAn4RDh0/LMKMhWaYAKA="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

287
apps/line_clock/app.js Normal file
View File

@ -0,0 +1,287 @@
const handWidth = 6;
const hourRadius = 4;
const hourWidth = 8;
const hourLength = 40;
const hourSLength = 20;
const radius = 220;
const lineOffset = 115;
const hourOffset = 32;
const numberOffset = 85;
const numberSize = 22;
const storage = require('Storage');
const SETTINGS_FILE = "line_clock.setting.json";
let initialSettings = {
showLock: true,
showMinute: true,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || initialSettings;
for (const key in saved_settings) {
initialSettings[key] = saved_settings[key];
}
let gWidth = g.getWidth(), gCenterX = gWidth/2;
let gHeight = g.getHeight(), gCenterY = gHeight/2;
let currentTime = new Date();
let currentHour = currentTime.getHours();
let currentMinute = currentTime.getMinutes();
let drawTimeout;
function imgLock() {
return {
width : 16, height : 16, bpp : 1,
transparent : 0,
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
};
}
/**
* Retrieves the angle of the hour hand for the current time.
*
* @returns {number} The angle of the hour hand in degrees.
*/
function getHourHandAngle() {
let hourHandAngle = 30 * currentHour;
hourHandAngle += 0.5 * currentMinute;
return hourHandAngle;
}
let hourAngle = getHourHandAngle();
/**
* Converts degrees to radians.
*
* @param {number} degrees - The degrees to be converted to radians.
* @return {number} - The equivalent value in radians.
*/
function degreesToRadians(degrees) {
return degrees * (Math.PI / 180);
}
/**
* Rotates an array of points around a given angle and radius.
*
* @param {Array} points - The array of points to be rotated.
* @param {number} angle - The angle in degrees to rotate the points.
* @param {number} rad - The radius to offset the rotation.
* @returns {Array} - The array of rotated points.
*/
function rotatePoints(points, angle, rad) {
const ang = degreesToRadians(angle);
const hAng = degreesToRadians(hourAngle);
const rotatedPoints = [];
points.map(function(point) {
return {
x: point.x * Math.cos(ang) - point.y * Math.sin(ang),
y: point.x * Math.sin(ang) + point.y * Math.cos(ang)
};
}).forEach(function(point) {
rotatedPoints.push(point.x + gCenterX - (rad * Math.sin(hAng)));
rotatedPoints.push(point.y + gCenterY + (rad * Math.cos(hAng)));
});
return rotatedPoints;
}
/**
* Draws a hand on the canvas.
*
* @function drawHand
*
* @returns {void}
*/
function drawHand() {
g.setColor(0xF800);
const halfWidth = handWidth / 2;
const points = [{
x: -halfWidth,
y: -gHeight
}, {
x: halfWidth,
y: -gHeight
}, {
x: halfWidth,
y: gHeight
}, {
x: -halfWidth,
y: gHeight
}];
g.fillPolyAA(rotatePoints(points, hourAngle, 0));
}
/**
* Retrieves the hour coordinates for a given small flag.
* @param {boolean} small - Determines if the flag is small.
* @returns {Array} - An array of hour coordinates.
*/
function getHourCoordinates(small) {
const dist = small ? (hourSLength - hourLength) : 0;
const halfWidth = hourWidth / 2;
const gh = gHeight + lineOffset;
return [{
x: -halfWidth,
y: -gh - dist
}, {
x: halfWidth,
y: -gh - dist
}, {
x: halfWidth,
y: -gh + hourLength
}, {
x: -halfWidth,
y: -gh + hourLength
}];
}
/**
* Assign the given time to the hour dot on the clock face.
*
* @param {number} a - The time value to assign to the hour dot.
* @return {void}
*/
function hourDot(a) {
const h = gHeight + lineOffset;
const rotatedPoints = rotatePoints(
[{
x: 0,
y: -h + hourLength - (hourRadius / 2)
}], a, radius
);
g.fillCircle(rotatedPoints[0], rotatedPoints[1], hourRadius);
}
/**
* Convert an hour into a number and display it on the clock face.
*
* @param {number} a - The hour to be converted (between 0 and 360 degrees).
*/
function hourNumber(a) {
const h = gHeight + lineOffset;
const rotatedPoints = rotatePoints(
[{
x: 0,
y: -h + hourLength + hourOffset
}], a, radius
);
g.drawString(String(a / 30), rotatedPoints[0], rotatedPoints[1]);
}
/**
* Draws a number on the display.
*
* @param {number} n - The number to be drawn.
* @return {void}
*/
function drawNumber(n) {
const h = gHeight + lineOffset;
const halfWidth = handWidth / 2;
const rotatedPoints = rotatePoints(
[{
x: 0,
y: -h + hourLength + numberOffset
}], hourAngle, radius
);
g.setColor(0xF800);
g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize+ halfWidth);
g.setColor(g.theme.bg);
g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize - halfWidth);
g.setColor(g.theme.fg);
g.setFont("Vector:"+numberSize);
g.drawString(String(n), rotatedPoints[0], rotatedPoints[1]);
}
const hourPoints = getHourCoordinates(false);
const hourSPoints = getHourCoordinates(true);
/**
* Draws an hour on a clock face.
*
* @param {number} h - The hour to be drawn on the clock face.
* @return {undefined}
*/
function drawHour(h) {
if (h === 0) { h= 12; }
if (h === 13) { h= 1; }
g.setColor(g.theme.fg);
g.setFont("Vector:32");
const a = h * 30;
g.fillPolyAA(rotatePoints(hourPoints, a, radius));
g.fillPolyAA(rotatePoints(hourSPoints, a + 15, radius));
hourNumber(a);
hourDot(a + 5);
hourDot(a + 10);
hourDot(a + 20);
hourDot(a + 25);
}
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
function lockListenerBw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
draw();
}
Bangle.on('lock', lockListenerBw);
Bangle.setUI({
mode : "clock",
// TODO implement https://www.espruino.com/Bangle.js+Fast+Load
// remove : function() {
// Bangle.removeListener('lock', lockListenerBw);
// if (drawTimeout) clearTimeout(drawTimeout);
// drawTimeout = undefined;
// }
});
/**
* Draws a clock on the canvas using the current time.
*
* @return {undefined}
*/
function draw() {
queueDraw();
currentTime = new Date();
currentHour = currentTime.getHours();
if (currentHour > 12) {
currentHour -= 12;
}
currentMinute = currentTime.getMinutes();
hourAngle = getHourHandAngle();
g.clear();
g.setFontAlign(0, 0);
g.setColor(g.theme.bg);
g.fillRect(0, 0, gWidth, gHeight);
if(initialSettings.showLock && Bangle.isLocked()){
g.setColor(g.theme.fg);
g.drawImage(imgLock(), gWidth-16, 2);
}
drawHour(currentHour);
drawHour(currentHour-1);
drawHour(currentHour+1);
drawHand();
if(initialSettings.showMinute){
drawNumber(currentMinute);
}
}
draw();

View File

@ -0,0 +1,19 @@
{ "id": "line_clock",
"name": "Line Clock",
"shortName":"Line Clock",
"version":"0.1",
"description": "a readable analog clock",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"line_clock.app.js","url":"app.js"},
{"name":"line_clock.img","url":"app-icon.js","evaluate":true},
{"name":"line_clock.settings.js","url":"settings.js"}
],
"data":[{"name":"line_clock.setting.json"}]
}

View File

@ -0,0 +1,37 @@
(function(back) {
const SETTINGS_FILE = "line_clock.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
showLock: true,
showMinute: true,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': 'Line Clock' },
'< Back': back,
'Show Lock': {
value: settings.showLock,
onchange: () => {
settings.showLock = !settings.showLock;
save();
},
},
'Show Minute': {
value: settings.showMinute,
onchange: () => {
settings.showMinute = !settings.showMinute;
save();
},
}
});
})

View File

@ -32,7 +32,6 @@
},
'Show Lock': {
value: settings.showLock,
format: () => (settings.showLock ? 'Yes' : 'No'),
onchange: () => {
settings.showLock = !settings.showLock;
save();
@ -40,7 +39,6 @@
},
'Hide Colon': {
value: settings.hideColon,
format: () => (settings.hideColon ? 'Yes' : 'No'),
onchange: () => {
settings.hideColon = !settings.hideColon;
save();

View File

@ -67,4 +67,4 @@ exports.getColor = function(msg,options) {
"youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors
}[s]||options.default;
};

View File

@ -25,7 +25,6 @@
'< Back': back,
'Show Widgets': {
value: settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: () => {
settings.showWidgets = !settings.showWidgets;
save();

View File

@ -32,3 +32,6 @@
0.25: Enable scaled image filtering on 2v19+ firmware
0.26: Ensure that when redrawing, we always cancel any in-progress track draw
0.27: Display message if no map is installed
0.28: Fix rounding errors
0.29: Keep exit at bottom of menu
Speed up latLonToXY for track rendering

View File

@ -65,14 +65,16 @@ function redraw() {
// Draw the POIs
function drawPOI() {
let waypoints;
try {
var waypoints = require("waypoints").load();
waypoints = require("waypoints").load();
} catch (ex) {
// Waypoints module not available.
return;
}
g.setFont("Vector", 18);
waypoints.forEach((wp, idx) => {
if (wp.lat === undefined || wp.lon === undefined) return;
var p = m.latLonToXY(wp.lat, wp.lon);
var sz = 2;
g.setColor(0,0,0);
@ -80,7 +82,7 @@ function drawPOI() {
g.setColor(0,0,0);
g.drawString(wp.name, p.x, p.y);
//print(wp.name);
})
});
}
function isInside(rect, e, w, h) {

View File

@ -45,7 +45,11 @@
<div id="map">
</div>
<div id="controls">
<div style="display:inline-block;text-align:center;vertical-align: top;" id="3bitdiv"> <input type="checkbox" id="3bit"></input><br/><span>3 bit</span></div>
<div style="display:inline-block;text-align:left;vertical-align: top;" id="3bitdiv">
<input type="checkbox" id="3bit"></input><span>3 bit</span>
<br>
<input type="checkbox" id="preview"><span>Preview</span>
</div>
<div class="form-group" style="display:inline-block;">
<select class="form-select" id="mapSize">
<option value="4">Small (4x4)</option>
@ -143,11 +147,13 @@ TODO:
let mapsLoadedContainer = document.getElementById("mapsLoadedContainer");
mapsLoadedContainer.innerHTML = "";
loadedMaps = [];
const mapsLoaded = [];
Puck.write(`\x10Bluetooth.println(require("Storage").list(/openstmap\\.\\d+\\.json/))\n`,function(files) {
Puck.eval(`require("Storage").list(/openstmap\\.\\d+\\.json/)\n`,function(files) {
console.log("MAPS:",files);
files.sort();
let promise = Promise.resolve();
files.trim().split(",").forEach(filename => {
files.forEach(filename => {
if (filename=="") return;
promise = promise.then(() => new Promise(resolve => {
Util.readStorageJSON(filename, mapInfo => {
@ -155,7 +161,7 @@ TODO:
let mapNumber = filename.match(/\d+/)[0]; // figure out what map number we are
loadedMaps[mapNumber] = mapInfo;
if (mapInfo!==undefined) {
let latlon = L.latLng(mapInfo.lat, mapInfo.lon);
mapsLoaded.push({mapNumber: mapNumber, mapInfo: mapInfo});
mapsLoadedContainer.innerHTML += `
<div class="tile">
<div class="tile-icon">
@ -171,19 +177,27 @@ TODO:
</div>
</div>
`;
setTimeout(function() {
let map = L.map(`tile-map-${mapNumber}`);
L.tileLayer(PREVIEWTILELAYER, {
maxZoom: 18
}).addTo(map);
let marker = new L.marker(latlon).addTo(map);
map.fitBounds(latlon.toBounds(2000/*meters*/), {animation: false});
}, 500);
}
resolve();
});
}));
});
promise = promise.then(() => new Promise(resolve => {
setTimeout(() => {
mapsLoaded.forEach(mapLoaded => {
let latlon = L.latLng(mapLoaded.mapInfo.lat, mapLoaded.mapInfo.lon);
let map = L.map(`tile-map-${mapLoaded.mapNumber}`);
L.tileLayer(PREVIEWTILELAYER, {
maxZoom: 18
}).addTo(map);
let marker = new L.marker(latlon).addTo(map);
const dist = mapLoaded.mapInfo.scale * mapLoaded.mapInfo.tilesize * mapLoaded.mapInfo.w;
map.fitBounds(latlon.toBounds(dist/2/*meters*/), {animation: false});
});
}, 0);
resolve();
}));
promise = promise.then(() => new Promise(resolve => {
if (!loadedMaps.length) {
mapsLoadedContainer.innerHTML += `
@ -275,7 +289,9 @@ TODO:
options.width = TILESIZE;
options.height = TILESIZE;
var imgstr = imageconverter.RGBAtoString(rgba, options);
//ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); // write preview
if (document.getElementById("preview").checked) {
ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); // write preview
}
/*var compress = 'require("heatshrink").decompress('
if (!imgstr.startsWith(compress)) throw "Data in wrong format";
imgstr = imgstr.slice(compress.length,-1);*/

View File

@ -2,7 +2,7 @@
"id": "openstmap",
"name": "OpenStreetMap",
"shortName": "OpenStMap",
"version": "0.27",
"version": "0.29",
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
"readme": "README.md",
"icon": "app.png",

View File

@ -38,17 +38,17 @@ if (m.map) {
m.lat = m.map.lat; // position of middle of screen
m.lon = m.map.lon; // position of middle of screen
}
var CX = g.getWidth()/2;
var CY = g.getHeight()/2;
// return number of tiles drawn
exports.draw = function() {
var cx = g.getWidth()/2;
var cy = g.getHeight()/2;
var p = Bangle.project({lat:m.lat,lon:m.lon});
let count = 0;
m.maps.forEach((map,idx) => {
var d = map.scale/m.scale;
var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx;
var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy;
var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - CX;
var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - CY;
var o = {};
var s = map.tilesize;
if (d!=1) { // if the two are different, add scaling
@ -76,7 +76,7 @@ exports.draw = function() {
for (var x=ox,ttx=tx; x<mx && ttx<map.w; x+=s,ttx++) {
for (var y=oy,tty=ty;y<my && tty<map.h;y+=s,tty++) {
o.frame = ttx+(tty*map.w);
g.drawImage(img,x,y,o);
g.drawImage(img,Math.round(x),Math.round(y),o);
count++;
}
}
@ -85,14 +85,12 @@ exports.draw = function() {
};
/// Convert lat/lon to pixels on the screen
exports.latLonToXY = function(lat, lon) {
var p = Bangle.project({lat:m.lat,lon:m.lon});
var q = Bangle.project({lat:lat, lon:lon});
var cx = g.getWidth()/2;
var cy = g.getHeight()/2;
exports.latLonToXY = function(lat, lon) { "ram"
var p = Bangle.project({lat:m.lat,lon:m.lon}),
q = Bangle.project({lat:lat, lon:lon});
return {
x : (q.x-p.x)/m.scale + cx,
y : cy - (q.y-p.y)/m.scale
x : Math.round((q.x-p.x)/m.scale + CX),
y : Math.round(CY - (q.y-p.y)/m.scale)
};
};
@ -102,4 +100,4 @@ exports.scroll = function(x,y) {
var b = Bangle.project({lat:m.lat+1,lon:m.lon+1});
this.lon += x * m.scale / (a.x-b.x);
this.lat -= y * m.scale / (a.y-b.y);
};
};

View File

@ -23,7 +23,7 @@
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
var theme_options = ['System', 'Light', 'Dark'];
E.showMenu({
'': { 'title': 'Pebble Clock' },
/*LANG*/'< Back': back,
@ -48,7 +48,6 @@
},
/*LANG*/'Show Lock': {
value: settings.showlock,
format: () => (settings.showlock ? /*LANG*/'Yes' : /*LANG*/'No'),
onchange: () => {
settings.showlock = !settings.showlock;
save();

View File

@ -4,3 +4,4 @@
Support for fast loading
0.04: Localisation request: added Miles and AM/PM
0.05: Prevent exceptions from halting the draw cycle
0.06: Fix Settings page to ensure that the currently set distance is displayed (not 0.75)

View File

@ -2,7 +2,7 @@
"id": "pebbled",
"name": "Pebble Clock with distance",
"shortName": "Pebble + distance",
"version": "0.05",
"version": "0.06",
"description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).",
"readme": "README.md",
"icon": "pebbled.png",

View File

@ -36,7 +36,7 @@
},
},
'Step length': {
value: 0.75 || s.avStep,
value: s.avStep || 0.75,
min: 0.2,
max: 1.5,
step: 0.01,

View File

@ -2,3 +2,4 @@
0.02: Major speed improvement. Added more stars. Up to 500!
0.03: Added more stars and constellations. Now it shows 20 constellations.
0.04: Use default Bangle formatter for booleans
0.05: Added more constellations (scorpio and aguila)

View File

@ -2,7 +2,7 @@
"id": "planetarium",
"name": "Planetarium",
"shortName": "Planetarium",
"version": "0.04",
"version": "0.05",
"description": "Planetarium showing up to 500 stars using the watch location and time",
"icon": "planetarium.png",
"tags": "",

View File

@ -38,3 +38,7 @@ Draco
e_15 131,131 70,70 382,382 e_15,382 187,187 423,423 e_16,e_16 207,207 122,122 e_17,e_17 232,232 342,342 452,452 428
Pegasus
92 85,138 54,54 85,138 92,283 85,283 389,160 85,92 258,258 297,297 83
Aguila
12 249,249 271,249 170,249 217,12 365,120 12
Scorpius
14 105,14 80,14 152,14 137,137 76,332 239,239 41,76 188,188 332,41 181,181 27

1 Orion
38 e_15 131,131 70,70 382,382 e_15,382 187,187 423,423 e_16,e_16 207,207 122,122 e_17,e_17 232,232 342,342 452,452 428
39 Pegasus
40 92 85,138 54,54 85,138 92,283 85,283 389,160 85,92 258,258 297,297 83
41 Aguila
42 12 249,249 271,249 170,249 217,12 365,120 12
43 Scorpius
44 14 105,14 80,14 152,14 137,137 76,332 239,239 41,76 188,188 332,41 181,181 27

View File

@ -0,0 +1 @@
E.toArrayBuffer(atob("MDAEEREREREREURERERERERERBERERERERERERERERERREREREREREREREEREREREREREREREREURERERERERERERERBERERERERERERERFEREREREREREREREREERERERERERERERREREREREREREREREREQREREREREREREURBEhEURERERERERERERBEREREREREREURBESERIkRERERERERERBERERERERERFEQhERIhEiREREREREREREERERERERERFERCERIiIiJEREREREREREQRERERERERREREERASIiEkREREREREREQRERERERERREREQQERIiEkRERERERERERBEREREREUREREQRERAiIiEkRERERERERBEREREREURERERAERERISEkRERERERERBEREREREUREREREEQAREiJEREREREREREERERERFEREREREIiIRESIUREREREREREERERERFEREREREERABERIRREREREREREERERERFEREREREQREREREhREREREREREERERERFEREREREIiJBERESFEREREREREQRERERFEREREREIiQRERERIUREREREREQRERERRERERERCIiIhEQEREhREREREREQRERERRERERERCIiIiIRERESFEREREREQRERERREREREQiIiIiIiQRESJEREREREQRERERREREREQiIiIiIiIkFBJEREREREQRERERREREREQiIiIiIiJBESEkREREREQRERERREREREQiIiIiIiIiRCIiREREREQRERERREREREQiIiIiIiIiIkQiREREREQRERERREREREQiIiIiIiIiIiIiJEREREQRERERREREREQiIiIiIyIiIiIiIkREREQRERERREREREQiIiIkQjMiIiIkIkREREQRERERREREREREREREQiIiIiJEQiIiMkQRERERREQiIkREQRESIiIiIiJCQiMzIkQRERERREIiIgAAABEBERRCMzMzIiIhARERERERFEIiIiIiIiIzMzMzMzMhAAJAEQVRERERQjMzMzMzMzMzMiIhEAAAAAEiBURBERFCIzMzIiIhERAAAAARERERERESFERBEREUERAAAAAAAAARERERERERFREUJEQhERERERFEREREREREREREREREREREJEQkEREREUREREREREREREREREREREREEkREERERFEREREREREREREREQiJEREREQkRBERERFEJERCIiRCIiIiJEIiIkREREQSERARERESREIiIiIiRERERERERERCREVSIAEREREUREREREREREREREREREREREVREBEREREURERERBFERERERERERERERVEAAREREREQAAABEREREREREUREREREEAAAAAABEREREREREREREREREAAAAAAAAAAAABERERERERERERRERERERBEREREREREREREREREREREREUREREREREREQRERERERERERERERERERERRERERERERBERERERERERER"))

196
apps/quoteclock/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/quoteclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,15 @@
{
"id": "quoteclock",
"name": "Quote Clock",
"version": "0.01",
"description": "A clock showing quotes every hour",
"icon": "app.png",
"type": "clock",
"tags": "clock,shakespeare",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"quoteclock.app.js","url":"app.js"},
{"name":"quoteclock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -14,3 +14,4 @@
0.14: cleanup code and fix fastload issue
0.15: fix draw before widget hide
0.16: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
0.17: Add fullscreen option (on by default) to show widgets, adjust sidebar 1 and 2 when fullscreen is off

View File

@ -10,14 +10,22 @@
* Tap top or bottom right to instantly cycle to the next sidebar
* Uses pedometer widget to get latest step count
* Dependant apps are installed when Rebble installs
* Uses the whole screen, widgets are made invisible but still run in the background
* When in fullscreen widgets are made invisible but still run in the background
* The icon is James Dean - 'Rebel Without a Cause'
## Fullscreen
![](screenshot_rebble.png)
![](screenshot_rebble2.png)
![](screenshot_rebble3.png)
![](screenshot_rebble4.png)
## With widgets
![](screenshot_rebble_w1.png)
![](screenshot_rebble_w2.png)
![](screenshot_rebble_w3.png)
## Future Enhancements
* Support for Weather Icons in the Steps Sidebar

View File

@ -2,12 +2,12 @@
"id": "rebble",
"name": "Rebble Clock",
"shortName": "Rebble",
"version": "0.16",
"version": "0.17",
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
"readme": "README.md",
"icon": "rebble.png",
"dependencies": {"mylocation":"app"},
"screenshots": [{"url":"screenshot_rebble.png"}],
"screenshots": [{"url":"screenshot_rebble.png"}, {"url":"screenshot_rebble2.png"}, {"url":"screenshot_rebble3.png"}, {"url":"screenshot_rebble4.png"}, {"url":"screenshot_rebble_w1.png"}, {"url":"screenshot_rebble_w2.png"}, {"url":"screenshot_rebble_w3.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],

View File

@ -40,7 +40,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
}
let loadSettings=function() {
settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true,'sideTap':0};
settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'fullScreen': true, 'sideTap':0};
//sideTap 0 = on | 1 = sidebar1...
let tmp = require('Storage').readJSON(SETTINGS_FILE, 1) || settings;
@ -118,32 +118,60 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
if (drawCount % 60 == 0)
updateSunRiseSunSet(location.lat, location.lon);
g.reset();
g.setColor(g.theme.bg);
g.fillRect(0, 0, w2, h);
g.setColor(settings.bg);
g.fillRect(w2, 0, w, h);
if (settings.fullScreen) {
g.setColor(g.theme.bg);
g.fillRect(0, 0, w2, h);
g.setColor(settings.bg);
g.fillRect(w2, 0, w, h);
// time
g.setColor(g.theme.fg);
g.setFontKdamThmor();
g.setFontAlign(0, -1);
g.drawString(hh, w2/2, 10 + 0);
g.drawString(mm, w2/2, 10 + h/2);
// time
g.setColor(g.theme.fg);
g.setFontKdamThmor();
g.setFontAlign(0, -1);
g.drawString(hh, w2/2, 10 + 0);
g.drawString(mm, w2/2, 10 + h/2);
switch(sideBar) {
case 0:
drawSideBar1();
break;
case 1:
drawSideBar2();
break;
case 2:
drawSideBar3();
break;
switch(sideBar) {
case 0:
drawSideBar1();
break;
case 1:
drawSideBar2();
break;
case 2:
drawSideBar3();
break;
}
} else {
g.setColor(g.theme.bg);
g.fillRect(0, 24, 113, 176);
g.setColor(settings.bg);
g.fillRect(113, 24, 176, 176);
// time
g.setColor(g.theme.fg);
g.setFontKdamThmor();
g.setFontAlign(0, -1);
g.drawString(hh, 57, 24);
g.drawString(mm, 57, 100);
switch(sideBar) {
case 0:
drawSideBar1Alt();
break;
case 1:
drawSideBar2Alt();
break;
case 2:
drawSideBar3();
break;
}
}
drawCount++;
queueDraw();
}
@ -164,6 +192,16 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
drawDateAndCalendar(w3, h/2, dy, dd, mm);
}
let drawSideBar1Alt=function() {
let date = new Date();
let dy= require("date_utils").dow(date.getDay(),1).toUpperCase();
let dd= date.getDate();
let mm= require("date_utils").month(date.getMonth()+1,1).toUpperCase();
let yy = date.getFullYear();
drawDateAndCalendarAlt(145, 46, dy, dd, mm, yy);
}
let drawSideBar2=function() {
drawBattery(w2 + (w-w2-wb)/2, h/10, wb, 17);
@ -178,6 +216,14 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
setSmallFont();
g.setFontAlign(0, -1);
g.drawString(formatSteps(), w3, 7*h/8);
}
let drawSideBar2Alt=function() {
// steps
g.drawImage(boot_img, 113, 59, { scale: 1 });
setSmallFont();
g.setFontAlign(0, -1);
g.drawString(formatSteps(), 145, 122);
}
// sunrise, sunset times
@ -212,6 +258,28 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
g.setFontAlign(0, -1);
g.drawString(mm.toUpperCase(), x, y + 70);
}
let drawDateAndCalendarAlt=function(x, y, dy, dd, mm, yy) {
// day
setTextColor();
setSmallFont();
g.setFontAlign(0, -1);
g.drawString(dy.toUpperCase(), x, y);
drawCalendar(x - 18, y + 28, 35, 3, dd);
// month
setTextColor();
setSmallFont();
g.setFontAlign(0, -1);
g.drawString(mm.toUpperCase(), x, y + 70);
// year
setTextColor();
setSmallFont();
g.setFontAlign(0, -1);
g.drawString(yy, x, y + 94);
}
// at x,y width:wi thicknes:th
let drawCalendar=function(x,y,wi,th,str) {
@ -311,7 +379,10 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
delete Graphics.prototype.setFontKdamThmor;
Bangle.removeListener('charging',chargingListener);
if (settings.fullScreen) {
Bangle.removeListener('charging',chargingListener);
}
}
let main=function(){
@ -341,17 +412,17 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
});
}
Bangle.on('charging',chargingListener);
Bangle.loadWidgets();
require("widget_utils").hide();
if (settings.fullScreen) {
Bangle.on('charging',chargingListener);
require("widget_utils").hide();
} else {
Bangle.drawWidgets();
}
draw();
}
main();
}

View File

@ -2,7 +2,7 @@
const SETTINGS_FILE = "rebble.json";
// initialize with default settings...
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'sideTap':0};
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'fullScreen': true, 'sideTap':0};
//sideTap 0 = on| 1= sideBar1 | 2 = ...
// ...and overwrite them with any saved values
@ -37,6 +37,14 @@
localSettings.bg = bg_code[v];
save();
},
},
'Fullscreen': {
value: localSettings.fullScreen,
onchange: (v) => {
localSettings.fullScreen = v;
save();
showMenu();
}
},
'Auto Cycle': {
value: localSettings.autoCycle,
@ -74,4 +82,4 @@
}
showMenu();
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -45,3 +45,4 @@
0.36: When recording with 1 second periods, log time with one decimal.
0.37: 1 second periods + gps log => log when gps event is received, not with
setInterval.
0.38: Tweaks to speed up track rendering

View File

@ -213,230 +213,230 @@ function viewTrack(filename, info) {
});
};
menu['< Back'] = () => { viewTracks(); };
return E.showMenu(menu);
}
function plotTrack(info) { "ram"
function distance(lat1,long1,lat2,long2) { "ram"
var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360);
var y = lat2 - lat1;
return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180;
}
function plotTrack(info) { "ram"
function distance(lat1,long1,lat2,long2) { "ram"
var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360);
var y = lat2 - lat1;
return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180;
}
// Function to convert lat/lon to XY
var getMapXY;
if (info.qOSTM) {
// scale map to view full track
const max = Bangle.project({lat: info.maxLat, lon: info.maxLong});
const min = Bangle.project({lat: info.minLat, lon: info.minLong});
const scaleX = (max.x-min.x)/Bangle.appRect.w;
const scaleY = (max.y-min.y)/Bangle.appRect.h;
osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin
getMapXY = osm.latLonToXY.bind(osm);
} else {
getMapXY = function(lat, lon) { "ram"
return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale),
y:cy + Math.round((info.lat - lat)*info.scale)};
};
}
// Function to convert lat/lon to XY
var XY;
if (info.qOSTM) {
// scale map to view full track
const max = Bangle.project({lat: info.maxLat, lon: info.maxLong});
const min = Bangle.project({lat: info.minLat, lon: info.minLong});
const scaleX = (max.x-min.x)/Bangle.appRect.w;
const scaleY = (max.y-min.y)/Bangle.appRect.h;
osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin
XY = osm.latLonToXY.bind(osm);
} else {
XY = function(lat, lon) { "ram"
return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale),
y:cy + Math.round((info.lat - lat)*info.scale)};
};
}
E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn);
g.flip(); // on buffered screens, draw a not saying we're busy
g.clear(1);
var s = require("Storage");
var W = g.getWidth();
var H = g.getHeight();
var cx = W/2;
var cy = 24 + (H-24)/2;
if (!info.qOSTM) {
g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]);
g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50);
} else {
osm.lat = info.lat;
osm.lon = info.lon;
osm.draw();
g.setColor("#000");
E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn);
g.flip(); // on buffered screens, draw a not saying we're busy
g.clear(1);
var s = require("Storage");
var G = g;
var W = g.getWidth();
var H = g.getHeight();
var cx = W/2;
var cy = 24 + (H-24)/2;
if (!info.qOSTM) {
g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]);
g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50);
} else {
osm.lat = info.lat;
osm.lon = info.lon;
osm.draw();
g.setColor("#000");
}
var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude");
g.drawString(asTime(info.duration),10,220);
var f = require("Storage").open(info.filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var ox=0;
var oy=0;
var olat,olong,dist=0;
var i=0, c = l.split(",");
// skip until we find our first data
while(l!==undefined && c[latIdx]=="") {
c = l.split(",");
l = f.readLine(f);
}
// now start plotting
var lat = +c[latIdx];
var long = +c[lonIdx];
var mp = XY(lat, long);
g.moveTo(mp.x,mp.y);
g.setColor("#0f0");
g.fillCircle(mp.x,mp.y,5);
if (info.qOSTM) g.setColor("#f09");
else g.setColor(g.theme.fg);
l = f.readLine(f);
g.flip(); // force update
while(l!==undefined) {
c = l.split(",");l = f.readLine(f);
if (c[latIdx]=="")continue;
lat = +c[latIdx];
long = +c[lonIdx];
mp = XY(lat, long);
G.lineTo(mp.x,mp.y);
if (info.qOSTM) G.fillCircle(mp.x,mp.y,2); // make the track more visible
var d = distance(olat,olong,lat,long);
if (!isNaN(d)) dist+=d;
olat = lat;
olong = long;
ox = mp.x;
oy = mp.y;
if (++i > 100) { G.flip();i=0; }
}
g.setColor("#f00");
g.fillCircle(ox,oy,5);
if (info.qOSTM) g.setColor("#000");
else g.setColor(g.theme.fg);
g.drawString(require("locale").distance(dist,2),g.getWidth() / 2, g.getHeight() - 20);
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.fn, info);
}, isBTN3?BTN3:BTN1);
Bangle.drawWidgets();
g.flip();
}
function plotGraph(info, style) { "ram"
E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Calculating...",/*LANG*/"Track "+info.fn);
var filename = info.filename;
var infn = new Float32Array(80);
var infc = new Uint16Array(80);
var title;
var lt = 0; // last time
var tn = 0; // count for each time period
var strt, dur = info.duration;
var f = require("Storage").open(filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var nl = 0, c, i;
var factor = 1; // multiplier used for values when graphing
var timeIdx = info.fields.indexOf("Time");
if (l!==undefined) {
c = l.split(",");
strt = c[timeIdx];
}
if (style=="Heartrate") {
title = /*LANG*/"Heartrate (bpm)";
var hrmIdx = info.fields.indexOf("Heartrate");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[hrmIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[hrmIdx];
infc[i]++;
}
} else if (style=="Altitude") {
title = /*LANG*/"Altitude (m)";
var altIdx = info.fields.indexOf("Barometer Altitude");
if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[altIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[altIdx];
infc[i]++;
}
} else if (style=="Speed") {
// use locate to work out units
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
let units = localeStr.replace(/[0-9.]*/,"");
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
// title
title = /*LANG*/"Speed"+` (${units})`;
var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude");
g.drawString(asTime(info.duration),10,220);
var f = require("Storage").open(info.filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var ox=0;
var oy=0;
var olat,olong,dist=0;
var i=0, c = l.split(",");
// skip until we find our first data
while(l!==undefined && c[latIdx]=="") {
c = l.split(",");
l = f.readLine(f);
}
// now start plotting
var lat = +c[latIdx];
var long = +c[lonIdx];
var mp = getMapXY(lat, long);
g.moveTo(mp.x,mp.y);
g.setColor("#0f0");
g.fillCircle(mp.x,mp.y,5);
if (info.qOSTM) g.setColor("#f09");
else g.setColor(g.theme.fg);
l = f.readLine(f);
g.flip(); // force update
// now iterate
var p,lp = Bangle.project({lat:c[1],lon:c[2]});
var t,dx,dy,d,lt = c[timeIdx];
while(l!==undefined) {
c = l.split(",");l = f.readLine(f);
if (c[latIdx]=="")continue;
lat = +c[latIdx];
long = +c[lonIdx];
mp = getMapXY(lat, long);
g.lineTo(mp.x,mp.y);
if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible
var d = distance(olat,olong,lat,long);
if (!isNaN(d)) dist+=d;
olat = lat;
olong = long;
ox = mp.x;
oy = mp.y;
if (++i > 100) { g.flip();i=0; }
}
g.setColor("#f00");
g.fillCircle(ox,oy,5);
if (info.qOSTM) g.setColor("#000");
else g.setColor(g.theme.fg);
g.drawString(require("locale").distance(dist,2),g.getWidth() / 2, g.getHeight() - 20);
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.fn, info);
}, isBTN3?BTN3:BTN1);
Bangle.drawWidgets();
g.flip();
}
function plotGraph(info, style) { "ram"
E.showMenu(); // remove menu
E.showMessage(/*LANG*/"Calculating...",/*LANG*/"Track "+info.fn);
var filename = info.filename;
var infn = new Float32Array(80);
var infc = new Uint16Array(80);
var title;
var lt = 0; // last time
var tn = 0; // count for each time period
var strt, dur = info.duration;
var f = require("Storage").open(filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
l = f.readLine(f); // skip headers
var nl = 0, c, i;
var factor = 1; // multiplier used for values when graphing
var timeIdx = info.fields.indexOf("Time");
if (l!==undefined) {
c = l.split(",");
strt = c[timeIdx];
}
if (style=="Heartrate") {
title = /*LANG*/"Heartrate (bpm)";
var hrmIdx = info.fields.indexOf("Heartrate");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[hrmIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[hrmIdx];
++nl;c=l.split(",");
l = f.readLine(f);
if (c[latIdx] == "") {
continue;
}
t = c[timeIdx];
i = Math.round(80*(t - strt)/dur);
p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
dx = p.x-lp.x;
dy = p.y-lp.y;
d = Math.sqrt(dx*dx+dy*dy);
if (t!=lt) {
infn[i]+=d / (t-lt); // speed
infc[i]++;
}
} else if (style=="Altitude") {
title = /*LANG*/"Altitude (m)";
var altIdx = info.fields.indexOf("Barometer Altitude");
if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
while(l!==undefined) {
++nl;c=l.split(",");l = f.readLine(f);
if (c[altIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[altIdx];
infc[i]++;
}
} else if (style=="Speed") {
// use locate to work out units
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
let units = localeStr.replace(/[0-9.]*/,"");
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
// title
title = /*LANG*/"Speed"+` (${units})`;
var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude");
// skip until we find our first data
while(l!==undefined && c[latIdx]=="") {
c = l.split(",");
l = f.readLine(f);
}
// now iterate
var p,lp = Bangle.project({lat:c[1],lon:c[2]});
var t,dx,dy,d,lt = c[timeIdx];
while(l!==undefined) {
++nl;c=l.split(",");
l = f.readLine(f);
if (c[latIdx] == "") {
continue;
}
t = c[timeIdx];
i = Math.round(80*(t - strt)/dur);
p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
dx = p.x-lp.x;
dy = p.y-lp.y;
d = Math.sqrt(dx*dx+dy*dy);
if (t!=lt) {
infn[i]+=d / (t-lt); // speed
infc[i]++;
}
lp = p;
lt = t;
}
} else throw new Error("Unknown type "+style);
var min=100000,max=-100000;
for (var i=0;i<infn.length;i++) {
if (infc[i]>0) infn[i]=factor*infn[i]/infc[i];
else { // no data - search back and see if we can find something
for (var j=i-1;j>=0;j--)
if (infc[j]) { infn[i]=infn[j]; break; }
}
var n = infn[i];
if (n>max) max=n;
if (n<min) min=n;
lp = p;
lt = t;
}
// work out a nice grid value
var heightDiff = max-min;
var grid = 1;
while (heightDiff/grid > 8) {
grid*=2;
} else throw new Error("Unknown type "+style);
var min=100000,max=-100000;
for (var i=0;i<infn.length;i++) {
if (infc[i]>0) infn[i]=factor*infn[i]/infc[i];
else { // no data - search back and see if we can find something
for (var j=i-1;j>=0;j--)
if (infc[j]) { infn[i]=infn[j]; break; }
}
// draw
g.clear(1).setFont("6x8",1);
var r = require("graph").drawLine(g, infn, {
x:4,y:24,
width: g.getWidth()-24,
height: g.getHeight()-(24+8),
axes : true,
gridy : grid,
gridx : infn.length / 3,
title: title,
miny: min,
maxy: max,
xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
});
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.filename, info);
}, isBTN3?BTN3:BTN1);
g.flip();
var n = infn[i];
if (n>max) max=n;
if (n<min) min=n;
}
return E.showMenu(menu);
// work out a nice grid value
var heightDiff = max-min;
var grid = 1;
while (heightDiff/grid > 8) {
grid*=2;
}
// draw
g.clear(1).setFont("6x8",1);
var r = require("graph").drawLine(g, infn, {
x:4,y:24,
width: g.getWidth()-24,
height: g.getHeight()-(24+8),
axes : true,
gridy : grid,
gridx : infn.length / 3,
title: title,
miny: min,
maxy: max,
xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
});
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
var isBTN3 = "BTN3" in global;
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
setWatch(function() {
viewTrack(info.filename, info);
}, isBTN3?BTN3:BTN1);
g.flip();
}
showMainMenu();
showMainMenu();

View File

@ -2,7 +2,7 @@
"id": "recorder",
"name": "Recorder",
"shortName": "Recorder",
"version": "0.37",
"version": "0.38",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget,clkinfo",

View File

@ -1,2 +1 @@
require("heatshrink").decompress(atob("mEwghC/AF8OoMRC6nu8kRiAuT93uGCgWBGCguCGCgsBoMUGCQuBFYMRDAIwQFQUhDAIFCFx/hiUyC4NOGAKMP8MRmYwBjwwBCxkEFAIXBiYwBC4PuC5hxCC4IwCDwPgC5gpBpxxBiMSL4QWMgQpBIIKnEFxsikcxOISODCxkDmUiLIQADFxsjUIQWELp0iLwYuQgMzkUiFydBkcyFycOoMSXoIuST4YuTB4NBZwIuSABAuPAA5dQdSQuBoIXBLwouPiUxGAguOC4imDRh3hC4wuMgBABC44WMgBxBI4wuNgBxCC4MhAoQWNC4IwBU4guOgEBFQVBiguQGAi7PGBCMPGBAuRGAoWSGAYuTAH4AcA="))
require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA"))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -21,42 +21,34 @@
},
/*LANG*/'Red': {
value: !!settings.colorRed,
format: () => (settings.colorRed ? 'Yes' : 'No'),
onchange: x => save('colorRed', x),
},
/*LANG*/'Green': {
value: !!settings.colorGreen,
format: () => (settings.colorGreen ? 'Yes' : 'No'),
onchange: x => save('colorGreen', x),
},
/*LANG*/'Blue': {
value: !!settings.colorBlue,
format: () => (settings.colorBlue ? 'Yes' : 'No'),
onchange: x => save('colorBlue', x),
},
/*LANG*/'Magenta': {
value: !!settings.colorMagenta,
format: () => (settings.colorMagenta ? 'Yes' : 'No'),
onchange: x => save('colorMagenta', x),
},
/*LANG*/'Cyan': {
value: !!settings.colorCyan,
format: () => (settings.colorCyan ? 'Yes' : 'No'),
onchange: x => save('colorCyan', x),
},
/*LANG*/'Yellow': {
value: !!settings.colorYellow,
format: () => (settings.colorYellow ? 'Yes' : 'No'),
onchange: x => save('colorYellow', x),
},
/*LANG*/'Black': {
value: !!settings.colorBlack,
format: () => (settings.colorBlack ? 'Yes' : 'No'),
onchange: x => save('colorBlack', x),
},
/*LANG*/'White': {
value: !!settings.colorWhite,
format: () => (settings.colorWhite ? 'Yes' : 'No'),
onchange: x => save('colorWhite', x),
}
};

Some files were not shown because too many files have changed in this diff Show More