Merge branch 'espruino:master' into master
|
@ -277,7 +277,7 @@ and which gives information about the app for the Launcher.
|
|||
// 'game' - a game
|
||||
// 'bluetooth' - uses Bluetooth LE
|
||||
// 'system' - used by the system
|
||||
// 'clkinfo' - provides or uses clock_info module for data on your clock face (see modules/clock_info.js)
|
||||
// 'clkinfo' - provides or uses clock_info module for data on your clock face or clocks that support it (see apps/clock_info/README.md)
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
|
|
58
android.html
|
@ -20,9 +20,6 @@
|
|||
<title>Bangle.js App Loader</title>
|
||||
</head>
|
||||
<body>
|
||||
<!--<button id="test">Test</button>
|
||||
<div id="status"></div>-->
|
||||
|
||||
<header class="navbar-primary navbar">
|
||||
<section class="navbar-section" >
|
||||
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2" ><img src="img/banglejs-logo-sml.png" alt="Bangle.js">
|
||||
|
@ -76,20 +73,26 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="filter-nav">
|
||||
<label class="chip active" filterid="">Default</label>
|
||||
<label class="chip" filterid="clock">Clocks</label>
|
||||
<label class="chip" filterid="game">Games</label>
|
||||
<label class="chip" filterid="tool">Tools</label>
|
||||
<label class="chip" filterid="widget">Widgets</label>
|
||||
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||||
<label class="chip" filterid="outdoors">Outdoors</label>
|
||||
<label class="chip" filterid="favourites">Favourites</label>
|
||||
<label class="chip active" filterid="" data-tooltip="Show all apps">All</label>
|
||||
<label class="chip tooltip" filterid="clock" data-tooltip="To tell the time!">Clocks</label>
|
||||
<label class="chip tooltip" filterid="launch" data-tooltip="Choose which apps to launch">Launchers</label>
|
||||
<label class="chip tooltip" filterid="game" data-tooltip="Have fun!">Games</label>
|
||||
<label class="chip tooltip" filterid="tool" data-tooltip="Useful applications">Tools</label>
|
||||
<label class="chip tooltip" filterid="textinput" data-tooltip="To allow you to enter text">Keyboards</label>
|
||||
<label class="chip tooltip" filterid="widget" data-tooltip="Appear in the top bar of Bangle.js apps">Widgets</label>
|
||||
<label class="chip tooltip" filterid="bluetooth" data-tooltip="Using Bluetooth Functionality">Bluetooth</label>
|
||||
<label class="chip tooltip" filterid="outdoors" data-tooltip="For outdoor use">Outdoors</label>
|
||||
<label class="chip tooltip" filterid="ram" data-tooltip="Apps that don't save anything to flash memory">Online</label>
|
||||
<label class="chip tooltip" filterid="clkinfo" data-tooltip="Info displayed on clocks, or clocks with info">Clock Info</label>
|
||||
<label class="chip tooltip" filterid="favourites" data-tooltip="Apps that you've liked ❤️">Favourites</label>
|
||||
</div>
|
||||
<div class="sort-nav hidden">
|
||||
<span>Sort by:</span>
|
||||
<label class="chip active" sortid="">None</label>
|
||||
<label class="chip" sortid="created">New</label>
|
||||
<label class="chip" sortid="modified">Updated</label>
|
||||
<label class="chip hidden tooltip" sortid="created" data-tooltip="Most recent apps">New</label>
|
||||
<label class="chip hidden tooltip" sortid="modified" data-tooltip="Most recently changed">Updated</label>
|
||||
<label class="chip hidden tooltip" sortid="installs" data-tooltip="Most installed by users">Installed</label>
|
||||
<label class="chip hidden tooltip" sortid="favourites" data-tooltip="Most liked by users">Favourited</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -130,13 +133,14 @@
|
|||
<p>Using <a href="https://espruino.com/" target="_blank">Espruino</a>, Icons from <a href="https://icons8.com/" target="_blank">icons8.com</a></p>
|
||||
|
||||
<h3>Utilities</h3>
|
||||
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
||||
<button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button>
|
||||
<button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button>
|
||||
<button class="btn" id="installdefault">Install default apps</button>
|
||||
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button></p>
|
||||
<p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
|
||||
<button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
|
||||
<p><button class="btn tooltip" id="settime" data-tooltip="Set the Bangle's time to your Browser's time">Set Bangle.js Time</button>
|
||||
<button class="btn tooltip" id="removeall" data-tooltip="Delete everything, leave it blank">Remove all Apps</button>
|
||||
<button class="btn tooltip" id="reinstallall" data-tooltip="Re-install every app, leave all data">Reinstall apps</button>
|
||||
<button class="btn tooltip" id="installdefault" data-tooltip="Delete everything, install default apps">Install default apps</button>
|
||||
<button class="btn tooltip" id="installfavourite" data-tooltip="Delete everything, install your favourites">Install favourite apps</button>
|
||||
<button class="btn tooltip" id="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button></p>
|
||||
<p><button class="btn tooltip" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
|
||||
<button class="btn tooltip" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
|
||||
<h3>Settings</h3>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
|
@ -147,12 +151,24 @@
|
|||
<input type="checkbox" id="settings-settime">
|
||||
<i class="form-icon"></i> Always update time when we connect
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-usage-stats">
|
||||
<i class="form-icon"></i> Send app analytics to banglejs.com (apps installed, favourites, firmware version).<br/>
|
||||
<small>Used for 'Sort by Installed/Favourited' functionality. See the <a href="http://www.espruino.com/Privacy">privacy policy</a></small>.
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<select class="form-select form-inline" id="settings-lang" style="width: 10em">
|
||||
<option value="">None (English)</option>
|
||||
</select> <span>Translations (<a href="https://github.com/espruino/BangleApps/issues/1311" target="_blank">BETA - more info</a>). Any apps that are uploaded to Bangle.js after changing this will have any text automatically translated.</span>
|
||||
</div>
|
||||
<button class="btn" id="defaultsettings">Default settings</button>
|
||||
<details>
|
||||
<summary>Advanced Options</summary>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-minify">
|
||||
<i class="form-icon"></i> Minify apps before upload (⚠️DANGER⚠️: Not recommended. Uploads smaller, faster apps but this <b>will</b> break many apps)
|
||||
</label>
|
||||
<button class="btn" id="defaultsettings">Reset to default settings</button>
|
||||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
<h3>Device info</h3>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"dependencies" : { "clock_info":"module" },
|
||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"screenshots": [
|
||||
{"url":"orig.png"},
|
||||
{"url":"impl.png"},
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"shortName": "AGPS",
|
||||
"version": "0.05",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"sortorder": -1,
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tool,outdoors,agps,gps,a-gps",
|
||||
"tags": "tool,outdoors,agps,gps,a-gps,agps",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": []
|
||||
"storage": [],
|
||||
"sortorder": -1
|
||||
}
|
||||
|
|
|
@ -105,7 +105,8 @@ if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function(
|
|||
// show timings
|
||||
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
// ================================================== BOOT.JS
|
||||
// Append *.boot.js files
|
||||
// Append *.boot.js files.
|
||||
// Name files with a number - eg 'foo.5.boot.js' to enforce order (lowest first). Numbered files get placed before non-numbered
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||
let getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
"use strict";
|
||||
var __assign = Object.assign;
|
||||
var Layout = require("Layout");
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Bluetooth Heart Rate Monitor
|
||||
|
||||
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
|
||||
When this app is installed it overrides Bangle.js's built in heart rate monitor with an external Bluetooth one.
|
||||
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM')` event as if it came from the on board monitor.
|
||||
|
||||
|
@ -44,7 +44,7 @@ So far it has been tested on:
|
|||
|
||||
## Recorder plugin
|
||||
|
||||
The recorder plugin can record the BT HRM event (blue) and the original unchanged HRM event (green). This is mainly useful for debugging purposes or comparing the BT with the internal HRM, as the resulting "merged" HRM can be recordet using the default HRM recorder.
|
||||
The recorder plugin can record the BT HRM event (blue) and the original unchanged HRM event (green). This is mainly useful for debugging purposes or comparing the BT with the internal HRM, as the resulting "merged" HRM can be recorded using the default HRM recorder.
|
||||
|
||||
## Internals
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,44 @@
|
|||
# Bluetooth Heart Rate Monitor - Lite
|
||||
|
||||
When this app is installed it overrides Bangle.js's built in heart rate monitor with an external Bluetooth one.
|
||||
|
||||
The [`bthrm` app](https://banglejs.com/apps/?id=bthrm) is a much more sophisticated version of this app, however is uses a lot more
|
||||
RAM so may not be suitable for Bangle.js 1.
|
||||
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
|
||||
|
||||
This means it's compatible with many Bangle.js apps including:
|
||||
|
||||
* [Heart Rate Widget](https://banglejs.com/apps/#widhrt)
|
||||
* [Heart Rate Recorder](https://banglejs.com/apps/#heart)
|
||||
|
||||
It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm)
|
||||
as that requires live sensor data (rather than just BPM readings).
|
||||
|
||||
## Usage
|
||||
|
||||
Just install the app, then install an app that uses the heart rate monitor.
|
||||
|
||||
Once an app requests Heart Rate `bthrmlite` will automatically try and connect to the first bluetooth
|
||||
heart rate monitor it finds.
|
||||
|
||||
**To disable this and return to normal HRM, uninstall the app**
|
||||
|
||||
## Compatible Heart Rate Monitors
|
||||
|
||||
This works with any heart rate monitor providing the standard Bluetooth
|
||||
Heart Rate Service (`180D`) and characteristic (`2A37`).
|
||||
|
||||
So far it has been tested on:
|
||||
|
||||
* CooSpo Bluetooth Heart Rate Monitor
|
||||
|
||||
## Internals
|
||||
|
||||
This replaces `Bangle.setHRMPower` with its own implementation.
|
||||
|
||||
To get debug info, you can call `Bangle.enableBTHRMLog()` in the IDE to enable log messages.
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA=="))
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
let log = function() {};//print
|
||||
let gatt;
|
||||
|
||||
Bangle.enableBTHRMLog = function() {
|
||||
log = print;
|
||||
};
|
||||
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
log("setHRMPower ->", isOn, app);
|
||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
|
||||
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
|
||||
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
|
||||
isOn = Bangle._PWR.HRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
log("setHRMPower on", app);
|
||||
if (!gatt) {
|
||||
log("HRM not already on");
|
||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||
log("Found device "+device.id);
|
||||
device.on('gattserverdisconnected', function(reason) {
|
||||
gatt = undefined;
|
||||
});
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
log("Connected");
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService(0x180D);
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic(0x2A37);
|
||||
}).then(function(characteristic) {
|
||||
log("Got characteristic");
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var flags = dv.getUint8(0);
|
||||
// 0 = 8 or 16 bit
|
||||
// 1,2 = sensor contact
|
||||
// 3 = energy expended shown
|
||||
// 4 = RR interval
|
||||
var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit
|
||||
/* var idx = 2 + (flags&1); // index of next field
|
||||
if (flags&8) idx += 2; // energy expended
|
||||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
Bangle.emit('HRM',{
|
||||
bpm:bpm,
|
||||
confidence:100
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
log("Ready");
|
||||
console.log("Done!");
|
||||
}).catch(function(err) {
|
||||
console.log("BTHRM Error",err);
|
||||
gatt = undefined;
|
||||
// retry connection if we got a connect timeout
|
||||
if (err == "Connection Timeout")
|
||||
setTimeout(Bangle.setHRMPower, 1000, isOn, app);
|
||||
});
|
||||
}
|
||||
} else { // not on
|
||||
log("setHRMPower off", app);
|
||||
if (gatt) {
|
||||
log("HRM connected - disconnecting");
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("HRM disconnect error", e);
|
||||
}
|
||||
gatt = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
// disconnect when swapping apps
|
||||
E.on("kill", function() {
|
||||
if (gatt) {
|
||||
log("HRM connected - disconnecting");
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("HRM disconnect error", e);
|
||||
}
|
||||
gatt = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "bthrmlite",
|
||||
"name": "Bluetooth Heart Rate Monitor (Lite)",
|
||||
"shortName":"BT HRM",
|
||||
"version":"0.01",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. Cut-down version that uses less RAM.",
|
||||
"icon": "app.png",
|
||||
"tags": "health,bluetooth",
|
||||
"type": "bootloader",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthrmlite.boot.js","url":"boot.js"},
|
||||
{"name":"bthrmlite.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -31,3 +31,4 @@ clkinfo.addInteractive that would cause ReferenceError.
|
|||
0.29: use setItem of clockInfoMenu to change the active item
|
||||
0.30: Use widget_utils
|
||||
0.31: Use clock_info module as an app
|
||||
0.32: Make the border of the clock_info box extend all the way to the right of the screen.
|
||||
|
|
|
@ -135,7 +135,7 @@ let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
|||
app: "bwclk",
|
||||
x : 0,
|
||||
y: 135,
|
||||
w: W,
|
||||
w: W+1,
|
||||
h: H-135,
|
||||
draw : (itm, info, options) => {
|
||||
var hideClkInfo = info.text == null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.31",
|
||||
"version": "0.32",
|
||||
"description": "A very minimalistic clock.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -34,3 +34,4 @@ clkinfo.addInteractive that would cause ReferenceError.
|
|||
0.32: Diverge from BW Clock. Change out the custom font for a standard bitmap one to speed up loading times.
|
||||
Remove invertion of theme as this doesn'twork very well with fastloading.
|
||||
Do an quick inital fillRect on theclock info area.
|
||||
0.33: Make the border of the clock_info box extend all the way to the right of the screen.
|
||||
|
|
|
@ -95,7 +95,7 @@ let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
|||
app: "bwclklite",
|
||||
x : 0,
|
||||
y: 135,
|
||||
w: W,
|
||||
w: W+1,
|
||||
h: H-135,
|
||||
draw : (itm, info, options) => {
|
||||
let hideClkInfo = info.text == null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "bwclklite",
|
||||
"name": "BW Clock Lite",
|
||||
"version": "0.32",
|
||||
"version": "0.33",
|
||||
"description": "A very minimalistic clock. This version of BW Clock is quicker at the cost of the custom font.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: First version
|
||||
0.02: Support BangleJS2
|
||||
0.03: Added threshold
|
||||
0.04: Added notification
|
||||
|
|
|
@ -24,16 +24,17 @@
|
|||
lim = sum / cnt;
|
||||
require('Storage').writeJSON('chargent.json', {limit: lim});
|
||||
}
|
||||
require('notify').show({id: 'chargent', title: 'Fully charged'});
|
||||
// TODO ? customizable
|
||||
Bangle.buzz(500);
|
||||
setTimeout(() => Bangle.buzz(500), 1000);
|
||||
}
|
||||
}, 30*1000);
|
||||
}, 3e4);
|
||||
}
|
||||
} else {
|
||||
if (id) {
|
||||
clearInterval(id);
|
||||
id = undefined;
|
||||
id = clearInterval(id);
|
||||
require('notify').hide({id: 'chargent'});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
{ "id": "chargent",
|
||||
"name": "Charge Gently",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
"tags": "battery",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"dependencies": {"notify": "type"},
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name": "chargent.boot.js", "url": "boot.js"}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"dependencies" : { "clock_info":"module" },
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: First version
|
|
@ -0,0 +1,61 @@
|
|||
# GPS Clock Info
|
||||
|
||||

|
||||
|
||||
A clock info that displays the Ordanance Survey (OS) grid reference
|
||||
|
||||
- The primary use is for walking where a GPS fix that is 2 minutes old is
|
||||
perfectly fine for providing an OS map grid reference.
|
||||
- Saves power by only turning the GPS on for the time it takes to get a fix.
|
||||
- It then switches the GPS off for 90 seconds before trying to get the next fix
|
||||
- At the start of the walk update the GPS with using one of the AGPS apps. This will
|
||||
significantly reduce the time to the first fix.
|
||||
- Displays the GPS time and number of satelites while waiting for a fix.
|
||||
- The fix is invalidated after 4 minutes and will display 00:00:00 0
|
||||
or the gps time while the gps is powered on and searching for a fix.
|
||||
- If the display is shows solid 00:00:00 0 then tap the clkinfo to restart it
|
||||
as it will have timed out after 4 minutes
|
||||
- I suggest installing the GPS power widget so that you can be assured
|
||||
when the GPS is draining power.
|
||||
- It is unlikley that this style of gps clock info will work well with the Recorder
|
||||
app as that would hold the GPS power permantly on all the time during the
|
||||
recording.
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||

|
||||
|
||||
- Above: The GPS is powered on and waiting for a fix.
|
||||
- The GPS widget shows yellow indicating powered on
|
||||
- The time from the GPS chip is displayed with the satellite count
|
||||
- The time from the GPS chip is incrementing approximately every second
|
||||
- Note the time on the GPS is in UTC and not the current timezone
|
||||
|
||||
|
||||

|
||||
|
||||
- Above: The GPS has a fix
|
||||
- The OS grid reference has been calculated
|
||||
- The GPS has been turned off for 90 seconds
|
||||
- The GPS widget is grey showing the GPS is off
|
||||
- You will not see the GPS widget turn green as the GPS is turned off after a fix
|
||||
|
||||
|
||||

|
||||
|
||||
- Above: The GPS has been powered on after 90 seconds and is waiting for a fix
|
||||
|
||||
|
||||

|
||||
|
||||
- Above: The GPS has not had a fix for 4 minutes and the cycle has stopped
|
||||
- The time from the GPS is 00:00:00 0, indicating that the GPS not on
|
||||
- The GPS widget is grey showing the GPS is off
|
||||
- Tap the clock_info to restart the GPS clock info
|
||||
|
||||
|
||||
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
After Width: | Height: | Size: 789 B |
|
@ -0,0 +1,127 @@
|
|||
(function () {
|
||||
var timeout;
|
||||
var last_fix;
|
||||
var fixTs;
|
||||
var geo = require("geotools");
|
||||
|
||||
var debug = function(o) {
|
||||
console.log(o);
|
||||
};
|
||||
|
||||
var resetLastFix = function() {
|
||||
last_fix = {
|
||||
fix: 0,
|
||||
alt: 0,
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
speed: 0,
|
||||
time: 0,
|
||||
course: 0,
|
||||
satellites: 0
|
||||
};
|
||||
};
|
||||
|
||||
var formatTime = function(now) {
|
||||
try {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
return fd[4];
|
||||
} catch (e) {
|
||||
return "00:00:00";
|
||||
}
|
||||
};
|
||||
|
||||
var clearTimer = function() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
debug("timer cleared");
|
||||
}
|
||||
};
|
||||
|
||||
var queueGPSon = function() {
|
||||
clearTimer();
|
||||
// power on the GPS again in 90 seconds
|
||||
timeout = setTimeout(function() {
|
||||
timeout = undefined;
|
||||
Bangle.setGPSPower(1,"clkinfo");
|
||||
}, 90000);
|
||||
debug("gps on queued");
|
||||
};
|
||||
|
||||
var onGPS = function(fix) {
|
||||
//console.log(fix);
|
||||
last_fix.time = fix.time;
|
||||
|
||||
// we got a fix
|
||||
if (fix.fix) {
|
||||
last_fix = fix;
|
||||
fixTs = Math.round(getTime());
|
||||
// cancel the timeout, if not already timed out
|
||||
clearTimer();
|
||||
// power off the GPS
|
||||
Bangle.setGPSPower(0,"clkinfo");
|
||||
queueGPSon();
|
||||
}
|
||||
|
||||
// if our last fix was more than 4 minutes ago, reset the fix to show gps time + satelites
|
||||
if (Math.round(getTime()) - fixTs > 240) {
|
||||
resetLastFix();
|
||||
fixTs = Math.round(getTime());
|
||||
// cancel the timeout and power off the gps, tap required to restart
|
||||
clearTimer();
|
||||
Bangle.setGPSPower(0,"clkinfo");
|
||||
}
|
||||
|
||||
info.items[0].emit("redraw");
|
||||
};
|
||||
|
||||
var img = function() {
|
||||
return atob("GBgBAAAAAAAAABgAAb2ABzzgB37gD37wHn54AAAADEwIPn58Pn58Pv58Pn58FmA4AAAAHn54D37wD37gBzzAAb2AABgAAAAAAAAA");
|
||||
};
|
||||
|
||||
var gpsText = function() {
|
||||
if (last_fix === undefined)
|
||||
return '';
|
||||
|
||||
// show gps time and satelite count
|
||||
if (!last_fix.fix)
|
||||
return formatTime(last_fix.time) + ' ' + last_fix.satellites;
|
||||
|
||||
return geo.gpsToOSMapRef(last_fix);
|
||||
};
|
||||
|
||||
var info = {
|
||||
name: "Gps",
|
||||
items: [
|
||||
{
|
||||
name: "gridref",
|
||||
get: function () { return ({
|
||||
img: img(),
|
||||
text: gpsText()
|
||||
}); },
|
||||
run : function() {
|
||||
console.log("run");
|
||||
// if the timer is already runnuing reset it, we can get multiple run calls by tapping
|
||||
clearTimer();
|
||||
Bangle.setGPSPower(1,"clkinfo");
|
||||
},
|
||||
show: function () {
|
||||
console.log("show");
|
||||
resetLastFix();
|
||||
fixTs = Math.round(getTime());
|
||||
Bangle.on("GPS",onGPS);
|
||||
this.run();
|
||||
},
|
||||
hide: function() {
|
||||
console.log("hide");
|
||||
clearTimer();
|
||||
Bangle.setGPSPower(0,"clkinfo");
|
||||
Bangle.removeListener("GPS", onGPS);
|
||||
resetLastFix();
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return info;
|
||||
});
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
*
|
||||
* A module of Geo functions for use with gps fixes
|
||||
*
|
||||
* let geo = require("geotools");
|
||||
* let os = geo.gpsToOSGrid(fix);
|
||||
* let ref = geo.gpsToOSMapRef(fix);
|
||||
*
|
||||
*/
|
||||
|
||||
Number.prototype.toRad = function() { return this*Math.PI/180; };
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */
|
||||
/* - www.movable-type.co.uk/scripts/gridref.js */
|
||||
/* - www.movable-type.co.uk/scripts/latlon-gridref.html */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
function OsGridRef(easting, northing) {
|
||||
this.easting = 0|easting;
|
||||
this.northing = 0|northing;
|
||||
}
|
||||
OsGridRef.latLongToOsGrid = function(point) {
|
||||
var lat = point.lat.toRad();
|
||||
var lon = point.lon.toRad();
|
||||
|
||||
var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
|
||||
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
|
||||
var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W
|
||||
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
|
||||
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
|
||||
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
|
||||
|
||||
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
|
||||
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
|
||||
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
|
||||
var eta2 = nu/rho-1;
|
||||
|
||||
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
|
||||
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
|
||||
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
|
||||
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
|
||||
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
|
||||
|
||||
var cos3lat = cosLat*cosLat*cosLat;
|
||||
var cos5lat = cos3lat*cosLat*cosLat;
|
||||
var tan2lat = Math.tan(lat)*Math.tan(lat);
|
||||
var tan4lat = tan2lat*tan2lat;
|
||||
|
||||
var I = M + N0;
|
||||
var II = (nu/2)*sinLat*cosLat;
|
||||
var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
|
||||
var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
|
||||
var IV = nu*cosLat;
|
||||
var V = (nu/6)*cos3lat*(nu/rho-tan2lat);
|
||||
var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
|
||||
|
||||
var dLon = lon-lon0;
|
||||
var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon;
|
||||
|
||||
var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6;
|
||||
var E = E0 + IV*dLon + V*dLon3 + VI*dLon5;
|
||||
|
||||
return new OsGridRef(E, N);
|
||||
};
|
||||
|
||||
/*
|
||||
* converts northing, easting to standard OS grid reference.
|
||||
*
|
||||
* [digits=10] - precision (10 digits = metres)
|
||||
* to_map_ref(8, 651409, 313177); => 'TG 5140 1317'
|
||||
* to_map_ref(0, 651409, 313177); => '651409,313177'
|
||||
*
|
||||
*/
|
||||
function to_map_ref(digits, easting, northing) {
|
||||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
|
||||
|
||||
let e = easting;
|
||||
let n = northing;
|
||||
|
||||
// use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7
|
||||
if (digits == 0) {
|
||||
const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 };
|
||||
const ePad = e.toLocaleString('en', format);
|
||||
const nPad = n.toLocaleString('en', format);
|
||||
return `${ePad},${nPad}`;
|
||||
}
|
||||
|
||||
// get the 100km-grid indices
|
||||
const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000);
|
||||
|
||||
// translate those into numeric equivalents of the grid letters
|
||||
let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5);
|
||||
let l2 = (19 - n100km) * 5 % 25 + e100km % 5;
|
||||
|
||||
// compensate for skipped 'I' and build grid letter-pairs
|
||||
if (l1 > 7) l1++;
|
||||
if (l2 > 7) l2++;
|
||||
const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0));
|
||||
|
||||
// strip 100km-grid indices from easting & northing, and reduce precision
|
||||
e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2));
|
||||
n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2));
|
||||
|
||||
// pad eastings & northings with leading zeros
|
||||
e = e.toString().padStart(digits/2, '0');
|
||||
n = n.toString().padStart(digits/2, '0');
|
||||
|
||||
return `${letterPair} ${e} ${n}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Module exports section, example code below
|
||||
*
|
||||
* let geo = require("geotools");
|
||||
* let os = geo.gpsToOSGrid(fix);
|
||||
* let ref = geo.gpsToOSMapRef(fix);
|
||||
*/
|
||||
|
||||
// get easting and northings
|
||||
exports.gpsToOSGrid = function(gps_fix) {
|
||||
return OsGridRef.latLongToOsGrid(gps_fix);
|
||||
}
|
||||
|
||||
// string with an OS Map grid reference
|
||||
exports.gpsToOSMapRef = function(gps_fix) {
|
||||
let os = OsGridRef.latLongToOsGrid(gps_fix);
|
||||
return to_map_ref(6, os.easting, os.northing);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "clkinfogps",
|
||||
"name": "GPS Grid Ref Clockinfo",
|
||||
"version":"0.01",
|
||||
"description": "Clockinfo that displays the GPS OS Grid Reference",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot1.png"}],
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo,gps",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme":"README.md",
|
||||
"storage": [
|
||||
{"name":"geotools","url":"geotools.js"},
|
||||
{"name":"gps.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
|
@ -1 +1,3 @@
|
|||
0.01: New clkinfo!
|
||||
0.02: Added format option, reduced battery usage
|
||||
0.03: Hardcode colon-format, show milliseconds for the first minute
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"use strict";
|
||||
(function () {
|
||||
var durationOnPause = "---";
|
||||
var redrawInterval;
|
||||
var startTime;
|
||||
var showMillis = true;
|
||||
var milliTime = 60;
|
||||
var unqueueRedraw = function () {
|
||||
if (redrawInterval)
|
||||
clearInterval(redrawInterval);
|
||||
|
@ -11,20 +12,31 @@
|
|||
var queueRedraw = function () {
|
||||
var _this = this;
|
||||
unqueueRedraw();
|
||||
redrawInterval = setInterval(function () { return _this.emit('redraw'); }, 100);
|
||||
redrawInterval = setInterval(function () {
|
||||
if (startTime) {
|
||||
if (showMillis && Date.now() - startTime > milliTime * 1000) {
|
||||
showMillis = false;
|
||||
changeInterval(redrawInterval, 1000);
|
||||
}
|
||||
}
|
||||
else {
|
||||
unqueueRedraw();
|
||||
}
|
||||
_this.emit('redraw');
|
||||
}, 100);
|
||||
};
|
||||
var pad2 = function (s) { return ('0' + s.toFixed(0)).slice(-2); };
|
||||
var duration = function (start) {
|
||||
var seconds = (Date.now() - start) / 1000;
|
||||
if (seconds < 60)
|
||||
if (seconds < milliTime)
|
||||
return seconds.toFixed(1);
|
||||
var mins = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (mins < 60)
|
||||
return "".concat(pad2(mins), "m").concat(pad2(seconds), "s");
|
||||
return "".concat(mins.toFixed(0), ":").concat(pad2(seconds));
|
||||
var hours = mins / 60;
|
||||
mins %= 60;
|
||||
return "".concat(Math.round(hours), "h").concat(pad2(mins), "m").concat(pad2(seconds), "s");
|
||||
return "".concat(hours.toFixed(0), ":").concat(pad2(mins), ":").concat(pad2(seconds));
|
||||
};
|
||||
var img = function () { return atob("GBiBAAAAAAB+AAB+AAAAAAB+AAH/sAOB8AcA4A4YcAwYMBgYGBgYGBg8GBg8GBgYGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="); };
|
||||
return {
|
||||
|
@ -39,16 +51,23 @@
|
|||
: durationOnPause,
|
||||
img: img(),
|
||||
}); },
|
||||
show: queueRedraw,
|
||||
show: function () {
|
||||
if (startTime) {
|
||||
queueRedraw.call(this);
|
||||
}
|
||||
else {
|
||||
this.emit('redraw');
|
||||
}
|
||||
},
|
||||
hide: unqueueRedraw,
|
||||
run: function () {
|
||||
if (startTime) {
|
||||
durationOnPause = duration(startTime);
|
||||
startTime = undefined;
|
||||
unqueueRedraw();
|
||||
}
|
||||
else {
|
||||
queueRedraw.call(this);
|
||||
showMillis = true;
|
||||
startTime = Date.now();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
((): ClockInfo.Menu => {
|
||||
(() => {
|
||||
let durationOnPause = "---";
|
||||
let redrawInterval: number | undefined;
|
||||
let startTime: number | undefined;
|
||||
let showMillis = true;
|
||||
const milliTime = 60;
|
||||
|
||||
const unqueueRedraw = () => {
|
||||
if (redrawInterval) clearInterval(redrawInterval);
|
||||
|
@ -10,7 +12,17 @@
|
|||
|
||||
const queueRedraw = function(this: ClockInfo.MenuItem) {
|
||||
unqueueRedraw();
|
||||
redrawInterval = setInterval(() => this.emit('redraw'), 100);
|
||||
redrawInterval = setInterval(() => {
|
||||
if (startTime) {
|
||||
if (showMillis && Date.now() - startTime > milliTime * 1000) {
|
||||
showMillis = false;
|
||||
changeInterval(redrawInterval, 1000);
|
||||
}
|
||||
} else {
|
||||
unqueueRedraw();
|
||||
}
|
||||
this.emit('redraw')
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const pad2 = (s: number) => ('0' + s.toFixed(0)).slice(-2);
|
||||
|
@ -18,19 +30,19 @@
|
|||
const duration = (start: number) => {
|
||||
let seconds = (Date.now() - start) / 1000;
|
||||
|
||||
if (seconds < 60)
|
||||
if (seconds < milliTime)
|
||||
return seconds.toFixed(1);
|
||||
|
||||
let mins = seconds / 60;
|
||||
seconds %= 60;
|
||||
|
||||
if (mins < 60)
|
||||
return `${pad2(mins)}m${pad2(seconds)}s`;
|
||||
return `${mins.toFixed(0)}:${pad2(seconds)}`;
|
||||
|
||||
let hours = mins / 60;
|
||||
mins %= 60;
|
||||
|
||||
return `${Math.round(hours)}h${pad2(mins)}m${pad2(seconds)}s`;
|
||||
return `${hours.toFixed(0)}:${pad2(mins)}:${pad2(seconds)}`;
|
||||
};
|
||||
|
||||
const img = () => atob("GBiBAAAAAAB+AAB+AAAAAAB+AAH/sAOB8AcA4A4YcAwYMBgYGBgYGBg8GBg8GBgYGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA==");
|
||||
|
@ -47,19 +59,25 @@
|
|||
: durationOnPause,
|
||||
img: img(),
|
||||
}),
|
||||
show: queueRedraw,
|
||||
show: function(this: ClockInfo.MenuItem) {
|
||||
if(startTime){ // only queue if active
|
||||
queueRedraw.call(this);
|
||||
}else{
|
||||
this.emit('redraw')
|
||||
}
|
||||
},
|
||||
hide: unqueueRedraw,
|
||||
run: function() { // tapped
|
||||
if (startTime) {
|
||||
durationOnPause = duration(startTime);
|
||||
startTime = undefined;
|
||||
unqueueRedraw();
|
||||
startTime = undefined; // this also unqueues the redraw
|
||||
} else {
|
||||
queueRedraw.call(this);
|
||||
showMillis = true;
|
||||
startTime = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
||||
}) satisfies ClockInfoFunc
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "clkinfostopw",
|
||||
"name": "Stop Watch Clockinfo",
|
||||
"version":"0.01",
|
||||
"version":"0.03",
|
||||
"description": "A simple stopwatch, shown via clockinfo",
|
||||
"icon": "app.png",
|
||||
"type": "clkinfo",
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
const enum StopWatchFormat {
|
||||
HMS,
|
||||
Colon,
|
||||
}
|
||||
type StopWatchSettings = {
|
||||
format: StopWatchFormat,
|
||||
};
|
||||
|
||||
(back => {
|
||||
const SETTINGS_FILE = "clkinfostopw.setting.json";
|
||||
|
||||
const storage = require("Storage");
|
||||
const settings: StopWatchSettings = storage.readJSON(SETTINGS_FILE, true) || {};
|
||||
settings.format ??= StopWatchFormat.HMS;
|
||||
|
||||
const save = () => {
|
||||
storage.writeJSON(SETTINGS_FILE, settings)
|
||||
};
|
||||
|
||||
E.showMenu({
|
||||
"": { "title": "stopwatch" },
|
||||
"< Back": back,
|
||||
"Format": {
|
||||
value: settings.format,
|
||||
min: StopWatchFormat.HMS,
|
||||
max: StopWatchFormat.Colon,
|
||||
format: v => v === StopWatchFormat.HMS ? "12m34s" : "12:34",
|
||||
onchange: v => {
|
||||
settings.format = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
});
|
||||
}) satisfies SettingsFunc
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Use ClockFace library, add settings
|
||||
0.03: Use ClockFace_menu.addSettingsFile
|
||||
0.04: Hide widgets instead of not loading them at all
|
||||
0.05: Support Bangle.js 2
|
||||
|
|
|
@ -44,7 +44,7 @@ const clock = new ClockFace({
|
|||
precision: 1,
|
||||
settingsFile: "cogclock.settings.json",
|
||||
init: function() {
|
||||
this.r1 = 84; // inner radius
|
||||
this.r1 = (process.env.HWVERSION>1) ? 68 : 84; // inner radius
|
||||
this.r3 = Math.min(Bangle.appRect.w/2, Bangle.appRect.h/2); // outer radius
|
||||
this.r2 = (this.r1*3+this.r3*2)/5;
|
||||
this.teeth = 12;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"id": "cogclock",
|
||||
"name": "Cog Clock",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "A cross-shaped clock inside a cog",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"screenshots": [{"url":"screenshot_b1.png"},{"url":"screenshot_b2.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"cogclock.app.js","url":"app.js"},
|
||||
|
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 4.4 KiB |
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("j0ewkBiIAxHIQMJiBJEIxAaCAIQfHDgIUFDwwNCHYgVFiAVBHYgIDEghKCCIQGCFYoaDAYgORGIJ2DBwYIBHgQOPgAOIPIYOGAgQOFFgh7DHZQeDBwhoFQgh3JEAgOFFoqkHYRzgOfx4bCJ4gNGSIaJEABA7EAGA"))
|
||||
require("heatshrink").decompress(atob("mEwwMB/4AFgYCB4H//kAAoMAn/w+IFBx8P8fjAoPH4/n4/gg/j8/Px4rB+Pz58ch/wnHzz0wv/+hl5zlhDoOGnOY44FB8cZyOP/1/+OJwcfAoP44OGn4FB/lh5giBAIMz7n/AoP/nf4Aocf/IFDz5YBAoWP+YFD54FFMgIFD84FD84FM/0AApKfDApiaCAAJBCApKyCWgRlBAAWfOIIACj/8Aoc//g/BJ4KTBn4FBBIUfAoIbCx4CBFoUHAQPgDIMhAoOEV4NwVgMOn/4/jdBn8fDILpBUIfwh5TBIAYABA="))
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New app!
|
|
@ -0,0 +1,33 @@
|
|||
# Drained
|
||||
|
||||
With this app installed, your Bangle will automatically switch into low power mode when the battery reaches 5% battery (or a preconfigured percentage), displaying a simple clock. When the battery is then charged above 20% (also configurable), normal operation is restored.
|
||||
|
||||
Low power mode can also be exited manually, by tapping the primary watch button (an initial tap may be required to unlock the watch).
|
||||
|
||||
# Features
|
||||
|
||||
## Persistence
|
||||
- [x] Restore normal operation with sufficient charge
|
||||
- [x] Reactivate on watch startup
|
||||
|
||||
## Time
|
||||
- [x] Show simple date/time
|
||||
- [ ] Disable alarms - with a setting?
|
||||
- [ ] Smarter/backoff interval for checking battery percentage
|
||||
|
||||
## No backlight (#2502)
|
||||
- [x] LCD brightness
|
||||
- [ ] LCD timeout?
|
||||
|
||||
## Peripherals
|
||||
- [x] Disable auto heart rate measurement in health app (#2502)
|
||||
- [x] Overwrite setGPSPower() function (#2502)
|
||||
- [x] Turn off already-running GPS / HRM
|
||||
|
||||
## Features
|
||||
- [x] Wake on twist -> off (#2502)
|
||||
- [x] Emit `"drained"` event
|
||||
|
||||
# Creator
|
||||
|
||||
- [bobrippling](https://github.com/bobrippling/)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4AwqwAHF84HOF/6OQA5pe/F/4v2X1AvHL1crF9srwMrMwWJAAIvlFwIABAwQvCGEJXCFwYvBSQIvDGEAuHqySBF4gwfK4IuDF4IDClowjFwovEMMYuHF4gwhFw16q16BAoweFwwvJGAwueGAQJIGAgufABYvYFypfYFy3+F6wuXF6wuYF6ouZL9QuEX9IuFF64wQFwwvYGBwuHF7IwMFxAvaGBQuJF7YwIFxQvcGAwuLF7owEFxgveGAQuNF74wBB5wvfAB4v/FDgANM1YA/AH4A/AHI"))
|
|
@ -0,0 +1,102 @@
|
|||
var app = "drained";
|
||||
if (typeof drainedInterval !== "undefined")
|
||||
drainedInterval = clearInterval(drainedInterval);
|
||||
Bangle.setLCDBrightness(0);
|
||||
var powerNoop = function () { return false; };
|
||||
var forceOff = function (name) {
|
||||
var _a;
|
||||
if ((_a = Bangle._PWR) === null || _a === void 0 ? void 0 : _a[name])
|
||||
Bangle._PWR[name] = [];
|
||||
Bangle["set".concat(name, "Power")](0, app);
|
||||
Bangle["set".concat(name, "Power")] = powerNoop;
|
||||
};
|
||||
forceOff("GPS");
|
||||
forceOff("HRM");
|
||||
try {
|
||||
NRF.disconnect();
|
||||
NRF.sleep();
|
||||
}
|
||||
catch (e) {
|
||||
console.log("couldn't disable ble: ".concat(e));
|
||||
}
|
||||
Bangle.removeAllListeners();
|
||||
clearWatch();
|
||||
Bangle.setOptions({
|
||||
wakeOnFaceUp: 0,
|
||||
wakeOnTouch: 0,
|
||||
wakeOnTwist: 0,
|
||||
});
|
||||
var nextDraw;
|
||||
var draw = function () {
|
||||
var x = g.getWidth() / 2;
|
||||
var y = g.getHeight() / 2 - 48;
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date, 1);
|
||||
var dateStr = require("locale").date(date, 0).toUpperCase() +
|
||||
"\n" +
|
||||
require("locale").dow(date, 0).toUpperCase();
|
||||
g.reset()
|
||||
.clearRect(Bangle.appRect)
|
||||
.setFont("Vector", 55)
|
||||
.setFontAlign(0, 0)
|
||||
.drawString(timeStr, x, y)
|
||||
.setFont("Vector", 24)
|
||||
.drawString(dateStr, x, y + 56)
|
||||
.drawString("".concat(E.getBattery(), "%"), x, y + 104);
|
||||
if (nextDraw)
|
||||
clearTimeout(nextDraw);
|
||||
nextDraw = setTimeout(function () {
|
||||
nextDraw = undefined;
|
||||
draw();
|
||||
}, 60000 - (date.getTime() % 60000));
|
||||
};
|
||||
var reload = function () {
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
remove: function () {
|
||||
if (nextDraw)
|
||||
clearTimeout(nextDraw);
|
||||
nextDraw = undefined;
|
||||
},
|
||||
btn: function () {
|
||||
E.showPrompt("Restore watch to full power?").then(function (v) {
|
||||
if (v) {
|
||||
drainedRestore();
|
||||
}
|
||||
else {
|
||||
reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Bangle.CLOCK = 1;
|
||||
g.clear();
|
||||
draw();
|
||||
};
|
||||
reload();
|
||||
Bangle.emit("drained", E.getBattery());
|
||||
var _a = require("Storage").readJSON("".concat(app, ".setting.json"), true) || {}, _b = _a.disableBoot, disableBoot = _b === void 0 ? false : _b, _c = _a.restore, restore = _c === void 0 ? 20 : _c;
|
||||
function drainedRestore() {
|
||||
if (disableBoot) {
|
||||
try {
|
||||
eval(require('Storage').read('bootupdate.js'));
|
||||
}
|
||||
catch (e) {
|
||||
console.log("error restoring bootupdate:" + e);
|
||||
}
|
||||
}
|
||||
load();
|
||||
}
|
||||
if (disableBoot) {
|
||||
var checkCharge_1 = function () {
|
||||
if (E.getBattery() < restore)
|
||||
return;
|
||||
drainedRestore();
|
||||
};
|
||||
if (Bangle.isCharging())
|
||||
checkCharge_1();
|
||||
Bangle.on("charging", function (charging) {
|
||||
if (charging)
|
||||
checkCharge_1();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
const app = "drained";
|
||||
|
||||
// from boot.js
|
||||
declare var drainedInterval: number | undefined;
|
||||
if(typeof drainedInterval !== "undefined")
|
||||
drainedInterval = clearInterval(drainedInterval) as undefined;
|
||||
|
||||
// backlight
|
||||
Bangle.setLCDBrightness(0);
|
||||
|
||||
// peripherals
|
||||
const powerNoop = () => false;
|
||||
|
||||
const forceOff = (name: "GPS" | "HRM" | "Compass" /*| "Barom"*/) => {
|
||||
if ((Bangle as any)._PWR?.[name])
|
||||
(Bangle as any)._PWR[name] = [];
|
||||
|
||||
// if(name === "Barom"){ setBarometerPower(...) }
|
||||
// ^^^^
|
||||
Bangle[`set${name}Power`](0, app);
|
||||
Bangle[`set${name}Power`] = powerNoop;
|
||||
};
|
||||
forceOff("GPS");
|
||||
forceOff("HRM");
|
||||
try{
|
||||
NRF.disconnect();
|
||||
NRF.sleep();
|
||||
}catch(e){
|
||||
console.log(`couldn't disable ble: ${e}`);
|
||||
}
|
||||
|
||||
// events
|
||||
Bangle.removeAllListeners();
|
||||
clearWatch();
|
||||
|
||||
// UI
|
||||
Bangle.setOptions({
|
||||
wakeOnFaceUp: 0,
|
||||
wakeOnTouch: 0,
|
||||
wakeOnTwist: 0,
|
||||
});
|
||||
|
||||
// clock
|
||||
let nextDraw: number | undefined;
|
||||
const draw = () => {
|
||||
const x = g.getWidth() / 2;
|
||||
const y = g.getHeight() / 2 - 48;
|
||||
|
||||
const date = new Date();
|
||||
|
||||
const timeStr = require("locale").time(date, 1);
|
||||
const dateStr = require("locale").date(date, 0).toUpperCase() +
|
||||
"\n" +
|
||||
require("locale").dow(date, 0).toUpperCase();
|
||||
|
||||
g.reset()
|
||||
.clearRect(Bangle.appRect)
|
||||
.setFont("Vector", 55)
|
||||
.setFontAlign(0, 0)
|
||||
.drawString(timeStr, x, y)
|
||||
.setFont("Vector", 24)
|
||||
.drawString(dateStr, x, y + 56)
|
||||
.drawString(`${E.getBattery()}%`, x, y + 104);
|
||||
|
||||
if(nextDraw) clearTimeout(nextDraw);
|
||||
nextDraw = setTimeout(() => {
|
||||
nextDraw = undefined;
|
||||
draw();
|
||||
}, 60000 - (date.getTime() % 60000));
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
remove: () => {
|
||||
if (nextDraw) clearTimeout(nextDraw);
|
||||
nextDraw = undefined;
|
||||
},
|
||||
btn: () => {
|
||||
E.showPrompt("Restore watch to full power?").then(v => {
|
||||
if(v){
|
||||
drainedRestore();
|
||||
}else{
|
||||
reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
Bangle.CLOCK=1;
|
||||
|
||||
g.clear();
|
||||
draw();
|
||||
};
|
||||
reload();
|
||||
|
||||
// permit other apps to put themselves into low-power mode
|
||||
Bangle.emit("drained", E.getBattery());
|
||||
|
||||
// restore normal boot on charge
|
||||
const { disableBoot = false, restore = 20 }: DrainedSettings
|
||||
= require("Storage").readJSON(`${app}.setting.json`, true) || {};
|
||||
|
||||
// re-enable normal boot code when we're above a threshold:
|
||||
function drainedRestore() { // "public", to allow users to call
|
||||
if(disableBoot){
|
||||
try{
|
||||
eval(require('Storage').read('bootupdate.js'));
|
||||
}catch(e){
|
||||
console.log("error restoring bootupdate:" + e);
|
||||
}
|
||||
}
|
||||
load(); // necessary after updating boot.0
|
||||
}
|
||||
|
||||
if(disableBoot){
|
||||
const checkCharge = () => {
|
||||
if(E.getBattery() < restore) return;
|
||||
drainedRestore();
|
||||
};
|
||||
|
||||
if (Bangle.isCharging())
|
||||
checkCharge();
|
||||
|
||||
Bangle.on("charging", charging => {
|
||||
if(charging) checkCharge();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
var _a = require("Storage").readJSON("drained.setting.json", true) || {}, _b = _a.battery, threshold_1 = _b === void 0 ? 5 : _b, _c = _a.interval, interval = _c === void 0 ? 10 : _c, _d = _a.disableBoot, disableBoot_1 = _d === void 0 ? false : _d;
|
||||
drainedInterval = setInterval(function () {
|
||||
if (Bangle.isCharging())
|
||||
return;
|
||||
if (E.getBattery() > threshold_1)
|
||||
return;
|
||||
var app = "drained.app.js";
|
||||
if (disableBoot_1)
|
||||
require("Storage").write(".boot0", "if(typeof __FILE__ === \"undefined\" || __FILE__ !== \"".concat(app, "\") setTimeout(load, 100, \"").concat(app, "\");"));
|
||||
load(app);
|
||||
}, interval * 60 * 1000);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
const { battery: threshold = 5, interval = 10, disableBoot = false }: DrainedSettings
|
||||
= require("Storage").readJSON(`drained.setting.json`, true) || {};
|
||||
|
||||
drainedInterval = setInterval(() => {
|
||||
if(Bangle.isCharging())
|
||||
return;
|
||||
if(E.getBattery() > threshold)
|
||||
return;
|
||||
|
||||
const app = "drained.app.js";
|
||||
|
||||
if(disableBoot)
|
||||
require("Storage").write(
|
||||
".boot0",
|
||||
`if(typeof __FILE__ === "undefined" || __FILE__ !== "${app}") setTimeout(load, 100, "${app}");`
|
||||
);
|
||||
|
||||
load(app);
|
||||
}, interval * 60 * 1000);
|
||||
}
|
After Width: | Height: | Size: 469 B |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "drained",
|
||||
"name": "Drained",
|
||||
"version": "0.01",
|
||||
"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": [
|
||||
{"name":"drained.boot.js","url":"boot.js"},
|
||||
{"name":"drained.app.js","url":"app.js"},
|
||||
{"name":"drained.settings.js","url":"settings.js"},
|
||||
{"name":"drained.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
(function (back) {
|
||||
var _a, _b, _c, _d;
|
||||
var SETTINGS_FILE = "drained.setting.json";
|
||||
var storage = require("Storage");
|
||||
var settings = storage.readJSON(SETTINGS_FILE, true) || {};
|
||||
(_a = settings.battery) !== null && _a !== void 0 ? _a : (settings.battery = 5);
|
||||
(_b = settings.restore) !== null && _b !== void 0 ? _b : (settings.restore = 20);
|
||||
(_c = settings.interval) !== null && _c !== void 0 ? _c : (settings.interval = 10);
|
||||
(_d = settings.disableBoot) !== null && _d !== void 0 ? _d : (settings.disableBoot = false);
|
||||
var save = function () {
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
};
|
||||
E.showMenu({
|
||||
"": { "title": "Drained" },
|
||||
"< Back": back,
|
||||
"Keep startup code": {
|
||||
value: settings.disableBoot,
|
||||
format: function () { return settings.disableBoot ? "No" : "Yes"; },
|
||||
onchange: function () {
|
||||
settings.disableBoot = !settings.disableBoot;
|
||||
save();
|
||||
},
|
||||
},
|
||||
"Trigger at batt%": {
|
||||
value: settings.battery,
|
||||
min: 0,
|
||||
max: 95,
|
||||
step: 5,
|
||||
format: function (v) { return "".concat(v, "%"); },
|
||||
onchange: function (v) {
|
||||
settings.battery = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
"Poll interval": {
|
||||
value: settings.interval,
|
||||
min: 1,
|
||||
max: 60 * 2,
|
||||
step: 5,
|
||||
format: function (v) { return "".concat(v, " mins"); },
|
||||
onchange: function (v) {
|
||||
settings.interval = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
"Restore watch at %": {
|
||||
value: settings.restore,
|
||||
min: 0,
|
||||
max: 95,
|
||||
step: 5,
|
||||
format: function (v) { return "".concat(v, "%"); },
|
||||
onchange: function (v) {
|
||||
settings.restore = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
type DrainedSettings = {
|
||||
battery?: number,
|
||||
restore?: number,
|
||||
interval?: number,
|
||||
disableBoot?: ShortBoolean,
|
||||
};
|
||||
|
||||
(back => {
|
||||
const SETTINGS_FILE = "drained.setting.json";
|
||||
|
||||
const storage = require("Storage")
|
||||
const settings: DrainedSettings = storage.readJSON(SETTINGS_FILE, true) || {};
|
||||
settings.battery ??= 5;
|
||||
settings.restore ??= 20;
|
||||
settings.interval ??= 10;
|
||||
settings.disableBoot ??= false;
|
||||
|
||||
const save = () => {
|
||||
storage.writeJSON(SETTINGS_FILE, settings)
|
||||
};
|
||||
|
||||
E.showMenu({
|
||||
"": { "title": "Drained" },
|
||||
"< Back": back,
|
||||
"Keep startup code": {
|
||||
value: settings.disableBoot,
|
||||
format: () => settings.disableBoot ? "No" : "Yes",
|
||||
onchange: () => {
|
||||
settings.disableBoot = !settings.disableBoot;
|
||||
save();
|
||||
},
|
||||
},
|
||||
"Trigger at batt%": {
|
||||
value: settings.battery,
|
||||
min: 0,
|
||||
max: 95,
|
||||
step: 5,
|
||||
format: (v: number) => `${v}%`,
|
||||
onchange: (v: number) => {
|
||||
settings.battery = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
"Poll interval": {
|
||||
value: settings.interval,
|
||||
min: 1,
|
||||
max: 60 * 2,
|
||||
step: 5,
|
||||
format: (v: number) => `${v} mins`,
|
||||
onchange: (v: number) => {
|
||||
settings.interval = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
"Restore watch at %": {
|
||||
value: settings.restore,
|
||||
min: 0,
|
||||
max: 95,
|
||||
step: 5,
|
||||
format: (v: number) => `${v}%`,
|
||||
onchange: (v: number) => {
|
||||
settings.restore = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
});
|
||||
}) satisfies SettingsFunc
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New face :)
|
||||
0.02: code improvements
|
||||
0.03: code improvments to queuedraw and draw
|
|
@ -0,0 +1,18 @@
|
|||
# Encouragement & Positivity Clock
|
||||
|
||||
Tap on the watch for a note of encouragement
|
||||
|
||||
## Features
|
||||
|
||||
Pretty backgrounds
|
||||
|
||||
<img width="181" alt="Screenshot 2023-03-28-2" src="https://user-images.githubusercontent.com/44651387/228306964-be0b1d46-aee7-4562-9953-4e461954d41b.png">
|
||||
<img width="181" alt="Screenshot 2023-03-28-1" src="https://user-images.githubusercontent.com/44651387/228306967-768e0315-8652-4043-9c98-ebf0c72fd2a4.png">
|
||||
|
||||
## Requests
|
||||
|
||||
If you have any issues or would like to suggest an encouraging note, please tweet me!
|
||||
|
||||
## Creator
|
||||
|
||||
[Eleanor Tayam](http://twitter.com/elykittytee)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwMB/4AFgYCB4H//kAAoMAn/w+IFBx8P8fjAoPH4/n4/gg/j8/Px4rB+Pz58ch/wnHzz0wv/+hl5zlhDoOGnOY44FB8cZyOP/1/+OJwcfAoP44OGn4FB/lh5giBAIMz7n/AoP/nf4Aocf/IFDz5YBAoWP+YFD54FFMgIFD84FD84FM/0AApKfDApiaCAAJBCApKyCWgRlBAAWfOIIACj/8Aoc//g/BJ4KTBn4FBBIUfAoIbCx4CBFoUHAQPgDIMhAoOEV4NwVgMOn/4/jdBn8fDILpBUIfwh5TBIAYABA="))
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "encourageclk",
|
||||
"name": "Encouragement & Positivity Clock",
|
||||
"shortName":"Encouragement Clock",
|
||||
"version": "0.03",
|
||||
"description": "Tap on the watch for a note of encouragement",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme":"README.md",
|
||||
"storage": [
|
||||
{"name":"encourageclk.app.js","url":"app.js"},
|
||||
{"name":"encourageclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 4.7 KiB |
|
@ -15,7 +15,7 @@
|
|||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span></p>
|
||||
</ul>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p>If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
|
||||
<p id="fw-old-bootloader-msg">If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
|
||||
will fail with a message about the DFU version. If so, please <a href="bootloader_espruino_2v12_banglejs2.hex" class="fw-link">click here to update to DFU 2v12</a> and then click the 'Upload' button that appears.</p>
|
||||
<div id="latest-firmware" style="display:none">
|
||||
<p>The currently available Espruino firmware releases are:</p>
|
||||
|
@ -104,6 +104,9 @@ function onInit(device) {
|
|||
version += `(⚠ update required)`;
|
||||
}
|
||||
document.getElementById("boot-version").innerHTML = version;
|
||||
var versionNumber = parseFloat(version.replace(".","").replace("v","."));
|
||||
if (versionNumber>=2.15)
|
||||
document.getElementById("fw-old-bootloader-msg").style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -156,7 +159,8 @@ function checkForFileOnServer() {
|
|||
for (var i=0;i<fwlinks.length;i++)
|
||||
fwlinks[i].addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
downloadURL(e.target.href).then(info=>{
|
||||
var href = e.target.href;
|
||||
if (href) downloadURL(href).then(info=>{
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
0.20: New App!
|
||||
0.21: Tell clock widgets to hide.
|
||||
|
||||
0.22: Changed font so 5 and 6 are not similar
|
||||
|
|
|
@ -1,13 +1,36 @@
|
|||
Graphics.prototype.setFontLECO1976Regular42 = function (scale) {
|
||||
|
||||
Graphics.prototype.setFontLECO1976Regular5fix42 = function(scale) {
|
||||
// Actual height 42 (41 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAA/AAAAAAAAH/AAAAAAAA//AAAAAAAP//AAAAAAB///AAAAAAP///AAAAAB////AAAAAf////AAAAD////4AAAAf////AAAAH////4AAAA////+AAAAA////wAAAAA///+AAAAAA///gAAAAAA//8AAAAAAA//gAAAAAAA/4AAAAAAAA/AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////gD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4B/gH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAH+AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ERkmHyYmJiYmJCYmEQ=="), 60 + (scale << 8) + (1 << 16));
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('ADMD/gHFv/AAwkHB3QAtngGFj47Fh5KFh//BwkH/4OEgf/BwgGCBwcBAwIOEAwQODAwX/wB7CCos/Awo/BAAPgDgvwJwgGEBwX4LoplFAw0P/yCF/4GFh6YGKgQAhNAZGDAwZ4CB3ibCg4ODZoYO/BwyV/BxIA7YX7C/YRRZCAAZZDB2AAgNAMHO4v4O42PB3P4AIL+EwABBQwQO/BwgABBwgGBB34A0h/wAYMDSogDBSogGBUgoOOd/4O2AAbgEAAIO+AGY7C/AHDIIWAB3wQCBwjiDB34OGf1gOdAGbgDgZKFwF/JQn4g4O3/ABBBwmAB34OLcAgOBd4oO6AGY5CJQoADd4gO5f2wOdf1IOdAEgqBA4v//AOGwAO5AwqGCB34OJAAbRCAwbgDB3QAzO/4OL/ABBg4ODwABBv4O/BwyV/BxIAzHYX4gZKFSogOCSowOxf2gOdf1YOdAGkH/EAgY7DSgMASoSWCCIIO3ADg='))),
|
||||
46,
|
||||
atob("ERkmICYmJiYmJCYmEQ=="),
|
||||
60+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontLECO1976Regular22 = function (scale) {
|
||||
Graphics.prototype.setFontLECO1976Regular5fix22 = function(scale) {
|
||||
// Actual height 22 (21 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nA/+cD/5wP/nAAAAAAAAPwAA/gAD+AAPwAAAAAD+AAP4AA/gAAAAAAAAAAAAAcOAP//A//8D//wP//AHDgAcOAP//A//8D//wP//AHDgAAAAAAAAH/jgf+OB/44H/jj8OP/w4//Dj/8OPxw/4HD/gcP+Bw/4AAAAAAAP+AA/8AD/wQOHHA4c8D//wP/8A//gAD4AAfAAH/8A//wP//A84cDjhwIP/AA/8AB/wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8ABwAAAAAAAAD8AAP4AA/gAD8AAAAAAAAAAAEAAD+AB//A///v/D//gB/wABwAAAAAADgAA/wAf/4P8///wf/4AP8AAOAAAAAAAAAyAAHcAAPwAD/gAP/AA/8AA/AAH8AAMwAAAAAAAAAAAAADgAAOAAA4AAf8AD/wAP/AA/8AAOAAA4AADgAAAAAAAAAAD8AAfwAB/AAD8AAAAAAAADgAAOAAA4AADgAAOAAA4AADgAAAAAAAAAADgAAOAAA4AADgAAAAAAAAABwAB/AA/8A//gP/gA/wADwAAIAAAAAAD//wP//A//8D//wOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA4AcDgBwOAHA//8D//wP//A//8AABwAAHAAAcAAAAAAAA+f8D5/wPn/A+f8DhxwOHHA4ccDhxwP/HA/8cD/xwP/HAAAAAAAAOAHA4AcDhxwOHHA4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/wAP/AA/8AD/wAAHAAAcAABwAAHAA//8D//wP//A//8AAAAAAAA/98D/3wP/fA/98DhxwOHHA4ccDhxwOH/A4f8Dh/wOH/AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccDh/wOH/A4f8Dh/wAAAAAAAD4AAPgAA+AADgAAOAAA4AADgAAP//A//8D//wP//AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA//8D//wP//A//8AAAAAAAAOA4A4DgDgOAOA4AAAAAAAAOA/A4H8DgfwOA/AAAAAAAAB4AAPwAA/AAD8AAf4ABzgAPPAA8cAHh4AAAAAAAAAAAAHHAAccABxwAHHAAccABxwAHHAAccABxwAHHAAAAAAAAAOHAA4cADzwAPPAAf4AB/gAD8AAPwAAeAAB4AAAAAAAAA+AAD4AAPgAA+ecDh9wOH3A4fcDhwAP/AA/8AD/wAP/AAAAAAAAAP//4///j//+P//44ADjn/OOf845/zjnHOP8c4//zj//OP/84AAAAAAAP//A//8D//wP//A4cADhwAOHAA4cAD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA//8D//wP9/A/j8AAAAAAAA//8D//wP//A//8DgBwOAHA4AcDgBwOAHA4AcDgBwOAHAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA8A8D//wH/+AP/wAf+AAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4ccDhxwOAHA4AcAAAAAAAA//8D//wP//A//8DhwAOHAA4cADhwAOHAA4cADgAAOAAAAAAD//wP//A//8D//wOAHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA//8D//wP//A//8ABwAAHAAAcAABwAP//A//8D//wP//AAAAAAAAP//A//8D//wP//AAAAAAAAOAHA4AcDgBwOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA//8D//wP//A//8AHwAA/AAP8AB/wAPn/A8f8DB/wIH/AAAAAAAAP//A//8D//wP//AAAcAABwAAHAAAcAABwAAHAAAAAAAAP//A//8D//wP//Af8AAP+AAH/AAD8AAHwAD/AB/wAf8AP+AA//8D//wP//AAAAAAAAP//A//8D//wP//AfwAAfwAAfwAAfwAAfwP//A//8D//wAAAAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHAA4cADhwAOHAA/8AD/wAP/AA/8AAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//+P//4///j//+AAA4AADgAAAP//A//8D//wP//A4eADh+AOH8A4f4D/3wP/HA/8MD/wQAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA4AADgAAOAAA//8D//wP//A//8DgAAOAAA4AADgAAAAAA//8D//wP//A//8AABwAAHAAAcAABwP//A//8D//wP//AAAADAAAPgAA/wAD/4AB/8AA/8AAfwAB/AA/8Af+AP/AA/wAD4AAMAAA4AAD+AAP/gA//8AH/wAB/AAf8Af/wP/4A/4AD/gAP/4AH/8AB/wAB/AB/8D//wP/gA/gADgAAIABA4AcDwDwPw/Afn4Af+AA/wAD/AA//AH5+A/D8DwDwOAHAgAEAAAAP/AA/8AD/wAP/AAAf8AB/wAH/AAf8D/wAP/AA/8AD/wAAAAAAAADh/wOH/A4f8Dh/wOHHA4ccDhxwOHHA/8cD/xwP/HA/8cAAAAAAAAf//9///3///f//9wAA3AADcAAMAAAOAAA/gAD/wAH/8AB/8AA/wAAPAAAEAAAAHAADcAANwAB3///f//9///wAA"), 32, atob("BwYLDg4UDwYJCQwMBgkGCQ4MDg4ODg4NDg4GBgwMDA4PDg4ODg4NDg4GDQ4MEg8ODQ8ODgwODhQODg4ICQg="), 22 + (scale << 8) + (1 << 16));
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('AAs8AYV8AaQjOgP8AYMPAYV/AYMH/4DBn///EA///4ADB/wSB//gAYQlCCIIABCIIAFDYIjBAaYjBLYIDTF64AH+CDCGdLLV/i7C/wfCAZ/4/BPCAaTiBAaaHBABaPIIaxPMcbxbBAapgCAahPhVYLDTUbA7CAZ/wv5PKN6xPzAof+AaTuXdcCbuJ8H4ngDCE4QDOJ+8PgBPBh+AE4IDPAA4'))),
|
||||
46,
|
||||
atob("CQ0UERQUFBQUExQUCQ=="),
|
||||
32+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontLECO1976Regular5fix11 = function(scale) {
|
||||
// Actual height 11 (10 - 0)
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('AAMBwEDgEGgECgF8g/4v/w/+B+ARBg//h/+j/8mEYsEw//h//D/+AgEMg0Yhk/DofggHAFwMAh9+j38nv4scw41h/nD/OH+YdC5kxzAODxgsBw47CIIM/wF/gAGBhkAjBKFCAN/mH+FgUZw0zhl+jH8mP4CAJZEBwVmBwdj+HwgPggfAQoIxBFgJoDSwUDJQhZDO4QsB4CVB+cP80fNAiVGgaWDmAA=='))),
|
||||
46,
|
||||
atob("BAYJCAkJCQkJCQkJBA=="),
|
||||
15+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
|
@ -60,7 +83,7 @@ function drawCal() {
|
|||
const CAL_Y = g.getHeight() - 44; // Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3;
|
||||
const CAL_AREA_H = 44; // g.getHeight()-CAL_Y+24; //+24: top widgtes only
|
||||
const CELL_W = g.getWidth() / 7; //cell width
|
||||
const CELL_H = (CAL_AREA_H - DAY_NAME_FONT_SIZE) / 3; //cell heigth
|
||||
const CELL_H = 1+parseInt((CAL_AREA_H - DAY_NAME_FONT_SIZE) / 3); //cell heigth
|
||||
const DAY_NUM_FONT_SIZE = Math.min(CELL_H + 3, 15); //size down, max 15
|
||||
|
||||
const wdStrt = 1;
|
||||
|
@ -73,11 +96,14 @@ function drawCal() {
|
|||
const tdyNumClr = 0; // today fg
|
||||
|
||||
g.setFont("Vector", DAY_NAME_FONT_SIZE + 3);
|
||||
|
||||
|
||||
|
||||
g.setColor(nrgb[1]);
|
||||
g.setFontAlign(-1, -1);
|
||||
// g.clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H);
|
||||
|
||||
//draw grid & Headline
|
||||
// == draw grid & Headline ==
|
||||
const dNames = ABR_DAY.map((a) => a.length <= 2 ? a : a.substr(0, 2)); //force shrt 2
|
||||
for (var dNo = 0; dNo < dNames.length; dNo++) {
|
||||
const dIdx = wdStrt >= 0 ? ((wdStrt + dNo) % 7) : ((dNo + d.getDay() + 4) % 7);
|
||||
|
@ -103,9 +129,10 @@ function drawCal() {
|
|||
// horizontal lines
|
||||
// for(i=0; i<3; i++){ const y=nextY+i*CELL_H; g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y); }
|
||||
|
||||
g.setFont("Vector", DAY_NUM_FONT_SIZE);
|
||||
// g.setFont("Vector", DAY_NUM_FONT_SIZE);
|
||||
|
||||
g.setFont("7x11Numeric7Seg", 1);
|
||||
// g.setFont("7x11Numeric7Seg", 1);
|
||||
g.setFontLECO1976Regular5fix11();
|
||||
|
||||
//write days
|
||||
const tdyDate = d.getDate();
|
||||
|
@ -113,16 +140,38 @@ function drawCal() {
|
|||
var rD = new Date(d.getTime());
|
||||
rD.setDate(rD.getDate() - days);
|
||||
var rDate = rD.getDate();
|
||||
|
||||
// == today background rectangle ==
|
||||
for (var y = 0; y < 3; y++) {
|
||||
for (var x = 0; x < dNames.length; x++) {
|
||||
if (rDate === tdyDate) { //today
|
||||
g.setColor(nrgb[tdyMrkClr]); //today marker color or fg color
|
||||
|
||||
// rectangle
|
||||
g.fillRect(x * CELL_W, nextY + CELL_H - 1, x * CELL_W + CELL_W, nextY + CELL_H + CELL_H - 1);
|
||||
g.setColor(nrgb[tdyNumClr]); //today color or fg color
|
||||
var frm=3; // fame pixels
|
||||
g.drawRect(x * CELL_W-frm, nextY + CELL_H - 1-frm, x * CELL_W + CELL_W+frm, nextY + CELL_H + CELL_H - 1+frm);
|
||||
}
|
||||
rD.setDate(rDate + 1);
|
||||
rDate = rD.getDate();
|
||||
}
|
||||
}
|
||||
|
||||
// == individual days ==
|
||||
rD = new Date(d.getTime());
|
||||
rD.setDate(rD.getDate() - days);
|
||||
rDate = rD.getDate();
|
||||
for (var y = 0; y < 3; y++) {
|
||||
for (var x = 0; x < dNames.length; x++) {
|
||||
if (rDate === tdyDate) { //today
|
||||
g.setColor(nrgb[tdyMrkClr]); //today marker color or fg color
|
||||
|
||||
// rectangle
|
||||
// g.fillRect(x * CELL_W, nextY + CELL_H - 1, x * CELL_W + CELL_W, nextY + CELL_H + CELL_H - 1);
|
||||
// g.setColor(nrgb[tdyNumClr]); //today color or fg color
|
||||
// g.drawRect(x * CELL_W, nextY + CELL_H - 1, x * CELL_W + CELL_W, nextY + CELL_H + CELL_H - 1);
|
||||
|
||||
// simulate "bold"
|
||||
// g.setColor(nrgb[tdyNumClr]); //today color or fg color
|
||||
g.drawString(rDate, 1 + x * CELL_W + ((CELL_W - g.stringWidth(rDate)) / 2) + 2, nextY + ((CELL_H - DAY_NUM_FONT_SIZE + 2) / 2) + (CELL_H * y));
|
||||
|
||||
} else if (IS_SUNDAY[rD.getDay()]) { //sundays
|
||||
|
@ -153,7 +202,7 @@ function draw() {
|
|||
|
||||
// g.setFont('Vector', 30);
|
||||
// g.setFont("7x11Numeric7Seg", 5);
|
||||
g.setFontLECO1976Regular42();
|
||||
g.setFontLECO1976Regular5fix42();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(timeString(h, m), g.getWidth() / 2, 28);
|
||||
g.drawString(dayString(d), g.getWidth() * 3 / 4, 88);
|
||||
|
@ -162,7 +211,7 @@ function draw() {
|
|||
g.reset();
|
||||
|
||||
// Steps
|
||||
g.setFontLECO1976Regular22();
|
||||
g.setFontLECO1976Regular5fix22();
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString(getSteps(), 8, 88);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "glbasic",
|
||||
"name": "GLBasic Clock",
|
||||
"shortName": "GLBasic",
|
||||
"version": "0.21",
|
||||
"version": "0.22",
|
||||
"description": "A clock with large numbers",
|
||||
"dependencies": {"widpedom":"app"},
|
||||
"icon": "icon48.png",
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
|
||||
var $name = document.getElementById('add_product_name')
|
||||
var $form = document.getElementById('add_product_form')
|
||||
var $button = document.getElementById('add_product_button')
|
||||
var $quantity = document.getElementById('add_product_quantity')
|
||||
var $list = document.getElementById('products')
|
||||
var $reset = document.getElementById('reset')
|
||||
|
@ -68,7 +67,8 @@
|
|||
ok: false
|
||||
})
|
||||
|
||||
renderProducts()
|
||||
renderProducts();
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
$name.value = ''
|
||||
$quantity.value = 1
|
||||
})
|
||||
|
|
|
@ -21,3 +21,4 @@
|
|||
0.20: Fix the settings page, it would not update settings correctly.
|
||||
0.21: Update boot.min.js.
|
||||
0.22: Fix timeout for heartrate sensor on 3 minute setting (#2435)
|
||||
0.23: Fix HRM logic
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
(function(){
|
||||
var settings = require("Storage").readJSON("health.json",1)||{};
|
||||
(function() {
|
||||
var settings = require("Storage").readJSON("health.json", 1) || {};
|
||||
var hrm = 0|settings.hrm;
|
||||
if (hrm == 1 || hrm == 2) {
|
||||
function onHealth() {
|
||||
Bangle.setHRMPower(1, "health");
|
||||
setTimeout(()=>Bangle.setHRMPower(0, "health"),hrm*60000); // give it 1 minute detection time for 3 min setting and 2 minutes for 10 min setting
|
||||
if (hrm == 1){
|
||||
for (var i = 1; i <= 2; i++){
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => Bangle.setHRMPower(0, "health"), hrm * 60000); // give it 1 minute detection time for 3 min setting and 2 minutes for 10 min setting
|
||||
if (hrm == 1) {
|
||||
function startMeasurement() {
|
||||
Bangle.setHRMPower(1, "health");
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => {
|
||||
Bangle.setHRMPower(0, "health");
|
||||
}, 60000);
|
||||
}, (i * 200000));
|
||||
}
|
||||
setTimeout(startMeasurement, 200000);
|
||||
setTimeout(startMeasurement, 400000);
|
||||
}
|
||||
}
|
||||
Bangle.on("health", onHealth);
|
||||
Bangle.on('HRM', h => {
|
||||
if (h.confidence>80) Bangle.setHRMPower(0, "health");
|
||||
Bangle.on("HRM", (h) => {
|
||||
if (h.confidence > 90 && Math.abs(Bangle.getHealthStatus().bpm - h.bpm) < 1) Bangle.setHRMPower(0, "health");
|
||||
});
|
||||
if (Bangle.getHealthStatus().bpmConfidence) return;
|
||||
if (Bangle.getHealthStatus().bpmConfidence > 90) return;
|
||||
onHealth();
|
||||
} else Bangle.setHRMPower(hrm!=0, "health");
|
||||
} else Bangle.setHRMPower(!!hrm, "health");
|
||||
})();
|
||||
|
||||
Bangle.on("health", health => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
function l(){var a=require("Storage").readJSON("health.json",1)||{},d=Bangle.getHealthStatus("day").steps;a.stepGoalNotification&&0<a.stepGoal&&d>=a.stepGoal&&(d=(new Date(Date.now())).toISOString().split("T")[0],!a.stepGoalNotificationDate||a.stepGoalNotificationDate<d)&&(Bangle.buzz(200,.5),require("notify").show({title:a.stepGoal+" steps",body:"You reached your step goal!",icon:atob("DAyBABmD6BaBMAsA8BCBCBCBCA8AAA==")}),a.stepGoalNotificationDate=d,require("Storage").writeJSON("health.json",
|
||||
a))}(function(){var a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){function d(){Bangle.setHRMPower(1,"health");setTimeout(()=>Bangle.setHRMPower(0,"health"),6E4*a);if(1==a)for(var b=1;2>=b;b++)setTimeout(()=>{Bangle.setHRMPower(1,"health");setTimeout(()=>{Bangle.setHRMPower(0,"health")},6E4)},2E5*b)}Bangle.on("health",d);Bangle.on("HRM",b=>{80<b.confidence&&Bangle.setHRMPower(0,"health")});Bangle.getHealthStatus().bpmConfidence||d()}else Bangle.setHRMPower(0!=a,"health")})();
|
||||
Bangle.on("health",a=>{function d(c){return String.fromCharCode(c.steps>>8,c.steps&255,c.bpm,Math.min(c.movement/8,255))}var b=new Date(Date.now()-59E4);a&&0<a.steps&&l();var f=function(c){return 145*(c.getDate()-1)+6*c.getHours()+(0|6*c.getMinutes()/60)}(b);b=function(c){return"health-"+c.getFullYear()+"-"+(c.getMonth()+1)+".raw"}(b);var g=require("Storage").read(b);if(g){var e=g.substr(8+4*f,4);if("\u00ff\u00ff\u00ff\u00ff"!=e){print("HEALTH ERR: Already written!");return}}else require("Storage").write(b,
|
||||
"HEALTH1\x00",0,17988);var h=8+4*f;require("Storage").write(b,d(a),h,17988);if(143==f%145)if(f=h+4,"\u00ff\u00ff\u00ff\u00ff"!=g.substr(f,4))print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++)e=g.substr(h,4),"\u00ff\u00ff\u00ff\u00ff"!=e&&(a.steps+=(e.charCodeAt(0)<<8)+e.charCodeAt(1),a.movement+=e.charCodeAt(2),a.movCnt++,e=e.charCodeAt(2),a.bpm+=e,e&&a.bpmCnt++),h-=4;a.bpmCnt&&(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt);
|
||||
require("Storage").write(b,d(a),f,17988)}})
|
||||
a))}(function(){var a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){var d=function(){Bangle.setHRMPower(1,"health");setTimeout(function(){return Bangle.setHRMPower(0,"health")},6E4*a);if(1==a){var b=function(){Bangle.setHRMPower(1,"health");setTimeout(function(){Bangle.setHRMPower(0,"health")},6E4)};setTimeout(b,2E5);setTimeout(b,4E5)}};Bangle.on("health",d);Bangle.on("HRM",function(b){90<b.confidence&&1>Math.abs(Bangle.getHealthStatus().bpm-b.bpm)&&Bangle.setHRMPower(0,
|
||||
"health")});90<Bangle.getHealthStatus().bpmConfidence||d()}else Bangle.setHRMPower(!!a,"health")})();Bangle.on("health",function(a){function d(c){return String.fromCharCode(c.steps>>8,c.steps&255,c.bpm,Math.min(c.movement/8,255))}var b=new Date(Date.now()-59E4);a&&0<a.steps&&l();var f=function(c){return 145*(c.getDate()-1)+6*c.getHours()+(0|6*c.getMinutes()/60)}(b);b=function(c){return"health-"+c.getFullYear()+"-"+(c.getMonth()+1)+".raw"}(b);var g=require("Storage").read(b);if(g){var e=g.substr(8+
|
||||
4*f,4);if("\u00ff\u00ff\u00ff\u00ff"!=e){print("HEALTH ERR: Already written!");return}}else require("Storage").write(b,"HEALTH1\x00",0,17988);var h=8+4*f;require("Storage").write(b,d(a),h,17988);if(143==f%145)if(f=h+4,"\u00ff\u00ff\u00ff\u00ff"!=g.substr(f,4))print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++)e=g.substr(h,4),"\u00ff\u00ff\u00ff\u00ff"!=e&&(a.steps+=(e.charCodeAt(0)<<8)+e.charCodeAt(1),a.movement+=e.charCodeAt(2),
|
||||
a.movCnt++,e=e.charCodeAt(2),a.bpm+=e,e&&a.bpmCnt++),h-=4;a.bpmCnt&&(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt);require("Storage").write(b,d(a),f,17988)}})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"shortName": "Health",
|
||||
"version": "0.22",
|
||||
"version": "0.23",
|
||||
"description": "Logs health data and provides an app to view it",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
0.02: Improve battery life, sprite resolution, fix launcher issue and unaligned text bug
|
||||
0.03: Reduce code size, refresh once a minute and faster refresh
|
||||
0.04: Show a random kana every minute to improve learning
|
||||
0.05: Tell clock widgets to hide.
|
||||
0.05: Tell clock widgets to hide
|
||||
0.06: Fix exception when showing missing hiragana 'WO'
|
||||
|
|
|
@ -102,6 +102,7 @@ hiragana['RU'] = image(51, 50, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lB
|
|||
hiragana['RE'] = image(56, 50, "gEf8AGF+AGigP/wAGDg//GYQGBh//C4M/AYICB/AGDv///gGC+P/AwQKB+YGB/wNC+//w4GDBYMDAwn4AwQ3BFQIGF8AGF4AGFgAGEAYMDHwIGBAYIGDn5XBAwhlBAwd/Axh6CAwSPBAwMHAxEDAwqdBAwidDAw5IBOoQGDU4QGDUAIGE//fAwufCgrmCh4iCAwk4nwGE/EcAwbSBjAGFegReCUgIGJOYIUEQIYGCIYOAAwPgAwIAIA=");
|
||||
hiragana['RO'] = image(50, 50, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAgAD");
|
||||
hiragana['WA'] = image(51, 50, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/ABggAEA=");
|
||||
hiragana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); // XXX there's no WO in hiragana, so we fill it with a copy of the katakana char
|
||||
hiragana['N'] = image(54, 50, "AAVgAYUP8EHwAGCv/Av4RD/8D/wFCgf8g/8DQf4j/4AwU/8E/+AaDwF//4VBgIfB/4GCD4MPAwcf+YFB/4jBn4FC/4jBAof/4AYC//n/+DBYeD/wZC/f/FgIrCGIQsCKYU/444CKYP/z4xCvxOBv+/8EBQQP4B4KFCCoJeCNIYPBQgQKBj53CAYSbBCYQDBHgJbCTYUDOQZHBM4QTBTYX/GQQxBP4Y8BDQRGBTYY4Eh5MDHgZTDAojdEbAYGEHgIGEv7/DHgIhFfAh1EEIg8GEIg8GTYYhDHhYAF");
|
||||
/// /////////////////////////////////////////
|
||||
|
||||
|
@ -123,7 +124,6 @@ function next () {
|
|||
}
|
||||
}
|
||||
curkana = 'KA';
|
||||
kana = hiramode ? hiragana[curkana] : katakana[curkana];
|
||||
updateWatch(ohhmm);
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,6 @@ function randKana() {
|
|||
const total = keys.length;
|
||||
let index = 0 | (Math.random() * total);
|
||||
curkana = keys[index];
|
||||
kana = hiramode ? hiragana[curkana] : katakana[curkana];
|
||||
} catch (e) {
|
||||
randKana();
|
||||
}
|
||||
|
@ -146,7 +145,6 @@ function prev () {
|
|||
if (curkana === k) {
|
||||
if (count > 0) {
|
||||
curkana = oldk;
|
||||
kana = katakana[curkana];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +152,6 @@ function prev () {
|
|||
count++;
|
||||
}
|
||||
curkana = oldk;
|
||||
kana = katakana[curkana];
|
||||
updateWatch(ohhmm);
|
||||
}
|
||||
|
||||
|
@ -270,4 +267,3 @@ Bangle.loadWidgets();
|
|||
tickWatch();
|
||||
setInterval(tickWatch, 1000 * 60);
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "kanawatch",
|
||||
"name": "Kanawatch",
|
||||
"shortName": "Kanawatch",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"type": "clock",
|
||||
"description": "Learn Hiragana and Katakana",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot3.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies" : { "clock_info":"module" },
|
||||
"storage": [
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies" : { "clock_info":"module" },
|
||||
"storage": [
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,17 @@
|
|||
# Long Press, Buzz!
|
||||
|
||||
Buzz at boot after a long press to indicate the watch was reinitiated at your command.
|
||||
|
||||
To infinity and beyond, space ranger!
|
||||
|
||||
## Usage
|
||||
|
||||
Just install and it will run as boot code.
|
||||
|
||||
## Requests
|
||||
|
||||
Mention @[thyttan](https://github.com/thyttan) in an issue to the official [BangleApps repository](https://github.com/espruino/BangleApps/issues) for feature requests and bug reports.
|
||||
|
||||
## Creators
|
||||
|
||||
[thyttan](https://github.com/thyttan) and [Gordon Williams](https://github.com/gfwilliams).
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
|||
if (BTN.read()) Bangle.buzz(80,0.40);
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "longpressbuzz",
|
||||
"name": "Long Press, Buzz!",
|
||||
"shortName":"LPB",
|
||||
"version":"0.01",
|
||||
"description": "Buzz at boot after a long press to indicate the watch was reinitiated at your command.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"longpressbuzz.0.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
|
@ -7,3 +7,4 @@
|
|||
1.3: icon changed
|
||||
1.4: new management of events implemented; removed code no longer used (from now the music will be managed by the Messagesgui app)
|
||||
1.5: Fix graphic bug; View via popup while there are other open apps
|
||||
1.6: fix for #2689; ( white screen )
|
|
@ -467,7 +467,7 @@ const updateTimeout = function(){
|
|||
if (settings.timeOut!="Off"){
|
||||
removeTimeout();
|
||||
if( callInProgress) return; //c'è una chiamata in corso -> no timeout
|
||||
if( music!=undefined && EventQueue.length==0 ) return; //ho aperto l'interfaccia della musica e non ho messaggi davanti -> no timeout
|
||||
//if( typeof music !== 'undefined' && EventQueue.length==0 ) return; //ho aperto l'interfaccia della musica e non ho messaggi davanti -> no timeout
|
||||
|
||||
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages_light",
|
||||
"name": "Messages Light",
|
||||
"version": "1.5",
|
||||
"version": "1.6",
|
||||
"description": "A light implementation of messages App (display notifications from iOS and Gadgetbridge/Android)",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -17,3 +17,5 @@
|
|||
Satellite count moved to widget bar to leave more room for the map
|
||||
0.15: Make track drawing an option (default off)
|
||||
0.16: Draw waypoints, too.
|
||||
0.17: With new Recorder app allow track to be drawn in the background
|
||||
Switch tile layer URL for faster/more reliable map tiles
|
|
@ -5,6 +5,7 @@ var fix = {};
|
|||
var mapVisible = false;
|
||||
var hasScrolled = false;
|
||||
var settings = require("Storage").readJSON("openstmap.json",1)||{};
|
||||
var plotTrack;
|
||||
|
||||
// Redraw the whole page
|
||||
function redraw() {
|
||||
|
@ -20,7 +21,9 @@ function redraw() {
|
|||
}
|
||||
if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) {
|
||||
g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later
|
||||
WIDGETS["recorder"].plotTrack(m);
|
||||
plotTrack = WIDGETS["recorder"].plotTrack(m, { async : true, callback : function() {
|
||||
plotTrack = undefined;
|
||||
}});
|
||||
}
|
||||
}
|
||||
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
|
@ -79,6 +82,7 @@ function showMap() {
|
|||
g.reset().clearRect(R);
|
||||
redraw();
|
||||
Bangle.setUI({mode:"custom",drag:e=>{
|
||||
if (plotTrack && plotTrack.stop) plotTrack.stop();
|
||||
if (e.b) {
|
||||
g.setClipRect(R.x,R.y,R.x2,R.y2);
|
||||
g.scroll(e.dx,e.dy);
|
||||
|
@ -90,6 +94,7 @@ function showMap() {
|
|||
redraw();
|
||||
}
|
||||
}, btn: btn=>{
|
||||
if (plotTrack && plotTrack.stop) plotTrack.stop();
|
||||
mapVisible = false;
|
||||
var menu = {"":{title:"Map"},
|
||||
"< Back": ()=> showMap(),
|
||||
|
|
|
@ -87,8 +87,8 @@ TODO:
|
|||
However some don't allow cross-origin use */
|
||||
//var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast, TOO SLOW
|
||||
//var TILELAYER = 'http://a.tile.stamen.com/toner/{z}/{x}/{y}.png'; // black and white
|
||||
var TILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var PREVIEWTILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var TILELAYER = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var PREVIEWTILELAYER = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
|
||||
var loadedMaps = [];
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "openstmap",
|
||||
"name": "OpenStreetMap",
|
||||
"shortName": "OpenStMap",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"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",
|
||||
|
|
|
@ -26,3 +26,6 @@
|
|||
0.20: Automatic translation of some more strings.
|
||||
0.21: Speed report now uses speed units from locale
|
||||
0.22: Convert Yes/No On/Off in settings to checkboxes
|
||||
0.23: Add graphing for HRM, fix some other graphs
|
||||
Altitude graphing now uses barometer altitude if it exists
|
||||
plotTrack in widget allows track to be drawn in the background (doesn't block execution)
|
|
@ -20,10 +20,29 @@ You can record
|
|||
* **BAT** Battery percentage and voltage
|
||||
* **Core** CoreTemp body temperature
|
||||
|
||||
You can then start/stop recording from the Recorder app itself (and as long as widgets are
|
||||
enabled in the app you're using, you can move to another app and continue recording).
|
||||
Some apps like the [Run app](https://banglejs.com/apps/?id=run) are able to automatically start/stop the Recorder too.
|
||||
|
||||
**Note:** It is possible for other apps to record information using this app
|
||||
as well. They need to define a `foobar.recorder.js` file - see the `getRecorders`
|
||||
function in `widget.js` for more information.
|
||||
|
||||
## Graphing
|
||||
|
||||
You can download the information to the PC using [the App Loader](https://banglejs.com/apps/?id=recorder). Connect
|
||||
to your Bangle, then in `My Apps` click the disk icon next to the `Recorder` app to download data.
|
||||
|
||||
You can also view some information on the watch.
|
||||
|
||||
* Tap `View Tracks`
|
||||
* Tap on the Track number you're interested in, and you'll see a page with information about that track...
|
||||
* `Plot Map` plots a map using GPS coordinates
|
||||
* `Plot OpenStMap` plots a map using GPS coordinates on top of an OpenStreetMap map (if the app is installed)
|
||||
* `Plot Alt` plots altitude over time
|
||||
* `Plot Speed` plots speed over time
|
||||
* `Plot HRM` plots heart rate over time
|
||||
|
||||
## Tips
|
||||
|
||||
When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a red satellite symbol, which you will see turn green when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix.
|
||||
|
|
|
@ -60,9 +60,9 @@ function showMainMenu() {
|
|||
setTimeout(function() {
|
||||
E.showMenu();
|
||||
WIDGETS["recorder"].setRecording(v).then(function() {
|
||||
print(/*LANG*/"Complete");
|
||||
//print("Record start Complete");
|
||||
loadSettings();
|
||||
print(settings.recording);
|
||||
print("Recording: "+settings.recording);
|
||||
showMainMenu();
|
||||
});
|
||||
}, 1);
|
||||
|
@ -185,7 +185,7 @@ function viewTrack(filename, info) {
|
|||
'': { 'title': /*LANG*/'Track '+info.fn }
|
||||
};
|
||||
if (info.time)
|
||||
menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){};
|
||||
menu[info.time.toISOString().substr(0,16).replace("T"," ")] = {};
|
||||
menu["Duration"] = { value : asTime(info.duration)};
|
||||
menu["Records"] = { value : ""+info.records };
|
||||
if (info.fields.includes("Latitude"))
|
||||
|
@ -198,7 +198,8 @@ function viewTrack(filename, info) {
|
|||
info.qOSTM = true;
|
||||
plotTrack(info);
|
||||
}
|
||||
if (info.fields.includes("Altitude"))
|
||||
if (info.fields.includes("Altitude") ||
|
||||
info.fields.includes("Barometer Altitude"))
|
||||
menu[/*LANG*/'Plot Alt.'] = function() {
|
||||
plotGraph(info, "Altitude");
|
||||
};
|
||||
|
@ -206,6 +207,10 @@ function viewTrack(filename, info) {
|
|||
menu[/*LANG*/'Plot Speed'] = function() {
|
||||
plotGraph(info, "Speed");
|
||||
};
|
||||
if (info.fields.includes("Heartrate"))
|
||||
menu[/*LANG*/'Plot HRM'] = function() {
|
||||
plotGraph(info, "Heartrate");
|
||||
};
|
||||
// TODO: steps, heart rate?
|
||||
menu[/*LANG*/'Erase'] = function() {
|
||||
E.showPrompt(/*LANG*/"Delete Track?").then(function(v) {
|
||||
|
@ -331,14 +336,26 @@ function viewTrack(filename, info) {
|
|||
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=="Altitude") {
|
||||
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("Altitude");
|
||||
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;
|
||||
|
@ -350,7 +367,7 @@ function viewTrack(filename, info) {
|
|||
// use locate to work out units
|
||||
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
|
||||
let units = localeStr.replace(/[0-9.]*/,"");
|
||||
var factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
|
||||
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
|
||||
// title
|
||||
title = /*LANG*/"Speed"+` (${units})`;
|
||||
var latIdx = info.fields.indexOf("Latitude");
|
||||
|
@ -386,6 +403,10 @@ function viewTrack(filename, info) {
|
|||
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;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.22",
|
||||
"version": "0.23",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
|
|
@ -265,8 +265,14 @@
|
|||
updateSettings(settings);
|
||||
WIDGETS["recorder"].reload();
|
||||
return Promise.resolve(settings.recording);
|
||||
},plotTrack:function(m) { // m=instance of openstmap module
|
||||
// Plots the current track in the currently set color
|
||||
},plotTrack:function(m, options) { // m=instance of openstmap module
|
||||
/* Plots the current track in the currently set color.
|
||||
options can be {
|
||||
async: if true, plots the path a bit at a time - returns an object with a 'stop' function to stop
|
||||
callback: a function to call back when plotting is finished
|
||||
}
|
||||
*/
|
||||
options = options||{};
|
||||
if (!activeRecorders.length) return; // not recording
|
||||
var settings = loadSettings();
|
||||
// keep function to draw track in RAM
|
||||
|
@ -282,11 +288,15 @@
|
|||
c = l.split(",");
|
||||
l = f.readLine(f);
|
||||
}
|
||||
var asyncTimeout;
|
||||
var color = g.getColor();
|
||||
function plotPartial() {
|
||||
asyncTimeout = undefined;
|
||||
if (l===undefined) return; // empty file?
|
||||
mp = m.latLonToXY(+c[la], +c[lo]);
|
||||
g.moveTo(mp.x,mp.y);
|
||||
g.moveTo(mp.x,mp.y).setColor(color);
|
||||
l = f.readLine(f);
|
||||
var n = 200; // only plot first 200 points to keep things fast(ish)
|
||||
var n = options.async ? 20 : 200; // only plot first 200 points to keep things fast(ish)
|
||||
while(l && n--) {
|
||||
c = l.split(",");
|
||||
if (c[la]) {
|
||||
|
@ -295,6 +305,18 @@
|
|||
}
|
||||
l = f.readLine(f);
|
||||
}
|
||||
if (options.async && n<0)
|
||||
asyncTimeout = setTimeout(plotPartial, 20);
|
||||
else if (options.callback) options.callback();
|
||||
}
|
||||
plotPartial();
|
||||
if (options.async) return {
|
||||
stop : function() {
|
||||
if (asyncTimeout) clearTimeout(asyncTimeout);
|
||||
asyncTimeout = undefined;
|
||||
if (options.callback) options.callback();
|
||||
}
|
||||
};
|
||||
}
|
||||
plot(g);
|
||||
}};
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: First rev. Could use a little optimization love.
|
||||
0.02: Added battery ring, bubble background for numbers, settings and little optimaztion love.
|
|
@ -1,11 +1,16 @@
|
|||
# Rings watchface
|
||||
|
||||
Ring based watchface, read from the outside in. When the watch is unlocked the circles shrink to show the date ring.
|
||||
|
||||
By Amos Blanton, inspired by and remixed from Rinkulainen by Jukio Kallio.
|
||||
Contributors: Amos Blanton, pinq-. Inspired by Rinkulainen by Julio Kallio.
|
||||
|
||||

|
||||
View when watch is locked.
|
||||
|
||||

|
||||

|
||||
View when watch is locked with numbers and bubble settings on
|
||||
|
||||

|
||||
Watch unlocked, showing the date.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,20 +2,30 @@
|
|||
// for Bangle.js 2
|
||||
// by Amos Blanton
|
||||
// Remixed from / inspired by Rinkulainen watch face by Jukio Kallio
|
||||
// 2023: pinq- added new futures
|
||||
|
||||
// To Do:
|
||||
// Make Month / year text buffer 1/2 size
|
||||
// Optimize text positioning transforms
|
||||
|
||||
const watch = {
|
||||
x:0, y:0, w:0, h:0,
|
||||
color:"#000000",
|
||||
dateRing : { size:109, weight:20, color:"#00FF00", cursor:14, numbers: true },
|
||||
hourRing : { size:85, weight:20, color:"#00FFFF", cursor:14, numbers: true },
|
||||
minuteRing : { size:61, weight:20, color:"#FFFF00", cursor:14, numbers: true },
|
||||
screen : { width:g.getWidth(), height:g.getHeight(), centerX: g.getWidth() *0.5, centerY: g.getHeight() * 0.5, cursor: 14, font:"6x8:2" },
|
||||
dateRing : { size:109, weight:20, color:"#00FF00", numbers: false, range: 30 },
|
||||
hourRing : { size:82, weight:20, color:"#00FFFF", numbers: false, range: 12},
|
||||
minuteRing : { size:55, weight:18, color:"#FFFF00", numbers: false, range: 60},
|
||||
batteryRing: { size :30, weight:10, color:"#ff3300", numbers: false, range: 100},
|
||||
screen : { width:g.getWidth(), height:g.getHeight(), centerX: g.getWidth() *0.5, centerY: g.getHeight() * 0.5, cursor: 14, font:"Vector:18", bubble:false },
|
||||
};
|
||||
|
||||
var settings = require('Storage').readJSON("rings.settings.json", true) || {};
|
||||
|
||||
if(settings.minute){
|
||||
watch.minuteRing.numbers = settings.minute.numbers;
|
||||
watch.hourRing.numbers = settings.hour.numbers;
|
||||
watch.dateRing.numbers = settings.date.numbers;
|
||||
watch.screen.bubble = settings.bubble;
|
||||
}
|
||||
delete settings;
|
||||
const month= ["JANUARY","FEBRUARY","MARCH","APRIL","MAY","JUNE","JULY",
|
||||
"AUGUST","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"];
|
||||
|
||||
|
@ -45,31 +55,77 @@ function queueDraw() {
|
|||
}, wait - (Date.now() % wait));
|
||||
}
|
||||
|
||||
// Draws a time circle (date, hours, minutes)
|
||||
function drawTimeCircle(color, size, weight, range, value ) {
|
||||
// Draws a circles (date, hours, minutes)
|
||||
function drawCircle(ringValues, offset, value ) {
|
||||
// variables for vertex transformations and positioning time
|
||||
var tver, tobj, tran;
|
||||
var ttime = (value / range) * (Math.PI * 2);
|
||||
let tver, tobj, tran;
|
||||
let ttime = (value / ringValues.range) * (Math.PI * 2);
|
||||
|
||||
// draw circle and line
|
||||
g.setColor(color).fillCircle(watch.screen.centerX, watch.screen.centerY, size);
|
||||
g.setColor("#000000").fillCircle(watch.screen.centerX, watch.screen.centerY, size - weight);
|
||||
|
||||
tver = [-watch.screen.cursor, 0, watch.screen.cursor, 0, watch.screen.cursor, -size*1.01, -watch.screen.cursor, -size*1.05];
|
||||
// draw circle
|
||||
g.setColor(ringValues.color).fillCircle(watch.screen.centerX, watch.screen.centerY, ringValues.size + offset);
|
||||
g.setColor("#000000").fillCircle(watch.screen.centerX, watch.screen.centerY, ringValues.size - ringValues.weight + offset);
|
||||
|
||||
tobj = { x:watch.screen.centerX, y:watch.screen.centerY, scale:1, rotate:ttime };
|
||||
if(watch.screen.bubble){
|
||||
tver = [-1, 0, 1, 0, 1, -ringValues.size-offset, -1, -(ringValues.size + offset -21)];
|
||||
tran = g.transformVertices(tver, tobj);
|
||||
if(ringValues.numbers){
|
||||
g.setColor("#000000").fillCircle((tran[4]+tran[6]) / 2 , (tran[5]+tran[7]) / 2, 17 + offset/10);
|
||||
}else{
|
||||
g.setColor("#000000").fillCircle((tran[4]+tran[6]) / 2 , (tran[5]+tran[7]) / 2, 10 + offset/10);
|
||||
}
|
||||
}else{
|
||||
if(ringValues.numbers){
|
||||
tver = [-watch.screen.cursor, 0, watch.screen.cursor, 0, watch.screen.cursor, -ringValues.size*1.01 - offset, -watch.screen.cursor, -ringValues.size*1.05 - offset];
|
||||
}else{
|
||||
tver = [-watch.screen.cursor * 0.4, 0, watch.screen.cursor * 0.4, 0, watch.screen.cursor *0.4, -ringValues.size*1.01 - offset, -watch.screen.cursor*0.4, -ringValues.size*1.05 - offset];
|
||||
}
|
||||
tran = g.transformVertices(tver, tobj);
|
||||
g.fillPoly(tran);
|
||||
|
||||
// Draw numbers
|
||||
g.setFontAlign(0,0).setFont(watch.screen.font, 2).setColor(1,1,1);
|
||||
}
|
||||
|
||||
// Draw numbers
|
||||
if(ringValues.numbers){
|
||||
// size - 21 is the right offset to get the numbers aligned in the circle.
|
||||
tver = [-1, 0, 1, 0, 1, -size, -1, -(size -21)];
|
||||
tver = [-1, 0, 1, 0, 1, -ringValues.size-offset, -1, -(ringValues.size + offset -21)];
|
||||
tran = g.transformVertices(tver, tobj);
|
||||
g.setColor(1,1,1);
|
||||
//g.setColor(1,1,1);
|
||||
g.setFontAlign(0,0).setFont(watch.screen.font, 2).setColor(1,1,1);
|
||||
g.drawString(value, (tran[4]+tran[6]) / 2 , (tran[5]+tran[7]) / 2 );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// For battery disable
|
||||
function drawArc(percent, color, ArchR) {
|
||||
let offset = 0;
|
||||
let end = 360;
|
||||
let radius = ArchR + 2;
|
||||
|
||||
if (percent <= 0) return; // no gauge needed
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
let startRotation = -offset;
|
||||
let endRotation = startRotation - (end * percent);
|
||||
|
||||
g.setColor(color);
|
||||
// convert to radians
|
||||
startRotation *= Math.PI / 180;
|
||||
let amt = Math.PI / 10;
|
||||
endRotation = (endRotation * Math.PI / 180) - amt;
|
||||
// all we need to draw is an arc, because we'll fill the center
|
||||
let poly = [watch.screen.centerX, watch.screen.centerY];
|
||||
for (let r = startRotation; r > endRotation; r -= amt)
|
||||
poly.push(
|
||||
watch.screen.centerX - radius * Math.sin(r),
|
||||
watch.screen.centerY - radius * Math.cos(r)
|
||||
);
|
||||
g.fillPoly(poly);
|
||||
g.setColor("#000000").fillCircle(watch.screen.centerX, watch.screen.centerY, ArchR - 10);
|
||||
g.setColor(color).fillCircle(watch.screen.centerX - (radius -5) * Math.sin(endRotation + amt), watch.screen.centerY - (radius -5) * Math.cos(endRotation + amt), 4);
|
||||
}
|
||||
|
||||
// Draws text for month and year in date circle
|
||||
|
@ -80,14 +136,14 @@ function drawMonthCircleText( text, circleSize, range, value){
|
|||
|
||||
monthCircleTextBuffer.clear();
|
||||
monthCircleTextBuffer.fillRect(0,0,watch.screen.width,watch.screen.height);
|
||||
|
||||
var tver, tobj, tran;
|
||||
|
||||
|
||||
// From here: https://forum.espruino.com/comments/16781795/
|
||||
var gr = Graphics.createArrayBuffer(24,16,1,{msb:true});
|
||||
var grimg = gr.asImage();
|
||||
grimg.transparent = 1;
|
||||
monthCircleTextBuffer.setColor(0,0,0);
|
||||
monthCircleTextBuffer.setColor(1,1,1);
|
||||
|
||||
for(z=0; z < text.length; z++){
|
||||
tobj = { x:watch.screen.centerX, y:watch.screen.centerY, scale:1, rotate: ((z + 1) / range) * (Math.PI * 2) };
|
||||
|
@ -119,7 +175,6 @@ function drawMonthCircleText( text, circleSize, range, value){
|
|||
function shrinkCircles(toggle){
|
||||
// If there's a queued draw operation,removeit so animation isn't interrupted.
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
|
||||
var date = new Date();
|
||||
var delta = 1;
|
||||
|
||||
|
@ -128,6 +183,7 @@ function shrinkCircles(toggle){
|
|||
counter = 1;
|
||||
// We're finished, so queue next draw.
|
||||
queueDraw();
|
||||
if(!toggle) drawArc(E.getBattery() / 100, watch.batteryRing.color, watch.batteryRing.size);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -142,14 +198,14 @@ function shrinkCircles(toggle){
|
|||
|
||||
// Draw the date ring (unless it's the last run of an expansion).
|
||||
if(counter < 11 || toggle){
|
||||
drawTimeCircle(watch.dateRing.color, watch.dateRing.size + delta, watch.dateRing.weight, getDays(date.getFullYear(), date.getMonth()+1), date.getDate() );
|
||||
|
||||
drawCircle(watch.dateRing, delta, date.getDate());
|
||||
// Draw month and year in date ring
|
||||
drawMonthCircleText( month[date.getMonth()]+" "+date.getFullYear(), watch.dateRing.size - 24, getDays(date.getFullYear(), date.getMonth()+1), date.getDate()) ;
|
||||
drawMonthCircleText( date.getDate() + " " + month[date.getMonth()] + " " +date.getFullYear(), watch.dateRing.size - 24, getDays(date.getFullYear(), date.getMonth()+1), date.getDate()) ;
|
||||
}
|
||||
|
||||
drawTimeCircle(watch.hourRing.color, watch.hourRing.size + delta, watch.hourRing.weight, 12, date.getHours() );
|
||||
|
||||
drawTimeCircle(watch.minuteRing.color, watch.minuteRing.size + delta, watch.minuteRing.weight, 60, date.getMinutes() );
|
||||
drawCircle(watch.hourRing, delta, date.getHours());
|
||||
drawCircle(watch.minuteRing, delta, date.getMinutes());
|
||||
|
||||
counter += 1;
|
||||
setTimeout(shrinkCircles, 10, toggle);
|
||||
|
@ -170,19 +226,23 @@ function draw() {
|
|||
g.fillRect(0, 0, watch.screen.width, watch.screen.height);
|
||||
|
||||
// If unlocked, draw date ring and text and make hour and minute rings smaller
|
||||
var days_month = getDays(date.getFullYear(), date.getMonth()+1);
|
||||
if(!Bangle.isLocked()){
|
||||
unLockedOffset = 24;
|
||||
drawTimeCircle(watch.dateRing.color, watch.dateRing.size - unLockedOffset, watch.dateRing.weight, getDays(date.getFullYear(), date.getMonth()+1), date.getDate() );
|
||||
drawMonthCircleText( month[date.getMonth()]+" "+date.getFullYear(), watch.dateRing.size - unLockedOffset, getDays(date.getFullYear(), date.getMonth()+1), date.getDate()) ;
|
||||
// if the day has changed
|
||||
if(watch.dateRing.range != days_month) watch.dateRing.range = days_month;
|
||||
drawCircle(watch.dateRing, -unLockedOffset, date.getDate());
|
||||
drawMonthCircleText( date.getDate() + " " + month[date.getMonth()] + " " + date.getFullYear(), watch.dateRing.size - 24, getDays(date.getFullYear(), date.getMonth()+1), date.getDate());
|
||||
}
|
||||
drawCircle(watch.hourRing, -unLockedOffset, date.getHours());
|
||||
drawCircle(watch.minuteRing, -unLockedOffset, date.getMinutes());
|
||||
if(Bangle.isLocked()){
|
||||
drawArc(E.getBattery() / 100, watch.batteryRing.color, watch.batteryRing.size);
|
||||
}
|
||||
|
||||
drawTimeCircle(watch.hourRing.color, watch.hourRing.size - unLockedOffset, watch.hourRing.weight, 12, date.getHours() );
|
||||
drawTimeCircle(watch.minuteRing.color, watch.minuteRing.size -unLockedOffset , watch.minuteRing.weight, 60, date.getMinutes() );
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Trigger shrink / expand animation on unlock / lock events
|
||||
//drawArc(E.getBattery() / 100, watch.batteryRing.color, watch.batteryRing.size); Trigger shrink / expand animation on unlock / lock events
|
||||
Bangle.on('lock', on=>{
|
||||
if (on) { // locked, expand circles
|
||||
counter = 1;
|
||||
|
@ -204,7 +264,7 @@ g.clear();
|
|||
draw();
|
||||
|
||||
|
||||
// console.log("Whatevs");
|
||||
// .log("Whatevs");
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
|
@ -1,16 +1,36 @@
|
|||
{ "id": "rings",
|
||||
{
|
||||
"id": "rings",
|
||||
"name": "Rings - an animated watchface",
|
||||
"shortName":"Rings",
|
||||
"version":"0.01",
|
||||
"shortName": "Rings",
|
||||
"version": "0.02",
|
||||
"description": "Ring based watchface that animates to show the date when unlocked. Inspired by / remixed from Rinkulainen.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot1.png"}, {"url":"screenshot2.png"}],
|
||||
"screenshots": [{
|
||||
"url": "screenshot1.png"
|
||||
}, {
|
||||
"url": "screenshot2.png"
|
||||
}, {
|
||||
"url": "screenshot3.png"
|
||||
}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"rings.app.js","url":"app.js"},
|
||||
{"name":"rings.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
"storage": [{
|
||||
"name": "rings.app.js",
|
||||
"url": "app.js"
|
||||
},
|
||||
{
|
||||
"name": "rings.img",
|
||||
"url": "app-icon.js",
|
||||
"evaluate": true
|
||||
},
|
||||
{
|
||||
"name": "rings.settings.js",
|
||||
"url": "settings.js"
|
||||
}
|
||||
],
|
||||
"data": [{
|
||||
"name": "rings.settings.json"
|
||||
}]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 17 KiB |