Merge branch 'espruino:master' into development
|
|
@ -3,6 +3,9 @@ apps/banglerun/rollup.config.js
|
|||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
apps/qrcode/qr-scanner.umd.min.js
|
||||
apps/gipy/pkg/gpconv.js
|
||||
apps/gipy/pkg/gps.js
|
||||
apps/health/chart.min.js
|
||||
*.test.js
|
||||
|
||||
# typescript/generated files
|
||||
apps/btadv/*.js
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "gitsubmodule"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
|
@ -98,7 +98,7 @@ This is the best way to test...
|
|||
|
||||
**Note:** It's a great idea to get a local copy of the repository on your PC,
|
||||
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
|
||||
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
|
||||
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule update --init`.
|
||||
|
||||
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
127
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,19 @@
|
|||
<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>
|
||||
</p><p>
|
||||
<button class="btn tooltip" id="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button>
|
||||
<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>
|
||||
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
|
||||
<button class="btn tooltip" id="webideremote" data-tooltip="Enable the Web IDE remote server">Web IDE Remote</button>
|
||||
</p>
|
||||
<h3>Settings</h3>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
|
|
@ -147,12 +156,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 App Loader settings</button>
|
||||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
<h3>Device info</h3>
|
||||
|
|
@ -183,6 +204,9 @@
|
|||
<script src="core/js/appinfo.js"></script>
|
||||
<script src="core/js/index.js"></script>
|
||||
<script src="core/js/pwa.js" defer></script>
|
||||
<!-- FIXME - use espruino.com/ide, github -->
|
||||
<script src="https://espruino.github.io/EspruinoWebIDE/js/libs/peerjs.min.js"></script>
|
||||
<script src="https://espruino.github.io/EspruinoWebIDE/EspruinoTools/libs/webrtc-connection.js"></script>
|
||||
<script>
|
||||
/*Android = {
|
||||
bangleTx : function(data) {
|
||||
|
|
@ -208,8 +232,10 @@ if (typeof Android!=="undefined") {
|
|||
if (writecb) setTimeout(writecb,10);
|
||||
},
|
||||
close : function() {},
|
||||
on : function(evt,cb) { connection.handlers[evt] = cb; },
|
||||
received : "",
|
||||
hadData : false
|
||||
hadData : false,
|
||||
handlers : []
|
||||
}
|
||||
|
||||
function bangleRx(data) {
|
||||
|
|
@ -217,6 +243,9 @@ if (typeof Android!=="undefined") {
|
|||
connection.received += data;
|
||||
connection.hadData = true;
|
||||
if (connection.cb) connection.cb(data);
|
||||
// call data event
|
||||
if (connection.handlers["data"])
|
||||
connection.handlers["data"](data);
|
||||
}
|
||||
|
||||
function log(level, s) {
|
||||
|
|
@ -301,6 +330,7 @@ if (typeof Android!=="undefined") {
|
|||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
|
||||
Puck = {
|
||||
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||||
debug : Puck.debug,
|
||||
|
|
@ -311,7 +341,8 @@ if (typeof Android!=="undefined") {
|
|||
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
||||
writeProgress : Puck.writeProgress,
|
||||
connect : function(callback) {
|
||||
setTimeout(callback, 10);
|
||||
setTimeout(callback, 10, connection);
|
||||
return connection;
|
||||
},
|
||||
write : write,
|
||||
eval : function(expr, cb) {
|
||||
|
|
@ -346,7 +377,57 @@ if (typeof Android!=="undefined") {
|
|||
if ("object"==typeof err) console.log(err.stack);
|
||||
});
|
||||
}, 500);
|
||||
} else {
|
||||
showToast("You're running the App Loader version for Gadgetbridge, but you don't seem to be in Gadgetbridge!","error");
|
||||
}
|
||||
|
||||
function showWebRTCID(id) {
|
||||
showToast("Bridge's Peer ID: "+id);
|
||||
showPrompt("Web IDE Remote Access",`
|
||||
Remote access enabled. Peer ID:
|
||||
<br/><br/>
|
||||
<b>${id}</b>
|
||||
<br/><br/>
|
||||
Go to <b>espruino.com/ide</b> on your
|
||||
desktop and enter this code under
|
||||
<b>Remote Connection Bridge Peer ID</b> in Settings.
|
||||
Then connect to the <b>Android</b> device.
|
||||
`,{ok:1},false/*shouldEscapeHtml*/).then(() => {
|
||||
}, function() { /* cancelled */ });
|
||||
}
|
||||
|
||||
// Button to Enable Remote Web IDE
|
||||
var el = document.getElementById("webideremote");
|
||||
var webrtc;
|
||||
if (el) el.addEventListener("click", event=>{
|
||||
if (webrtc) showWebRTCID(webrtc.peerId);
|
||||
else {
|
||||
webrtc = webrtcInit({
|
||||
bridge:true,
|
||||
onStatus : function(s) {
|
||||
showToast(s);
|
||||
},
|
||||
onPeerID : function(id) {
|
||||
showWebRTCID(id);
|
||||
},
|
||||
onGetPorts : function(cb) {
|
||||
cb([{path:"Android",description:"Remote Device Connection",type:"socket"}]);
|
||||
},
|
||||
onPortConnect : function(serialPort, cb) {
|
||||
cb(); // we're already connected...
|
||||
},
|
||||
onPortDisconnect : function(serialPort) {
|
||||
},
|
||||
onPortWrite : function(data, cb) {
|
||||
Puck.write(data, cb);
|
||||
}
|
||||
});
|
||||
connection.on("data", function(d) {
|
||||
webrtc.onPortReceived(d);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
A simple clock with perspective scaling.
|
||||
Battery drainer, performance tester, show-off piece work-in-progress.
|
||||
|
||||
Demo.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmZCZmZAAAAAAAAAAAAAAAAAAAAAAAAAACZAAkJkJAAAAAAAAAAAAAAAAAAAAAACQkJmZmQCZkAAAAAAAAAAAAAAAAAAAAACQAAAAAJmQmZAAAAAAAAAAAAAAAAAAAAAAAAAAAJkAAJkAAAAAAAAAAAAAAAAAAAAAAAAAAJCZkACQAAAAAAAAAAAAAAAJAAAAAAAAAACZAACZAAAAAAAAAAAAAAAAAAAAAAAAAAmZAJmZkAAAAAAAAAAAAAAAAAAAAACQmQkAmQAJmQAAAAAAAAAAAAAAAAAAAAmZAJkAAAkJmQAAAAAAAAAAAAAAAAkAAAAA///wAAAJmf///wAAAAmQAAAAAAAJAAD/////8AAJD/////AAAAAAAAAJAAAJCQD//////wCZ//////AAAJAAAAAJAAD//w//8AAP/wCf//kAD/AAAAkAAAAAAAD5CQ8J8AAP/wCZkJkAAPAAAJCQAAAAAJn//w//mQAP/wAAmQCQAPAAAAAAAA//CQ///w8J+ZAP/wAACQCQD/AAAACQAP//CQDw/58P/////wAACQAA//AAAJCQAAD/AAAP+QDwn///8AAAAAAP//AACQAAAAD/AAAP8JCZn////wAAmZ///wAAAACZAJD/AACfCQmZkJmf/wAAAP//8AAAAJkAkAD/AACfAJCQmZCf/wkJD//wAAAAAAAJAP//8ADwCQCZmZmf/wAA//8AAAAAAACQkAAJAJAACQ//mZmf/wAA//CQAAAAAAkAmQAACQAACQn//////5mf//////AAAAkACQAACQAACQn/////8AkP//////AAAAAJkACQAJAJkJAJ////AJmf//////AAAAmQAAAAmZCZmZmZkJmZkJAJCZAAAAAAAAAAAACZCZmZmZmQCZmQmQAJAAAAAAAAAACQkAAJmZmZmZmZmZmZkJmZkAAAAAAAAAAJkJCZkJmZmZmZmQkJCZmQAAAAAAAAAAAAAJAACZmZmZmZmQmZkJmZAAAAAAAAAAAACQkJmZmZmZmZmZmZCZkAAAAAAAAAAAAAAAAAkJmZmZmZkJmQkJkAAAAAAAAAAAAAAJmQmZCZmQCZmZmZAAAAAAAAAAAAAAAAAACQmZmZmZAJkJkJAAAAAAAAAAAAAAAAAAAJmQmZkJkACQkAAAAAAAAAAAAAAAAAAAAAAACZmZmQmZkAAAAAAAAAAAAAAAAAAAAAAAAJkAAJkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
After Width: | Height: | Size: 391 B |
|
|
@ -0,0 +1,16 @@
|
|||
{ "id": "3dclock",
|
||||
"name": "3D Clock",
|
||||
"shortName":"3DClock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "This is a simple 3D scalig demo based on Anton Clock",
|
||||
"screenshots" : [ { "url":"screenshot.png" }],
|
||||
"type":"clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"3dclock.app.js","url":"app.js"},
|
||||
{"name":"3dclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
|
|
@ -14,5 +14,6 @@
|
|||
{"name":"90sclk.app.js","url":"app.js"},
|
||||
{"name":"90sclk.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"90sclk.settings.js","url":"settings.js"}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"90sclk.setting.json"}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Increased Legibility, GUI rework
|
||||
0.03: 13 new chords
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Uke Chords
|
||||
|
||||
An app that simply describes finger placements on a Ukulele to form common chords.
|
||||
|
||||
## Usage
|
||||
|
||||
Select a chord to view.
|
||||
Use the button to return to the chord selection menu.
|
||||
|
||||
## Creator
|
||||
|
||||
NovaDawn999
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgtqiAXWiMRDKsBolBCqcQilEoQwTiMUoMkkJGUiQwUFwVCGCcUoVEkJ5SgJ2CAQMROyIsBoVDoIXQgMSiJ2EPB4uBdwMieCMBCoIZCDoJdQAAMSUYUBLqIXBIhxCBCAJdDIZwPBTgIAEFxrOCAAIuTCwVELoQuToIuRgIuDCoUiFxjNCFwq7BC5YWBFoZdDAQIXLCwpdEogXKLYgWBXYZ9BC5SKDCwQYCkIHBC5IuFFQIYBiQhCC5JdFCoIYBBIYXJIwlEFwUUBIYXOLgIYDA4ReJC4i4BI4RODOxj/CAQIyBFwSOMoIYCagQ4BCxQXEigrBiS7CLpRHGAIMiMwYXMQoYwCSogXKU4gwCC6gwCC6ApEUoIFDRxR4Fd4QXReAgcEC5hIFLyAwJFxwwIiIWODATbDCyIYCAAQWSACY"))
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
const stringInterval = 30;
|
||||
const stringLength = 131;
|
||||
const fretHeight = 35;
|
||||
const fingerOffset = 17;
|
||||
const x = 44;
|
||||
const y = 32;
|
||||
|
||||
//chords
|
||||
const cc = [
|
||||
"C",
|
||||
"x",
|
||||
"x",
|
||||
"x",
|
||||
"33"
|
||||
];
|
||||
|
||||
const dd = [
|
||||
"D",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"x"
|
||||
];
|
||||
|
||||
var ee = [
|
||||
"E",
|
||||
"33",
|
||||
"32",
|
||||
"34",
|
||||
"11"
|
||||
];
|
||||
|
||||
const ff = [
|
||||
"F",
|
||||
"22",
|
||||
"x",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
const gg = [
|
||||
"G",
|
||||
"x",
|
||||
"21",
|
||||
"33",
|
||||
"22",
|
||||
];
|
||||
|
||||
const aa = [
|
||||
"A",
|
||||
"22",
|
||||
"11",
|
||||
"x",
|
||||
"x"
|
||||
];
|
||||
|
||||
const bb = [
|
||||
"B",
|
||||
"42",
|
||||
"43",
|
||||
"44",
|
||||
"21"
|
||||
];
|
||||
|
||||
const cm = [
|
||||
"Cm",
|
||||
"11",
|
||||
"x",
|
||||
"12",
|
||||
"34"
|
||||
];
|
||||
|
||||
const dm = [
|
||||
"Dm",
|
||||
"x",
|
||||
"22",
|
||||
"33",
|
||||
"11"
|
||||
];
|
||||
|
||||
const em = [
|
||||
"Em",
|
||||
"x",
|
||||
"43",
|
||||
"32",
|
||||
"21"
|
||||
];
|
||||
|
||||
const fm = [
|
||||
"Fm",
|
||||
"33",
|
||||
"11",
|
||||
"11",
|
||||
"11"
|
||||
];
|
||||
|
||||
const gm = [
|
||||
"Gm",
|
||||
"x",
|
||||
"22",
|
||||
"33",
|
||||
"11"
|
||||
];
|
||||
|
||||
const am = [
|
||||
"Am",
|
||||
"22",
|
||||
"23",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
const bm = [
|
||||
"Bm",
|
||||
"x",
|
||||
"43",
|
||||
"32",
|
||||
"21"
|
||||
];
|
||||
|
||||
const c7 = [
|
||||
"C7",
|
||||
"22",
|
||||
"33",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
const d7 = [
|
||||
"D7",
|
||||
"x",
|
||||
"22",
|
||||
"11",
|
||||
"23"
|
||||
];
|
||||
|
||||
const e7 = [
|
||||
"E7",
|
||||
"x",
|
||||
"11",
|
||||
"x",
|
||||
"x"
|
||||
];
|
||||
|
||||
const f7 = [
|
||||
"F7",
|
||||
"11",
|
||||
"22",
|
||||
"11",
|
||||
"11"
|
||||
];
|
||||
|
||||
const g7 = [
|
||||
"G7",
|
||||
"x",
|
||||
"x",
|
||||
"x",
|
||||
"11"
|
||||
];
|
||||
|
||||
const a7 = [
|
||||
"A7",
|
||||
"21",
|
||||
"21",
|
||||
"21",
|
||||
"32"
|
||||
];
|
||||
|
||||
const b7 = [
|
||||
"B7",
|
||||
"11",
|
||||
"22",
|
||||
"x",
|
||||
"23"
|
||||
];
|
||||
|
||||
|
||||
|
||||
var index = 0;
|
||||
var chords = [];
|
||||
var menu = {
|
||||
"" : { "title" : "Uke Chords" },
|
||||
"C" : function() { draw(cc); },
|
||||
"D" : function() { draw(dd); },
|
||||
"E" : function() { draw(ee); },
|
||||
"F" : function() { draw(ff); },
|
||||
"G" : function() { draw(gg); },
|
||||
"A" : function() { draw(aa); },
|
||||
"B" : function() { draw(bb); },
|
||||
"C7" : function() { draw(c7); },
|
||||
"D7" : function() { draw(d7); },
|
||||
"E7" : function() { draw(e7); },
|
||||
"F7" : function() { draw(f7); },
|
||||
"G7" : function() { draw(g7); },
|
||||
"A7" : function() { draw(a7); },
|
||||
"B7" : function() { draw(b7); },
|
||||
"Cm" : function() { draw(cm); },
|
||||
"Dm" : function() { draw(dm); },
|
||||
"Em" : function() { draw(em); },
|
||||
"Fm" : function() { draw(fm); },
|
||||
"Gm" : function() { draw(gm); },
|
||||
"Am" : function() { draw(am); },
|
||||
"Bm" : function() { draw(bm); },
|
||||
"About" : function() {
|
||||
E.showMessage(
|
||||
"Created By:\nNovaDawn999", {
|
||||
title:"About"
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
function drawBase() {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
g.drawLine(x + i * stringInterval, y, x + i * stringInterval, y + stringLength);
|
||||
g.fillRect(x- 1, y + i * fretHeight - 1, x + stringInterval * 3 + 1, y + i * fretHeight + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawChord(chord) {
|
||||
g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16);
|
||||
for (let i = 0; i < chord.length; i++) {
|
||||
if (i === 0 || chord[i][0] === "x") {
|
||||
continue;
|
||||
}
|
||||
if (chord[i][0] === "0") {
|
||||
g.drawString(chord[i][1], x + (i - 1) * stringInterval - 5, y + fretHeight * chord[i][0] + 2, true);
|
||||
g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 10);
|
||||
}
|
||||
else {
|
||||
g.drawString(chord[i][1], x + (i - 1) * stringInterval -5, y -fingerOffset + fretHeight * chord[i][0] + 2, true);
|
||||
g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buttonPress() {
|
||||
setWatch(() => {
|
||||
buttonPress();
|
||||
}, BTN);
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function draw(chord) {
|
||||
g.clear();
|
||||
drawBase();
|
||||
drawChord(chord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function main() {
|
||||
E.showMenu(menu);
|
||||
setWatch(() => {
|
||||
buttonPress();
|
||||
}, BTN);
|
||||
}
|
||||
|
||||
main();
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "Uke",
|
||||
"name": "Uke Chords",
|
||||
"shortName":"Uke",
|
||||
"version":"0.03",
|
||||
"description": "Wrist mounted ukulele chords",
|
||||
"icon": "app.png",
|
||||
"tags": "uke, chords",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"Uke.app.js","url":"app.js"},
|
||||
{"name":"Uke.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
# App Name
|
||||
|
||||
More information on making apps:
|
||||
|
||||
* http://www.espruino.com/Bangle.js+First+App
|
||||
* http://www.espruino.com/Bangle.js+App+Loader
|
||||
|
||||
Describe the app...
|
||||
|
||||
Add screen shots (if possible) to the app folder and link then into this file with 
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New Widget!
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Clock Info Name
|
||||
|
||||
More info on making Clock Infos and what they are: http://www.espruino.com/Bangle.js+Clock+Info
|
||||
|
||||
Describe the clock info...
|
||||
|
||||
Add screen shots (if possible) to the app folder and link then into this file with 
|
||||
|
||||
## Usage
|
||||
|
||||
Describe how to use it
|
||||
|
||||
## Features
|
||||
|
||||
Name the function
|
||||
|
||||
## Controls
|
||||
|
||||
Name the buttons and what they are used for
|
||||
|
||||
## Requests
|
||||
|
||||
Name who should be contacted for support/update requests
|
||||
|
||||
## Creator
|
||||
|
||||
Your name
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
(function() {
|
||||
return {
|
||||
name: "Bangle",
|
||||
// img: 24x24px image for this list of items. The default "Bangle" list has its own image so this is not needed
|
||||
items: [
|
||||
{ name : "Item1",
|
||||
get : function() { return { text : "TextOfItem1",
|
||||
// v : 10, min : 0, max : 100, - optional
|
||||
img : atob("GBiBAAAAAAAAAAAYAAD/AAOBwAYAYAwAMAgAEBgAGBAACBCBCDHDjDCBDBAACBAACBhCGAh+EAwYMAYAYAOBwAD/AAAYAAAAAAAAAA==") }},
|
||||
show : function() {},
|
||||
hide : function() {},
|
||||
// run : function() {} optional (called when tapped)
|
||||
}
|
||||
]
|
||||
};
|
||||
}) // must not have a semi-colon!
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My clock info's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my clock info",
|
||||
"icon": "icon.png",
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
# Widget Name
|
||||
|
||||
More info on making Widgets and what they are: http://www.espruino.com/Bangle.js+Widgets
|
||||
|
||||
Describe the app...
|
||||
|
||||
Add screen shots (if possible) to the app folder and link then into this file with 
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
"shortName":"Short Name",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great widget",
|
||||
"icon": "widget.png",
|
||||
"icon": "icon.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
/* run widgets in their own function scope so they don't interfere with
|
||||
currently-running apps */
|
||||
/* run widgets in their own function scope if they need to define local
|
||||
variables so they don't interfere with currently-running apps */
|
||||
(() => {
|
||||
function draw() {
|
||||
g.reset(); // reset the graphics context to defaults (color/font/etc)
|
||||
// add your code
|
||||
g.drawString("X", this.x, this.y);
|
||||
}
|
||||
|
||||
// add your widget
|
||||
WIDGETS["mywidget"]={
|
||||
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right), be aware that not all apps support widgets at the bottom of the screen
|
||||
width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||
draw:draw // called to draw the widget
|
||||
draw:function() {
|
||||
g.reset(); // reset the graphics context to defaults (color/font/etc)
|
||||
// add your code
|
||||
g.drawString("X", this.x, this.y);
|
||||
} // called to draw the widget
|
||||
};
|
||||
})()
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Exit as first menu option, dont show decimal places for seconds
|
||||
0.04: Localisation, change Exit->Back to allow back-arrow to appear on 2v13 firmware
|
||||
0.05: Add max G values during recording, record actual G values and magnitude to CSV
|
||||
0.06: Convert Yes/No On/Off in settings to checkboxes
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ function showMenu() {
|
|||
viewLogs();
|
||||
},
|
||||
/*LANG*/"Log raw data" : {
|
||||
value : logRawData,
|
||||
format : v => v?/*LANG*/"Yes":/*LANG*/"No",
|
||||
value : !!logRawData,
|
||||
onchange : v => { logRawData=v; }
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "accellog",
|
||||
"name": "Acceleration Logger",
|
||||
"shortName": "Accel Log",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoor",
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Increase record time to 5 second
|
||||
Calculate the time moving in graph display
|
||||
Trigger on 1.04g now, and record 10 samples before trigger
|
||||
0.03: Bangle.js 2 compatibility
|
||||
|
|
@ -102,7 +102,7 @@ function showData() {
|
|||
g.drawString("FINISH",g.getWidth()-4,g.getHeight()/2);
|
||||
setWatch(function() {
|
||||
showMenu();
|
||||
}, BTN2);
|
||||
}, global.BTN2?BTN2:BTN);
|
||||
}
|
||||
|
||||
function showBig(txt) {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
"id": "accelrec",
|
||||
"name": "Acceleration Recorder",
|
||||
"shortName": "Accel Rec",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
|
|
|
|||
|
|
@ -14,5 +14,6 @@
|
|||
{"name":"activepedom.settings.js","url":"settings.js"},
|
||||
{"name":"activepedom.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"activepedom.app.js","url":"app.js"}
|
||||
]
|
||||
],
|
||||
"data":[{"name":"activepedom.settings.json"}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Use default Bangle formatter for booleans
|
||||
0.09: New app screen (instead of showing settings or the alert) and some optimisations
|
||||
0.10: Add software back button via setUI
|
||||
0.11: Add setting to unlock screen
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@
|
|||
if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
|
||||
Bangle.buzz(400);
|
||||
}
|
||||
|
||||
if ((storage.readJSON('activityreminder.s.json', 1) || {}).unlock) {
|
||||
Bangle.setLocked(false);
|
||||
Bangle.setLCDPower(1);
|
||||
}
|
||||
|
||||
setTimeout(load, 20000);
|
||||
}
|
||||
|
||||
|
|
@ -34,4 +40,4 @@
|
|||
Bangle.drawWidgets();
|
||||
run();
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Activity Reminder",
|
||||
"shortName":"Activity Reminder",
|
||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||
"version":"0.10",
|
||||
"version":"0.11",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "tool,activity",
|
||||
|
|
|
|||
|
|
@ -75,7 +75,14 @@
|
|||
settings.tempThreshold = v;
|
||||
activityreminder.writeSettings(settings);
|
||||
}
|
||||
}
|
||||
},
|
||||
'Unlock on alarm': {
|
||||
value: !!settings.unlock,
|
||||
onchange: v => {
|
||||
settings.unlock = v;
|
||||
activityreminder.writeSettings(settings);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return mainMenu;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
0.01: AdvCasio first version
|
||||
0.02: Remove un-needed fonts to improve memory usage
|
||||
0.03: Tell clock widgets to hide.
|
||||
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||
0.05: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
|
||||
|
|
@ -122,7 +122,7 @@ function draw() {
|
|||
g.setFontAlign(0,-1);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString(getTemperature(), 155, 132);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm), 109, 98);
|
||||
g.drawString(getSteps(), 158, 98);
|
||||
|
||||
g.setFontAlign(-1,-1);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "advcasio",
|
||||
"name": "Advanced Casio Clock",
|
||||
"shortName":"advcasio",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
|
||||
"icon": "app.png",
|
||||
"tags": "clock",
|
||||
|
|
|
|||
|
|
@ -12,3 +12,5 @@
|
|||
0.11: Setting to use "Today" and "Yesterday" instead of dates
|
||||
Added dynamic, short and range fields to clkinfo
|
||||
0.12: Added color field and updating clkinfo periodically (running events)
|
||||
0.13: Show day of the week in date
|
||||
0.14: Fixed "Today" and "Yesterday" wrongly displayed for allDay events on some time zones
|
||||
|
|
|
|||
|
|
@ -34,19 +34,19 @@ function getDate(timestamp) {
|
|||
return new Date(timestamp*1000);
|
||||
}
|
||||
function formatDay(date) {
|
||||
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
|
||||
if (!settings.useToday) {
|
||||
return Locale.date(date);
|
||||
return formattedDate;
|
||||
}
|
||||
const dateformatted = date.toISOString().split('T')[0]; // yyyy-mm-dd
|
||||
const today = new Date(Date.now()).toISOString().split('T')[0]; // yyyy-mm-dd
|
||||
if (dateformatted == today) {
|
||||
const today = new Date(Date.now());
|
||||
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
|
||||
return /*LANG*/"Today ";
|
||||
} else {
|
||||
const tomorrow = new Date(Date.now() + 86400 * 1000).toISOString().split('T')[0]; // yyyy-mm-dd
|
||||
if (dateformatted == tomorrow) {
|
||||
else {
|
||||
const tomorrow = new Date(Date.now() + 86400 * 1000);
|
||||
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
|
||||
return /*LANG*/"Tomorrow ";
|
||||
}
|
||||
return Locale.date(date);
|
||||
return formattedDate;
|
||||
}
|
||||
}
|
||||
function formatDateLong(date, includeDay, allDay) {
|
||||
|
|
@ -58,7 +58,7 @@ function formatDateLong(date, includeDay, allDay) {
|
|||
return shortTime;
|
||||
}
|
||||
function formatDateShort(date, allDay) {
|
||||
return formatDay(date).replace(/\d\d\d\d/,"")+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
|
||||
return formatDay(date)+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
|
||||
}
|
||||
|
||||
var lines = [];
|
||||
|
|
@ -75,25 +75,29 @@ function showEvent(ev) {
|
|||
if (titleCnt) lines.push(""); // add blank line after title
|
||||
if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
|
||||
includeDay = false;
|
||||
if(includeDay || ev.allDay) {
|
||||
if(includeDay && ev.allDay) {
|
||||
//single day all day (average to avoid getting previous day)
|
||||
lines = lines.concat(
|
||||
/*LANG*/"Start:",
|
||||
g.wrapString(formatDateLong(new Date((start+end)/2), includeDay, ev.allDay), g.getWidth()-10));
|
||||
} else if(includeDay || ev.allDay) {
|
||||
lines = lines.concat(
|
||||
/*LANG*/"Start"+":",
|
||||
g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10),
|
||||
/*LANG*/"End:",
|
||||
/*LANG*/"End"+":",
|
||||
g.wrapString(formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10));
|
||||
} else {
|
||||
lines = lines.concat(
|
||||
g.wrapString(Locale.date(start), g.getWidth()-10),
|
||||
g.wrapString(formatDateShort(start,true), g.getWidth()-10),
|
||||
g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10),
|
||||
g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10));
|
||||
}
|
||||
if(ev.location)
|
||||
lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10));
|
||||
if(ev.description)
|
||||
lines = lines.concat("",/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10));
|
||||
if(ev.description && ev.description.trim())
|
||||
lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10));
|
||||
if(ev.calName)
|
||||
lines = lines.concat(/*LANG*/"Calendar"+": ", g.wrapString(ev.calName, g.getWidth()-10));
|
||||
lines = lines.concat(["",/*LANG*/"< Back"]);
|
||||
lines = lines.concat("",/*LANG*/"Calendar"+": ", g.wrapString(ev.calName, g.getWidth()-10));
|
||||
lines = lines.concat("",/*LANG*/"< Back");
|
||||
E.showScroller({
|
||||
h : g.getFontHeight(), // height of each menu item in pixels
|
||||
c : lines.length, // number of menu items
|
||||
|
|
@ -120,7 +124,7 @@ function showList() {
|
|||
CALENDAR = CALENDAR.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000);
|
||||
}
|
||||
if(CALENDAR.length == 0) {
|
||||
E.showMessage("No events");
|
||||
E.showMessage(/*LANG*/"No events");
|
||||
return;
|
||||
}
|
||||
E.showScroller({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.12",
|
||||
"version": "0.14",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
|||
|
|
@ -4,4 +4,6 @@
|
|||
0.04: Use widget_utils module.
|
||||
0.05: Support for clkinfo.
|
||||
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||
0.07: Use clock_info.addInteractive instead of a custom implementation
|
||||
0.07: Use clock_info.addInteractive instead of a custom implementation
|
||||
0.08: Use clock_info module as an app
|
||||
0.09: clock_info now uses app name to maintain settings specifically for this clock face
|
||||
|
|
@ -193,6 +193,7 @@ function queueDraw() {
|
|||
*/
|
||||
let clockInfoItems = clock_info.load();
|
||||
let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
||||
app : "aiclock",
|
||||
x : 0,
|
||||
y: 0,
|
||||
w: W,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
"name": "AI Clock",
|
||||
"shortName":"AI Clock",
|
||||
"icon": "aiclock.png",
|
||||
"version":"0.07",
|
||||
"version":"0.09",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"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"},
|
||||
|
|
|
|||
|
|
@ -38,3 +38,9 @@
|
|||
0.35: Add automatic translation of more strings
|
||||
0.36: alarm widget moved out of app
|
||||
0.37: add message input and dated Events
|
||||
0.38: Display date in locale
|
||||
When switching 'repeat' from 'Workdays', 'Weekends' to 'Custom' preset Custom menu with previous selection
|
||||
Display alarm label in delete prompt
|
||||
0.39: Dated event repeat option
|
||||
0.40: Use substring of message when it's longer than fits the designated menu entry.
|
||||
0.41: Fix a menu bug affecting alarms with empty messages.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
|
|||
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
|
||||
- `New Timer` → Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
|
||||
- `New Event` → Configure a new event (triggered based on time and date)
|
||||
- `Repeat` → Alarm can be be fired only once or repeated (every X number of _days_, _weeks_, _months_ or _years_)
|
||||
- `Advanced`
|
||||
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
|
||||
- `Enable All` → Enable _all_ disabled alarms & timers
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ Bangle.drawWidgets();
|
|||
|
||||
// 0 = Sunday (default), 1 = Monday
|
||||
const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
|
||||
const WORKDAYS = 62
|
||||
const WORKDAYS = 62;
|
||||
const WEEKEND = firstDayOfWeek ? 192 : 65;
|
||||
const EVERY_DAY = firstDayOfWeek ? 254 : 127;
|
||||
const INTERVALS = ["day", "week", "month", "year"];
|
||||
const INTERVAL_LABELS = [/*LANG*/"Day", /*LANG*/"Week", /*LANG*/"Month", /*LANG*/"Year"];
|
||||
|
||||
const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA==");
|
||||
const iconAlarmOff = "\0" + (g.theme.dark
|
||||
|
|
@ -40,6 +42,30 @@ function handleFirstDayOfWeek(dow) {
|
|||
// Check the first day of week and update the dow field accordingly (alarms only!)
|
||||
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||
|
||||
function getLabel(e) {
|
||||
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
|
||||
return (e.timer
|
||||
? require("time_utils").formatDuration(e.timer)
|
||||
: (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
|
||||
) + (e.msg ? ` ${e.msg}` : "");
|
||||
}
|
||||
|
||||
function trimLabel(label, maxLength) {
|
||||
return (label.length > maxLength
|
||||
? label.substring(0,maxLength-3) + "..."
|
||||
: label.substring(0,maxLength));
|
||||
}
|
||||
|
||||
function formatAlarmMessage(msg) {
|
||||
if (msg == null) {
|
||||
return msg;
|
||||
} else if (msg.length > 7) {
|
||||
return msg.substring(0,6)+"...";
|
||||
} else {
|
||||
return msg.substring(0,7);
|
||||
}
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
const menu = {
|
||||
"": { "title": /*LANG*/"Alarms & Timers" },
|
||||
|
|
@ -48,11 +74,7 @@ function showMainMenu() {
|
|||
};
|
||||
|
||||
alarms.forEach((e, index) => {
|
||||
var label = (e.timer
|
||||
? require("time_utils").formatDuration(e.timer)
|
||||
: (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
|
||||
) + (e.msg ? " " + e.msg : "");
|
||||
menu[label] = {
|
||||
menu[trimLabel(getLabel(e),40)] = {
|
||||
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||
};
|
||||
|
|
@ -133,6 +155,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
},
|
||||
/*LANG*/"Message": {
|
||||
value: alarm.msg,
|
||||
format: formatAlarmMessage,
|
||||
onchange: () => {
|
||||
setTimeout(() => {
|
||||
keyboard.input({text:alarm.msg}).then(result => {
|
||||
|
|
@ -148,8 +171,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
onchange: v => alarm.on = v
|
||||
},
|
||||
/*LANG*/"Repeat": {
|
||||
value: decodeDOW(alarm),
|
||||
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
|
||||
value: decodeRepeat(alarm),
|
||||
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, date || alarm.dow, (repeat, dow) => {
|
||||
alarm.rp = repeat;
|
||||
alarm.dow = dow;
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||
|
|
@ -174,9 +197,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
};
|
||||
|
||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||
if (alarm.date || withDate) {
|
||||
delete menu[/*LANG*/"Repeat"];
|
||||
} else {
|
||||
if (!alarm.date) {
|
||||
delete menu[/*LANG*/"Day"];
|
||||
delete menu[/*LANG*/"Month"];
|
||||
delete menu[/*LANG*/"Year"];
|
||||
|
|
@ -184,7 +205,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
|
||||
if (!isNew) {
|
||||
menu[/*LANG*/"Delete"] = () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
||||
E.showPrompt(getLabel(alarm) + "\n" + /*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
||||
if (confirm) {
|
||||
alarms.splice(alarmIndex, 1);
|
||||
saveAndReload();
|
||||
|
|
@ -225,49 +246,77 @@ function saveAndReload() {
|
|||
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||
}
|
||||
|
||||
function decodeDOW(alarm) {
|
||||
function decodeRepeat(alarm) {
|
||||
return alarm.rp
|
||||
? require("date_utils")
|
||||
.dows(firstDayOfWeek, 2)
|
||||
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
||||
.join("")
|
||||
.toLowerCase()
|
||||
: /*LANG*/"Once"
|
||||
? (alarm.date
|
||||
? `${alarm.rp.num}*${INTERVAL_LABELS[INTERVALS.indexOf(alarm.rp.interval)]}`
|
||||
: require("date_utils")
|
||||
.dows(firstDayOfWeek, 2)
|
||||
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
||||
.join("")
|
||||
.toLowerCase())
|
||||
: /*LANG*/"Once";
|
||||
}
|
||||
|
||||
function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
||||
function showEditRepeatMenu(repeat, day, dowChangeCallback) {
|
||||
var originalRepeat = repeat;
|
||||
var originalDow = dow;
|
||||
var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
|
||||
var dow;
|
||||
|
||||
const menu = {
|
||||
"": { "title": /*LANG*/"Repeat Alarm" },
|
||||
"< Back": () => dowChangeCallback(repeat, dow),
|
||||
/*LANG*/"Once": {
|
||||
/*LANG*/"Only Once": () => dowChangeCallback(false, EVERY_DAY)
|
||||
// The alarm will fire once. Internally it will be saved
|
||||
// as "fire every days" BUT the repeat flag is false so
|
||||
// we avoid messing up with the scheduler.
|
||||
value: !repeat,
|
||||
onchange: () => dowChangeCallback(false, EVERY_DAY)
|
||||
},
|
||||
/*LANG*/"Workdays": {
|
||||
value: repeat && dow == WORKDAYS,
|
||||
onchange: () => dowChangeCallback(true, WORKDAYS)
|
||||
},
|
||||
/*LANG*/"Weekends": {
|
||||
value: repeat && dow == WEEKEND,
|
||||
onchange: () => dowChangeCallback(true, WEEKEND)
|
||||
},
|
||||
/*LANG*/"Every Day": {
|
||||
value: repeat && dow == EVERY_DAY,
|
||||
onchange: () => dowChangeCallback(true, EVERY_DAY)
|
||||
},
|
||||
/*LANG*/"Custom": {
|
||||
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
|
||||
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
|
||||
}
|
||||
};
|
||||
|
||||
let restOfMenu;
|
||||
if (typeof day === "number") {
|
||||
dow = day;
|
||||
var originalDow = dow;
|
||||
var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
|
||||
|
||||
restOfMenu = {
|
||||
/*LANG*/"Workdays": {
|
||||
value: repeat && dow == WORKDAYS,
|
||||
onchange: () => dowChangeCallback(true, WORKDAYS)
|
||||
},
|
||||
/*LANG*/"Weekends": {
|
||||
value: repeat && dow == WEEKEND,
|
||||
onchange: () => dowChangeCallback(true, WEEKEND)
|
||||
},
|
||||
/*LANG*/"Every Day": {
|
||||
value: repeat && dow == EVERY_DAY,
|
||||
onchange: () => dowChangeCallback(true, EVERY_DAY)
|
||||
},
|
||||
/*LANG*/"Custom": {
|
||||
value: isCustom ? decodeRepeat({ rp: true, dow: dow }) : false,
|
||||
onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, originalRepeat, originalDow)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// var date = day; // eventually: detect day of date and configure a repeat e.g. 3rd Monday of Month
|
||||
dow = EVERY_DAY;
|
||||
repeat = repeat || {interval: "month", num: 1};
|
||||
|
||||
restOfMenu = {
|
||||
/*LANG*/"Every": {
|
||||
value: repeat.num,
|
||||
min: 1,
|
||||
onchange: v => repeat.num = v
|
||||
},
|
||||
/*LANG*/"Interval": {
|
||||
value: INTERVALS.indexOf(repeat.interval),
|
||||
format: v => INTERVAL_LABELS[v],
|
||||
min: 0,
|
||||
max: INTERVALS.length - 1,
|
||||
onchange: v => repeat.interval = INTERVALS[v]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Object.assign(menu, restOfMenu);
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +327,7 @@ function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow)
|
|||
// If the user unchecks all the days then we assume repeat = once
|
||||
// and we force the dow to every day.
|
||||
var repeat = dow > 0;
|
||||
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
|
||||
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -289,7 +338,7 @@ function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow)
|
|||
};
|
||||
});
|
||||
|
||||
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
|
||||
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback);
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
|
@ -338,6 +387,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
},
|
||||
/*LANG*/"Message": {
|
||||
value: timer.msg,
|
||||
format: formatAlarmMessage,
|
||||
onchange: () => {
|
||||
setTimeout(() => {
|
||||
keyboard.input({text:timer.msg}).then(result => {
|
||||
|
|
@ -372,14 +422,14 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||
if (!isNew) {
|
||||
menu[/*LANG*/"Delete"] = () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
||||
E.showPrompt(getLabel(timer) + "\n" + /*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
||||
if (confirm) {
|
||||
alarms.splice(timerIndex, 1);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
} else {
|
||||
timer.timer = require("time_utils").encodeTime(time);
|
||||
setTimeout(showEditTimerMenu, 10, timer, timerIndex)
|
||||
setTimeout(showEditTimerMenu, 10, timer, timerIndex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -426,9 +476,9 @@ function enableAll(on) {
|
|||
alarm.on = on;
|
||||
if (on) {
|
||||
if (alarm.timer) {
|
||||
prepareTimerForSave(alarm, i, require("time_utils").decodeTime(alarm.timer))
|
||||
prepareTimerForSave(alarm, i, require("time_utils").decodeTime(alarm.timer));
|
||||
} else {
|
||||
prepareAlarmForSave(alarm, i, require("time_utils").decodeTime(alarm.t))
|
||||
prepareAlarmForSave(alarm, i, require("time_utils").decodeTime(alarm.t));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.37",
|
||||
"version": "0.41",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Actually upload correct code
|
||||
0.03: Display sea-level pressure, too, and allow calibration
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Bangle.setBarometerPower(true, "app");
|
||||
Bangle.setBarometerPower(true, "altimeter");
|
||||
|
||||
g.clear(1);
|
||||
Bangle.loadWidgets();
|
||||
|
|
@ -10,21 +10,62 @@ var MEDIANLENGTH = 20;
|
|||
var avr = [], median;
|
||||
var value = 0;
|
||||
|
||||
function getStandardPressure(altitude) {
|
||||
const P0 = 1013.25; // standard pressure at sea level in hPa
|
||||
const T0 = 288.15; // standard temperature at sea level in K
|
||||
const g0 = 9.80665; // standard gravitational acceleration in m/s^2
|
||||
const R = 8.31432; // gas constant in J/(mol*K)
|
||||
const M = 0.0289644; // molar mass of air in kg/mol
|
||||
const L = -0.0065; // temperature lapse rate in K/m
|
||||
|
||||
const temperature = T0 + L * altitude; // temperature at the given altitude
|
||||
const pressure = P0 * Math.pow((temperature / T0), (-g0 * M) / (R * L)); // pressure at the given altitude
|
||||
|
||||
return pressure;
|
||||
}
|
||||
|
||||
function convertToSeaLevelPressure(pressure, altitude) {
|
||||
return 1013.25 * (pressure / getStandardPressure(altitude));
|
||||
}
|
||||
|
||||
Bangle.on('pressure', function(e) {
|
||||
while (avr.length>MEDIANLENGTH) avr.pop();
|
||||
avr.unshift(e.altitude);
|
||||
median = avr.slice().sort();
|
||||
g.reset().clearRect(0,y-30,g.getWidth()-10,y+30);
|
||||
g.reset().clearRect(0,y-30,g.getWidth()-10,R.h);
|
||||
if (median.length>10) {
|
||||
var mid = median.length>>1;
|
||||
value = E.sum(median.slice(mid-4,mid+5)) / 9;
|
||||
g.setFont("Vector",50).setFontAlign(0,0).drawString((value-zero).toFixed(1), g.getWidth()/2, y);
|
||||
t = value-zero;
|
||||
if ((t > -100) && (t < 1000))
|
||||
t = t.toFixed(1);
|
||||
else
|
||||
t = t.toFixed(0);
|
||||
g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y);
|
||||
sea = convertToSeaLevelPressure(e.pressure, value-zero);
|
||||
t = sea.toFixed(1) + " " + e.temperature.toFixed(1);
|
||||
if (0) {
|
||||
print("alt raw:", value.toFixed(1));
|
||||
print("temperature:", e.temperature);
|
||||
print("pressure:", e.pressure);
|
||||
print("sea pressure:", sea);
|
||||
print("std pressure:", getStandardPressure(value-zero));
|
||||
}
|
||||
g.setFont("Vector",25).setFontAlign(-1,0).drawString(t,
|
||||
10, R.y+R.h - 35);
|
||||
}
|
||||
});
|
||||
|
||||
print(g.getFonts());
|
||||
g.reset();
|
||||
g.setFont("6x8").setFontAlign(0,0).drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
|
||||
g.setFont("Vector:15");
|
||||
g.setFontAlign(0,0);
|
||||
g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
|
||||
g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62);
|
||||
g.flip();
|
||||
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2);
|
||||
setWatch(function() {
|
||||
zero = value;
|
||||
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
|
||||
Bangle.setUI("updown", btn=> {
|
||||
if (!btn) zero=value;
|
||||
if (btn<0) zero-=5;
|
||||
if (btn>0) zero+=5;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "altimeter",
|
||||
"name": "Altimeter",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: first release
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# Half-Life Alyx Style clock
|
||||
|
||||

|
||||
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
const icoH = [
|
||||
[0,1,1,0,0,1,1,0],
|
||||
[1,1,1,1,1,1,1,1],
|
||||
[1,1,1,1,1,1,1,1],
|
||||
[1,1,1,1,1,1,1,1],
|
||||
[0,1,1,1,1,1,1,0],
|
||||
[0,0,1,1,1,1,0,0],
|
||||
[0,0,0,1,1,0,0,0],
|
||||
[0,0,0,0,0,0,0,0],
|
||||
]
|
||||
|
||||
const icoR = [
|
||||
[0,0,0,0,1,1,1,1,0,0,0,0],
|
||||
[0,0,1,1,0,0,0,0,1,1,0,0],
|
||||
[0,1,1,1,1,0,0,1,1,0,1,0],
|
||||
[0,1,1,0,0,0,0,0,0,0,1,0],
|
||||
[1,1,1,1,1,1,1,1,0,0,0,1],
|
||||
[1,1,0,0,1,0,0,0,0,0,0,1],
|
||||
[1,1,1,1,1,1,1,0,1,1,0,1],
|
||||
[1,1,1,1,1,1,0,0,0,0,1,1],
|
||||
[0,1,1,1,1,1,1,1,1,1,1,0],
|
||||
[0,1,1,1,1,1,1,1,1,1,1,0],
|
||||
[0,0,1,1,1,1,1,1,1,1,0,0],
|
||||
[0,0,0,0,1,1,1,1,0,0,0,0],
|
||||
]
|
||||
|
||||
let idTimeout = null;
|
||||
|
||||
function icon (icon, x, y, size, gap) {
|
||||
const color = g.getColor();
|
||||
for (let r=0; r<icon.length; r++) {
|
||||
for (let c=0; c<icon[r].length; c++) {
|
||||
if (icon[r][c]===1){
|
||||
g.setColor(color);
|
||||
g.fillRect(c * size + x, r * size + y, (c+1) * size - gap + x, (r+1)*size - gap + y);
|
||||
g.setColor('#fff');
|
||||
g.drawLine(c * size + x + size/2 - 1, r * size + y + size/2 - 1, c * size + x + size/2 - 1, r * size + y + size/2 - 1, )
|
||||
}
|
||||
}
|
||||
}
|
||||
g.setColor(color);
|
||||
}
|
||||
|
||||
function ohmA(x, y) {
|
||||
g.setColor('#666');
|
||||
g.fillRect(x, y, x+8, y+15);
|
||||
g.setColor('#00f');
|
||||
g.drawLine(x, y + 4, x + 8, y + 4);
|
||||
g.setColor('#f00');
|
||||
g.drawLine(x, y + 6, x + 8, y + 6);
|
||||
g.setColor('#0f0');
|
||||
g.drawLine(x, y + 8, x + 8, y + 8);
|
||||
}
|
||||
|
||||
function ohmB(x, y) {
|
||||
g.setColor('#666');
|
||||
g.fillRect(x, y, x+15, y+8);
|
||||
g.setColor('#00f');
|
||||
g.drawLine(x + 4, y + 8, x + 4, y);
|
||||
g.setColor('#f00');
|
||||
g.drawLine(x + 6, y + 8, x + 6, y);
|
||||
g.setColor('#0f0');
|
||||
g.drawLine(x + 8, y + 8, x + 8, y);
|
||||
}
|
||||
|
||||
function heart (x, y) {
|
||||
g.setColor('#000');
|
||||
g.fillRect(x-2, y-2, x + 32, y + 32)
|
||||
g.setColor('#666');
|
||||
g.drawRect(x-2, y-2, x + 32, y + 32)
|
||||
g.setColor('#f00');
|
||||
icon(icoH, x, y, 4, 2);
|
||||
}
|
||||
|
||||
function resin() {
|
||||
let d = Date();
|
||||
let h = d.getHours();
|
||||
let m = d.getMinutes();
|
||||
|
||||
const resinPosX = 25;
|
||||
const resinPosY = 130;
|
||||
g.setColor('#000');
|
||||
g.fillRect(resinPosX - 3, resinPosY - 3, Bangle.appRect.w - resinPosX + 2, resinPosY + 40);
|
||||
g.setColor('#666');
|
||||
g.drawRect(resinPosX - 3, resinPosY - 3, Bangle.appRect.w - resinPosX + 2, resinPosY + 40);
|
||||
g.setColor('#6ff');
|
||||
icon(icoR, resinPosX, resinPosY, 3, 1);
|
||||
g.setFont('6x8', 5);
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString('_' + (m<10?'0':'')+m, resinPosX + 40, resinPosY - 5);
|
||||
|
||||
g.setFontAlign(1, -1);
|
||||
g.setFont('6x8', 2);
|
||||
g.drawString(h, resinPosX + 66, resinPosY);
|
||||
}
|
||||
|
||||
function screw(x, y) {
|
||||
g.setColor('#666').fillCircle(x, y, 4).setColor('#000').drawLine(x - 4, y, x + 4, y)
|
||||
}
|
||||
|
||||
function led(x,y) {
|
||||
g.setColor('#0f0').fillCircle(x, y, 8).setColor('#fff').fillCircle(x-3, y-3, 3);
|
||||
}
|
||||
|
||||
|
||||
function drawTime() {
|
||||
const R = Bangle.appRect;
|
||||
g.setBgColor('#000');
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
|
||||
// pcb
|
||||
g.setColor('#030').fillRect(R.x, R.y, R.x2, R.y2);
|
||||
screw(R.x + 8, R.y + 8)
|
||||
screw(R.x2 - 8, R.y + 8)
|
||||
screw(R.x + 8, R.y2 - 8)
|
||||
screw(R.x2 - 8, R.y2 - 8)
|
||||
for(let i=0; i<6; i++) {
|
||||
g.setColor('#fff');
|
||||
g.drawLine(24 + i * 9, 70, 24 + i * 9, 110);
|
||||
g.drawLine(24 + i * 9, 110, 54 + i * 9, 140);
|
||||
}
|
||||
ohmA(29, 90);
|
||||
ohmA(56, 90);
|
||||
ohmB(80, 90);
|
||||
screw(90, 110)
|
||||
// led
|
||||
led(50, R.y+10);
|
||||
led(70, R.y+10);
|
||||
ohmB(20, R.y + 10);
|
||||
ohmB(90, R.y + 2);
|
||||
ohmB(90, R.y + 14);
|
||||
|
||||
heart(10, 52);
|
||||
heart(50, 52);
|
||||
heart(90, 52);
|
||||
|
||||
|
||||
|
||||
g.setColor('#666');
|
||||
for (let i=0; i<6; i++) {
|
||||
g.fillCircle(110 + i*10, 80+10, 3);
|
||||
g.fillCircle(110 + i*10, 110+10, 3);
|
||||
}
|
||||
g.setColor('#000');
|
||||
g.fillRect(110, 80+10, 170, 110+10);
|
||||
g.setColor('#666');
|
||||
g.drawRect(110, 80+10, 170, 110+10);
|
||||
g.setFont('6x8').setColor('#666').drawString('AH-118080\n0WT 18-001', 112, 85+10);
|
||||
|
||||
|
||||
resin();
|
||||
|
||||
let d = Date();
|
||||
let t = d.getSeconds()*1000 + d.getMilliseconds();
|
||||
idTimeout = setTimeout(drawTime, 60000 - t); // time till next minute
|
||||
}
|
||||
|
||||
// special function to handle display switch on
|
||||
Bangle.on('lcdPower', function(on){
|
||||
if (on) {
|
||||
drawTime();
|
||||
} else {
|
||||
if(idTimeout) {
|
||||
clearTimeout(idTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
drawTime();
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkAqoA/AHlUmYADC69FC601C600C60yC6x4SAANTPCgyFkowTSCgACkZ4TAAVDPCwvCPCaqEPCSnDPCZeDmc0Uyh4TqQXFPCBGEPCQWFPCClEPCSlDmjZDdiUlMYZ4NIwg0EPBpGEDoh4NIIYpCPCDqGMoUydh4oDPB5GGPCBGGG4h4CGQ6HDN4gIEqkjSY4+DBYreDSZINDHYpoDKYw9FTww5DC5BGJPAg8MQQw6DBpBGJWIsyCwtUkQABZhA7CkjYKfJIsGAB9UCqgA/ACQ"))
|
||||
|
After Width: | Height: | Size: 854 B |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "alyxclock",
|
||||
"name": "Alyx Clock",
|
||||
"version": "0.01",
|
||||
"description": "A clock in the style of half-life alyx gravity gloves",
|
||||
"icon": "alyxclock.png",
|
||||
"screenshots": [{"url":"screenshot_alyxclock.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"alyxclock.app.js","url":"alyxclock.app.js"},
|
||||
{"name":"alyxclock.img","url":"alyxclock.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -22,3 +22,9 @@
|
|||
0.21: Fix broken 'Messages' button in menu
|
||||
0.22: Handle connection events for GPS forwarding from phone
|
||||
0.23: Handle 'act' Gadgetbridge messages for realtime activity monitoring
|
||||
0.24: Handle new 'nav' event for navigation
|
||||
0.25: Added option to 'ignore' an app from the message
|
||||
0.26: Change handling of GPS status to depend on GPS events instead of connection events
|
||||
0.27: Issue newline before GB commands (solves issue with console.log and ignored commands)
|
||||
0.28: Navigation messages no longer launch the Maps view unless they're new
|
||||
0.29: Support for http request xpath return format
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
var lastMsg; // for music messages - may not be needed now...
|
||||
var actInterval; // Realtime activity reporting interval when `act` is true
|
||||
var actHRMHandler; // For Realtime activity reporting
|
||||
var gpsState = {}; // keep information on GPS via Gadgetbridge
|
||||
|
||||
// this settings var is deleted after this executes to save memory
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
|
|
@ -137,16 +138,38 @@
|
|||
},
|
||||
// {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
|
||||
"gps": function() {
|
||||
const settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
if (!settings.overwriteGps) return;
|
||||
// modify event for using it as Bangle GPS event
|
||||
delete event.t;
|
||||
event.satellites = NaN;
|
||||
if (!isFinite(event.satellites)) event.satellites = NaN;
|
||||
if (!isFinite(event.course)) event.course = NaN;
|
||||
event.fix = 1;
|
||||
if (event.long!==undefined) { // for earlier Gadgetbridge implementations
|
||||
event.lon = event.long;
|
||||
delete event.long;
|
||||
}
|
||||
if (event.time){
|
||||
event.time = new Date(event.time);
|
||||
}
|
||||
|
||||
if (!gpsState.lastGPSEvent) {
|
||||
// this is the first event, save time of arrival and deactivate internal GPS
|
||||
Bangle.moveGPSPower(0);
|
||||
} else {
|
||||
// this is the second event, store the intervall for expecting the next GPS event
|
||||
gpsState.interval = Date.now() - gpsState.lastGPSEvent;
|
||||
}
|
||||
gpsState.lastGPSEvent = Date.now();
|
||||
// in any case, cleanup the GPS state in case no new events arrive
|
||||
if (gpsState.timeoutGPS) clearTimeout(gpsState.timeoutGPS);
|
||||
gpsState.timeoutGPS = setTimeout(()=>{
|
||||
// reset state
|
||||
gpsState.lastGPSEvent = undefined;
|
||||
gpsState.timeoutGPS = undefined;
|
||||
gpsState.interval = undefined;
|
||||
// did not get an expected GPS event but have GPS clients, switch back to internal GPS
|
||||
if (Bangle.isGPSOn()) Bangle.moveGPSPower(1);
|
||||
}, (gpsState.interval || 10000) + 1000);
|
||||
Bangle.emit('GPS', event);
|
||||
},
|
||||
// {t:"is_gps_active"}
|
||||
|
|
@ -173,6 +196,19 @@
|
|||
gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM });
|
||||
lastSteps = steps;
|
||||
}, event.int*1000);
|
||||
},
|
||||
"nav": function() {
|
||||
event.id="nav";
|
||||
if (event.instr) {
|
||||
event.t="add";
|
||||
event.src="maps"; // for the icon
|
||||
event.title="Navigation";
|
||||
if (require("messages").getMessages().find(m=>m.id=="nav"))
|
||||
event.t = "modify";
|
||||
} else {
|
||||
event.t="remove";
|
||||
}
|
||||
require("messages").pushMessage(event);
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
|
|
@ -195,6 +231,7 @@
|
|||
//send the request
|
||||
var req = {t: "http", url:url, id:options.id};
|
||||
if (options.xpath) req.xpath = options.xpath;
|
||||
if (options.return) req.return = options.return; // for xpath
|
||||
if (options.method) req.method = options.method;
|
||||
if (options.body) req.body = options.body;
|
||||
if (options.headers) req.headers = options.headers;
|
||||
|
|
@ -243,11 +280,13 @@
|
|||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
Bangle.messageIgnore = msg => {
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:"MUTE", id: msg.id });
|
||||
};
|
||||
// GPS overwrite logic
|
||||
if (settings.overwriteGps) { // if the overwrite option is set../
|
||||
if (settings.overwriteGps) { // if the overwrite option is set..
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
// migrate all GPS clients to the other variant on connection events
|
||||
let handleConnection = (state) => {
|
||||
Bangle.moveGPSPower = (state) => {
|
||||
if (Bangle.isGPSOn()){
|
||||
let orig = Bangle._PWR.GPS;
|
||||
delete Bangle._PWR.GPS;
|
||||
|
|
@ -255,39 +294,45 @@
|
|||
Bangle._PWR.GPS = orig;
|
||||
}
|
||||
};
|
||||
NRF.on('connect', ()=>{handleConnection(0);});
|
||||
NRF.on('disconnect', ()=>{handleConnection(1);});
|
||||
|
||||
// Work around Serial1 for GPS not working when connected to something
|
||||
// work around Serial1 for GPS not working when connected to something
|
||||
let serialTimeout;
|
||||
let wrap = function(f){
|
||||
return (s)=>{
|
||||
if (serialTimeout) clearTimeout(serialTimeout);
|
||||
handleConnection(1);
|
||||
origSetGPSPower(1, "androidgpsserial");
|
||||
f(s);
|
||||
serialTimeout = setTimeout(()=>{
|
||||
serialTimeout = undefined;
|
||||
if (NRF.getSecurityStatus().connected) handleConnection(0);
|
||||
origSetGPSPower(0, "androidgpsserial");
|
||||
}, 10000);
|
||||
};
|
||||
};
|
||||
Serial1.println = wrap(Serial1.println);
|
||||
Serial1.write = wrap(Serial1.write);
|
||||
|
||||
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||
Bangle.setGPSPower = (isOn, appID) => {
|
||||
// if not connected use internal GPS power function
|
||||
if (!NRF.getSecurityStatus().connected) return origSetGPSPower(isOn, appID);
|
||||
if (!Bangle._PWR) Bangle._PWR={};
|
||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||
if (!appID) appID="?";
|
||||
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
|
||||
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
|
||||
let pwr = Bangle._PWR.GPS.length>0;
|
||||
// replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||
Bangle.setGPSPower = ((isOn, appID) => {
|
||||
let pwr;
|
||||
if (!this.lastGPSEvent){
|
||||
// use internal GPS power function if no gps event has arrived from GadgetBridge
|
||||
pwr = origSetGPSPower(isOn, appID);
|
||||
} else {
|
||||
// we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app
|
||||
if (!Bangle._PWR) Bangle._PWR={};
|
||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||
if (!appID) appID="?";
|
||||
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
|
||||
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
|
||||
pwr = Bangle._PWR.GPS.length>0;
|
||||
// stop internal GPS, no clients left
|
||||
if (!pwr) origSetGPSPower(0);
|
||||
}
|
||||
// always update Gadgetbridge on current power state
|
||||
gbSend({ t: "gps_power", status: pwr });
|
||||
return pwr;
|
||||
};
|
||||
// Allow checking for GPS via GadgetBridge
|
||||
}).bind(gpsState);
|
||||
// allow checking for GPS via GadgetBridge
|
||||
Bangle.isGPSOn = () => {
|
||||
return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.23",
|
||||
"version": "0.29",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
(function(back) {
|
||||
|
||||
|
||||
|
||||
function gb(j) {
|
||||
function gbSend(j) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
}
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
|
|
@ -12,12 +10,12 @@
|
|||
var mainmenu = {
|
||||
"" : { "title" : "Android" },
|
||||
"< Back" : back,
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
|
||||
/*LANG*/"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : /*LANG*/"Find Phone" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
|
||||
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
/*LANG*/"On" : _=>gbSend({t:"findPhone",n:true}),
|
||||
/*LANG*/"Off" : _=>gbSend({t:"findPhone",n:false}),
|
||||
}),
|
||||
/*LANG*/"Keep Msgs" : {
|
||||
value : !!settings.keep,
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ let sec = {
|
|||
};
|
||||
|
||||
NRF.getSecurityStatus = () => sec;
|
||||
// add an empty starting point to make the asserts work
|
||||
Bangle._PWR={};
|
||||
|
||||
setTimeout(() => {
|
||||
// add an empty starting point to make the asserts work
|
||||
Bangle._PWR={};
|
||||
let teststeps = [];
|
||||
|
||||
teststeps.push(()=>{
|
||||
print("Not connected, should use internal GPS");
|
||||
assertTrue(!NRF.getSecurityStatus().connected, "Not connected");
|
||||
|
||||
|
|
@ -51,6 +52,9 @@ setTimeout(() => {
|
|||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
|
||||
});
|
||||
|
||||
teststeps.push(()=>{
|
||||
print("Connected, should use GB GPS");
|
||||
sec.connected = true;
|
||||
|
||||
|
|
@ -60,67 +64,90 @@ setTimeout(() => {
|
|||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
|
||||
|
||||
print("Internal GPS stays on until the first GadgetBridge event arrives");
|
||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
||||
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
assertTrue(internalOn(), "Internal GPS on");
|
||||
|
||||
print("Send minimal GadgetBridge GPS event to trigger switch");
|
||||
GB({t:"gps"});
|
||||
});
|
||||
|
||||
teststeps.push(()=>{
|
||||
print("GPS should be on, internal off");
|
||||
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
});
|
||||
|
||||
teststeps.push(()=>{
|
||||
print("Switching GPS off turns both GadgetBridge as well as internal off");
|
||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
||||
|
||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
});
|
||||
|
||||
print("Connected, then reconnect cycle");
|
||||
sec.connected = true;
|
||||
teststeps.push(()=>{
|
||||
print("Wait for all timeouts to run out");
|
||||
return 12000;
|
||||
});
|
||||
|
||||
assertTrue(NRF.getSecurityStatus().connected, "Connected");
|
||||
|
||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
teststeps.push(()=>{
|
||||
print("Check auto switch when no GPS event arrives");
|
||||
|
||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
||||
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertTrue(internalOn(), "Internal GPS on");
|
||||
|
||||
print("Send minimal GadgetBridge GPS event to trigger switch");
|
||||
GB({t:"gps"});
|
||||
|
||||
print("Internal should be switched off now");
|
||||
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
|
||||
NRF.emit("disconnect", {});
|
||||
print("disconnect");
|
||||
sec.connected = false;
|
||||
//wait on next test
|
||||
return 12000;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
teststeps.push(()=>{
|
||||
print("Check state and disable GPS, internal should be on");
|
||||
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertTrue(internalOn(), "Internal GPS on");
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertTrue(internalOn(), "Internal GPS on");
|
||||
|
||||
print("connect");
|
||||
sec.connected = true;
|
||||
NRF.emit("connect", {});
|
||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
teststeps.push(()=>{
|
||||
print("Result Overall is " + (result ? "OK" : "FAIL"));
|
||||
});
|
||||
|
||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
||||
let wrap = (functions) => {
|
||||
if (functions.length > 0) {
|
||||
setTimeout(()=>{
|
||||
let waitingTime = functions.shift()();
|
||||
if (waitingTime){
|
||||
print("WAITING: ", waitingTime);
|
||||
setTimeout(()=>{wrap(functions);}, waitingTime);
|
||||
} else
|
||||
wrap(functions);
|
||||
},0);
|
||||
}
|
||||
};
|
||||
|
||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
setTimeout(()=>{
|
||||
wrap(teststeps);
|
||||
}, 5000);
|
||||
|
||||
setTimeout(() => {
|
||||
print("Test disconnect without gps on");
|
||||
|
||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
||||
assertFalse(internalOn(), "Internal GPS off");
|
||||
|
||||
print("Result Overall is " + (result ? "OK" : "FAIL"));
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 5000);
|
||||
|
|
@ -2,3 +2,5 @@
|
|||
0.02: Update to work with Bangle.js 2
|
||||
0.03: Select GNSS systems to use for Bangle.js 2
|
||||
0.04: Now turns GPS off after upload
|
||||
0.05: Fix regression in 0.04 that caused AGPS data not to get loaded
|
||||
0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
|
||||
|
|
@ -147,7 +147,10 @@
|
|||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
gnss_select=radios[i].value;
|
||||
js += `\x10var t=getTime()+0.5;while (getTime()<t);\n`; // This is nasty - but we just wait here until the GPS has had time to boot
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS03,1,0,0,1,1,0,0,0")}")\n`; // enable GGA,GSV,RMC packets (new Bangle.js 2 GPS firmwares don't include RMC by default!)
|
||||
// Serial1.println("$PCAS06,0*1B") gets the current firmware version
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
|
|
@ -158,7 +161,7 @@
|
|||
var chunk = bin.substr(i,chunkSize);
|
||||
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
}
|
||||
js = "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
|
||||
js += "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
|
||||
return js;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"version": "0.04",
|
||||
"shortName": "AGPS",
|
||||
"version": "0.06",
|
||||
"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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Don't fire if the app uses swipes already.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
Service that allows you to use an app's back button using left to right swipe gesture.
|
||||
|
||||
## Settings
|
||||
|
||||
Mode: Blacklist/Whitelist/Always On/Disabled
|
||||
App List: Black-/whitelisted apps
|
||||
Standard # of swipe handlers: 0-10 (Default: 0, must be changed for backswipe to work at all)
|
||||
Standard # of drag handlers: 0-10 (Default: 0, must be changed for backswipe to work at all)
|
||||
|
||||
|
||||
Standard # of handlers settings are used to fine tune when backswipe should trigger the back function. E.g. when using a keyboard that works on drags, we don't want the backswipe to trigger when we just wanted to select a letter. This might not be able to cover all cases however.
|
||||
|
||||
To get an indication for standard # of handlers `Bangle["#onswipe"]` and `Bangle["#ondrag"]` can be entered in the [Espruino Web IDE](https://www.espruino.com/ide) console field. They return `undefined` if no handler is active, a function if one is active, or a list of functions if multiple are active. Calling this on the clock app is a good start.
|
||||
|
||||
## TODO
|
||||
|
||||
- Possibly add option to tweak standard # of handlers on per app basis.
|
||||
|
||||
## Creator
|
||||
Kedlub
|
||||
|
||||
## Contributors
|
||||
thyttan
|
||||
|
|
@ -15,18 +15,28 @@
|
|||
|
||||
var currentFile = global.__FILE__ || "";
|
||||
|
||||
if(global.BACK) delete global.BACK;
|
||||
if (global.BACK) delete global.BACK;
|
||||
if (options && options.back && enabledForApp(currentFile)) {
|
||||
global.BACK = options.back;
|
||||
}
|
||||
setUI(mode, cb);
|
||||
};
|
||||
|
||||
function goBack(lr, ud) {
|
||||
function countHandlers(eventType) {
|
||||
if (Bangle["#on"+eventType] === undefined) {
|
||||
return 0;
|
||||
} else if (Bangle["#on"+eventType] instanceof Array) {
|
||||
return Bangle["#on"+eventType].length;
|
||||
} else if (Bangle["#on"+eventType] !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function goBack(lr, _) {
|
||||
// if it is a left to right swipe
|
||||
if (lr === 1) {
|
||||
// if we're in an app that has a back button, run the callback for it
|
||||
if (global.BACK) {
|
||||
if (global.BACK && countHandlers("swipe")<=settings.standardNumSwipeHandlers && countHandlers("drag")<=settings.standardNumDragHandlers) {
|
||||
global.BACK();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
{ "id": "backswipe",
|
||||
"name": "Back Swipe",
|
||||
"shortName":"BackSwipe",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
||||
"icon": "app.png",
|
||||
"tags": "back,gesture,swipe",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme":"README.md",
|
||||
"type": "bootloader",
|
||||
"storage": [
|
||||
{"name":"backswipe.boot.js","url":"boot.js"},
|
||||
|
|
|
|||
|
|
@ -4,19 +4,21 @@
|
|||
// Apps is an array of app info objects, where all the apps that are there are either blocked or allowed, depending on the mode
|
||||
var DEFAULTS = {
|
||||
'mode': 0,
|
||||
'apps': []
|
||||
'apps': [],
|
||||
'standardNumSwipeHandlers': 0,
|
||||
'standardNumDragHandlers': 0
|
||||
};
|
||||
|
||||
|
||||
var settings = {};
|
||||
|
||||
|
||||
var loadSettings = function() {
|
||||
settings = require('Storage').readJSON(FILE, 1) || DEFAULTS;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var saveSettings = function(settings) {
|
||||
require('Storage').write(FILE, settings);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Get all app info files
|
||||
var getApps = function() {
|
||||
var apps = require('Storage').list(/\.info$/).map(appInfoFileName => {
|
||||
|
|
@ -35,8 +37,8 @@
|
|||
return 0;
|
||||
});
|
||||
return apps;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var showMenu = function() {
|
||||
var menu = {
|
||||
'': { 'title': 'Backswipe' },
|
||||
|
|
@ -55,11 +57,31 @@
|
|||
},
|
||||
'App List': () => {
|
||||
showAppSubMenu();
|
||||
},
|
||||
'Standard # of swipe handlers' : { // If more than this many handlers are present backswipe will not go back
|
||||
value: 0|settings.standardNumSwipeHandlers,
|
||||
min: 0,
|
||||
max: 10,
|
||||
format: v=>v,
|
||||
onchange: v => {
|
||||
settings.standardNumSwipeHandlers = v;
|
||||
saveSettings(settings);
|
||||
},
|
||||
},
|
||||
'Standard # of drag handlers' : { // If more than this many handlers are present backswipe will not go back
|
||||
value: 0|settings.standardNumDragHandlers,
|
||||
min: 0,
|
||||
max: 10,
|
||||
format: v=>v,
|
||||
onchange: v => {
|
||||
settings.standardNumDragHandlers = v;
|
||||
saveSettings(settings);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
var showAppSubMenu = function() {
|
||||
var menu = {
|
||||
|
|
@ -101,4 +123,4 @@
|
|||
|
||||
loadSettings();
|
||||
showMenu();
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@
|
|||
0.07: Improve logging and charting of component states and add widget icon
|
||||
0.08: Fix for Home button in the app and README added.
|
||||
0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state
|
||||
0.10: Remove widget icon and improve listener and setInterval handling for widget (might help with https://github.com/espruino/BangleApps/issues/381)
|
||||
0.10: Remove widget icon and improve listener and setInterval handling for widget (might help with https://github.com/espruino/BangleApps/issues/381)
|
||||
0.11: Initial port to the BangleJS2
|
||||
0.12: Remove debug log
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
|
||||
|
||||
const board = process.env.BOARD;
|
||||
const isBangle2 = board === "BANGLEJS2" || board === "EMSCRIPTEN2";
|
||||
const GraphXZero = 40;
|
||||
const GraphYZero = 180;
|
||||
const GraphY100 = 80;
|
||||
const GraphYZero = isBangle2? g.getHeight() - (g.getHeight() * 0.1): 180;
|
||||
const GraphY100 = isBangle2? 50: 80;
|
||||
const MaxValueCount = g.getWidth() - (GraphXZero * 2); // 144
|
||||
|
||||
const GraphMarkerOffset = 5;
|
||||
const MaxValueCount = 144;
|
||||
const GraphXMax = GraphXZero + MaxValueCount;
|
||||
|
||||
const GraphLcdY = GraphYZero + 10;
|
||||
|
|
@ -12,9 +16,11 @@ const GraphBluetoothY = GraphYZero + 22;
|
|||
const GraphGpsY = GraphYZero + 28;
|
||||
const GraphHrmY = GraphYZero + 34;
|
||||
|
||||
|
||||
const Storage = require("Storage");
|
||||
|
||||
function renderCoordinateSystem() {
|
||||
g.setBgColor(0,0,0);
|
||||
g.setFont("6x8", 1);
|
||||
|
||||
// Left Y axis (Battery)
|
||||
|
|
@ -36,7 +42,12 @@ function renderCoordinateSystem() {
|
|||
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
|
||||
|
||||
// Right Y axis (Temperature)
|
||||
g.setColor(0.4, 0.4, 1);
|
||||
if (isBangle2) {
|
||||
g.setColor(1, 0, 0);
|
||||
} else {
|
||||
g.setColor(0.4, 0.4, 1);
|
||||
}
|
||||
|
||||
g.drawLine(GraphXMax, GraphYZero + GraphMarkerOffset, GraphXMax, GraphY100);
|
||||
g.drawString("°C", GraphXMax + GraphMarkerOffset, GraphY100 - 10);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
|
|
@ -62,6 +73,10 @@ function loadData() {
|
|||
let logFileName = "bclog" + startingDay;
|
||||
|
||||
let dataLines = loadLinesFromFile(MaxValueCount, logFileName);
|
||||
if (!dataLines) {
|
||||
console.log("Cannot load lines from file");
|
||||
dataLines = [];
|
||||
}
|
||||
|
||||
// Top up to MaxValueCount from previous days as required
|
||||
let previousDay = decrementDay(startingDay);
|
||||
|
|
@ -235,8 +250,12 @@ Bangle.on('lcdPower', (on) => {
|
|||
}
|
||||
});
|
||||
|
||||
setWatch(switchOffApp, BTN2, {edge:"falling", debounce:50, repeat:true});
|
||||
|
||||
if (isBangle2) {
|
||||
setWatch(switchOffApp, BTN1, {edge:"falling", debounce:50, repeat:true});
|
||||
g.setBgColor(0,0,0);
|
||||
} else {
|
||||
setWatch(switchOffApp, BTN2, {edge:"falling", debounce:50, repeat:true});
|
||||
}
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
"id": "batchart",
|
||||
"name": "Battery Chart",
|
||||
"shortName": "Battery Chart",
|
||||
"version": "0.10",
|
||||
"version": "0.12",
|
||||
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
||||
"icon": "app.png",
|
||||
"tags": "app,widget,battery,time,record,chart,tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"batchart.wid.js","url":"widget.js"},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(() => {
|
||||
let recordingInterval = null;
|
||||
const Storage = require("Storage");
|
||||
|
||||
|
||||
const switchableConsumers = {
|
||||
none: 0,
|
||||
lcd: 1,
|
||||
|
|
@ -96,15 +96,14 @@
|
|||
let logPercent = E.getBattery();
|
||||
let logTemperature = E.getTemperature();
|
||||
let logConsumers = getEnabledConsumersValue();
|
||||
|
||||
|
||||
let logString = [logTime, logPercent, logTemperature, logConsumers].join(",");
|
||||
|
||||
|
||||
bcLogFileA.write(logString + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
function reload() {
|
||||
console.log("Reloading BatteryChart widget");
|
||||
WIDGETS["batchart"].width = 0;
|
||||
|
||||
if (recordingInterval) {
|
||||
|
|
@ -121,4 +120,4 @@
|
|||
};
|
||||
|
||||
reload();
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Barometer altitude adjustment setting
|
||||
0.03: Use default Bangle formatter for booleans
|
||||
0.04: Add options for units in locale and recording GPS
|
||||
0.05: Allow toggling of "max" values (screen tap) and recording (button press)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const fontFactorB2 = 2/3;
|
|||
const colfg=g.theme.fg, colbg=g.theme.bg;
|
||||
const col1=colfg, colUncertain="#88f"; // if (lf.fix) g.setColor(col1); else g.setColor(colUncertain);
|
||||
|
||||
var altiGPS=0, altiBaro=0;
|
||||
var altiBaro=0;
|
||||
var hdngGPS=0, hdngCompass=0, calibrateCompass=false;
|
||||
|
||||
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
|
||||
|
|
@ -183,7 +183,6 @@ var KalmanFilter = (function () {
|
|||
|
||||
var lf = {fix:0,satellites:0};
|
||||
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
|
||||
var canDraw = 1;
|
||||
var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time.
|
||||
var sec; // actual seconds for testing purposes
|
||||
|
||||
|
|
@ -194,30 +193,9 @@ max.n = 0; // counter. Only start comparing for max after a certain number of
|
|||
|
||||
var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTEN2")?1:0; // 1 = running in emulator. Supplies test values;
|
||||
|
||||
var wp = {}; // Waypoint to use for distance from cur position.
|
||||
var SATinView = 0;
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
|
||||
function distance(a,b){
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
|
||||
// Distance in selected units
|
||||
var d = Math.sqrt(x*x + y*y) * 6371000;
|
||||
d = (d/parseFloat(cfg.dist)).toFixed(2);
|
||||
if ( d >= 100 ) d = parseFloat(d).toFixed(1);
|
||||
if ( d >= 1000 ) d = parseFloat(d).toFixed(0);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
function drawFix(dat) {
|
||||
|
||||
if (!canDraw) return;
|
||||
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
|
||||
var v = '';
|
||||
|
|
@ -227,7 +205,7 @@ function drawFix(dat) {
|
|||
v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString();
|
||||
|
||||
// Primary Units
|
||||
u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units;
|
||||
u = (showMax ? 'max ' : '') + (cfg.primSpd?cfg.spd_unit:dat.alt_units);
|
||||
|
||||
drawPrimary(v,u);
|
||||
|
||||
|
|
@ -260,14 +238,6 @@ function drawFix(dat) {
|
|||
}
|
||||
|
||||
|
||||
function drawClock() {
|
||||
if (!canDraw) return;
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
drawTime();
|
||||
g.reset();
|
||||
}
|
||||
|
||||
|
||||
function drawPrimary(n,u) {
|
||||
//if(emulator)console.log("\n1: " + n +" "+ u);
|
||||
var s=40; // Font size
|
||||
|
|
@ -337,16 +307,6 @@ function drawSats(sats) {
|
|||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(1,1); //right, bottom
|
||||
g.drawString(sats,screenW,screenH);
|
||||
|
||||
g.setFontVector(18);
|
||||
g.setColor(col1);
|
||||
|
||||
if ( cfg.modeA == 1 ) {
|
||||
if ( showMax ) {
|
||||
g.setFontAlign(0,1); //centre, bottom
|
||||
g.drawString('MAX',120,164);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onGPS(fix) {
|
||||
|
|
@ -367,7 +327,6 @@ function onGPS(fix) {
|
|||
|
||||
var sp = '---';
|
||||
var al = '---';
|
||||
var di = '---';
|
||||
var age = '---';
|
||||
|
||||
if (fix.fix) lf = fix;
|
||||
|
|
@ -403,6 +362,8 @@ function onGPS(fix) {
|
|||
|
||||
if ( sp < 10 ) sp = sp.toFixed(1);
|
||||
else sp = Math.round(sp);
|
||||
if (isNaN(sp)) sp = '---';
|
||||
|
||||
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
|
||||
|
||||
// Altitude
|
||||
|
|
@ -410,12 +371,14 @@ function onGPS(fix) {
|
|||
al = Math.round(parseFloat(al)/parseFloat(cfg.alt));
|
||||
if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al);
|
||||
|
||||
// Distance to waypoint
|
||||
di = distance(lf,wp);
|
||||
if (isNaN(di)) di = 0;
|
||||
|
||||
// Age of last fix (secs)
|
||||
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
||||
} else {
|
||||
// populate spd_unit
|
||||
if (cfg.spd == 0) {
|
||||
m = require("locale").speed(0).match(/[0-9,\.]+(.*)/);
|
||||
cfg.spd_unit = m[1];
|
||||
}
|
||||
}
|
||||
|
||||
if ( cfg.modeA == 1 ) {
|
||||
|
|
@ -440,15 +403,7 @@ function onGPS(fix) {
|
|||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(_=>load(), BTN1);
|
||||
|
||||
onGPS(lf);
|
||||
}
|
||||
|
||||
|
||||
function updateClock() {
|
||||
if (!canDraw) return;
|
||||
drawTime();
|
||||
g.reset();
|
||||
|
||||
|
|
@ -465,7 +420,7 @@ function updateClock() {
|
|||
// Read settings.
|
||||
let cfg = require('Storage').readJSON('bikespeedo.json',1)||{};
|
||||
|
||||
cfg.spd = 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
||||
cfg.spd = !cfg.localeUnits; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
||||
cfg.spd_unit = 'km/h'; // Displayed speed unit
|
||||
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
|
||||
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')
|
||||
|
|
@ -499,14 +454,6 @@ function onPressure(dat) {
|
|||
altiBaro = Number(dat.altitude.toFixed(0)) + Number(cfg.altDiff);
|
||||
}
|
||||
|
||||
Bangle.setBarometerPower(1); // needs some time...
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
onGPS(lf);
|
||||
Bangle.setGPSPower(1);
|
||||
Bangle.on('GPS', onGPS);
|
||||
Bangle.on('pressure', onPressure);
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
if (!CALIBDATA) calibrateCompass = true;
|
||||
function Compass_tiltfixread(O,S){
|
||||
|
|
@ -544,11 +491,58 @@ function Compass_reading() {
|
|||
Compass_heading = Compass_newHeading(d,Compass_heading);
|
||||
hdngCompass = Compass_heading.toFixed(0);
|
||||
}
|
||||
if (!calibrateCompass) setInterval(Compass_reading,200);
|
||||
|
||||
setButtons();
|
||||
if (emulator) setInterval(updateClock, 2000);
|
||||
else setInterval(updateClock, 10000);
|
||||
function nextMode() {
|
||||
showMax = 1 - showMax;
|
||||
}
|
||||
|
||||
function start() {
|
||||
Bangle.setBarometerPower(1); // needs some time...
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
onGPS(lf);
|
||||
Bangle.setGPSPower(1);
|
||||
Bangle.on('GPS', onGPS);
|
||||
Bangle.on('pressure', onPressure);
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
if (!calibrateCompass) setInterval(Compass_reading,200);
|
||||
|
||||
if (emulator) setInterval(updateClock, 2000);
|
||||
else setInterval(updateClock, 10000);
|
||||
|
||||
let createdRecording = false;
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
touch: nextMode,
|
||||
btn: () => {
|
||||
const rec = WIDGETS["recorder"];
|
||||
if(rec){
|
||||
const active = rec.isRecording();
|
||||
if(active){
|
||||
createdRecording = true;
|
||||
rec.setRecording(false);
|
||||
}else{
|
||||
rec.setRecording(true, { force: createdRecording ? "append" : "new" });
|
||||
}
|
||||
}else{
|
||||
nextMode();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// can't delay loadWidgets til here - need to have already done so for recorder
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
if (cfg.record && WIDGETS["recorder"]) {
|
||||
WIDGETS["recorder"]
|
||||
.setRecording(true)
|
||||
.then(start);
|
||||
|
||||
if (cfg.recordStopOnExit)
|
||||
E.on('kill', () => WIDGETS["recorder"].setRecording(false));
|
||||
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bikespeedo",
|
||||
"name": "Bike Speedometer (beta)",
|
||||
"shortName": "Bike Speedometer",
|
||||
"version": "0.03",
|
||||
"version": "0.05",
|
||||
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"Screenshot.png"}],
|
||||
|
|
|
|||
|
|
@ -11,9 +11,34 @@
|
|||
'< Back': back,
|
||||
'< Load Bike Speedometer': ()=>{load('bikespeedo.app.js');},
|
||||
'Barometer Altitude adjustment' : function() { E.showMenu(altdiffMenu); },
|
||||
'Kalman Filters' : function() { E.showMenu(kalMenu); }
|
||||
'Kalman Filters' : function() { E.showMenu(kalMenu); },
|
||||
'Speed units': {
|
||||
value: !!settings.localeUnits,
|
||||
format: b => b ? "Locale" : "km/h",
|
||||
onchange: b => {
|
||||
settings.localeUnits = b;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (global.WIDGETS && WIDGETS["recorder"]) {
|
||||
appMenu[/*LANG*/"Record rides"] = {
|
||||
value : !!settings.record,
|
||||
onchange : v => {
|
||||
settings.record = v;
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
appMenu[/*LANG*/"Stop record on exit"] = {
|
||||
value : !!settings.recordStopOnExit,
|
||||
onchange : v => {
|
||||
settings.recordStopOnExit = v;
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const altdiffMenu = {
|
||||
'': { 'title': 'Altitude adjustment' },
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AH4ARkQAHBwsBiIACiAHBgQXIkAXJiIuKGAwWEC4cjmYABn//AAMyC63yC653FC6HwC5aQBC5ybIC44WChGAWxMgC44rCxGIZxYXFIoYXBGAQNCAAQXILYYXBGAUDBoK0EC5AsBC4QwEC5wAEC853BhAWDI6CPCFwp3OX4ouCC8xHXCAJ3VX94XCwBHVGIiPTU4oNCAAQWBX5gDBgQRCAAoXGGAUIFwQXHkAXHJIgABCw4IBC5sAiIAEiAgHAAQXLHBAYIC+6wJQYIADgIXGGBJ3FC4iOBAH4A/ACAA=="))
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
E.showMessage("Scanning...");
|
||||
var devices = [];
|
||||
|
||||
setInterval(function() {
|
||||
NRF.findDevices(function(devs) {
|
||||
devs.forEach(dev=>{
|
||||
var existing = devices.find(d=>d.id==dev.id);
|
||||
if (existing) {
|
||||
existing.timeout = 0;
|
||||
existing.rssi = (existing.rssi*3 + dev.rssi)/4;
|
||||
} else {
|
||||
dev.timeout = 0;
|
||||
dev.new = 0;
|
||||
devices.push(dev);
|
||||
}
|
||||
});
|
||||
devices.forEach(d=>{d.timeout++;d.new++});
|
||||
devices = devices.filter(dev=>dev.timeout<8);
|
||||
devices.sort((a,b)=>b.rssi - a.rssi);
|
||||
g.clear(1).setFont("12x20");
|
||||
var wasNew = false;
|
||||
devices.forEach((d,y)=>{
|
||||
y*=20;
|
||||
var n = d.name;
|
||||
if (!n) n=d.id.substr(0,22);
|
||||
if (d.new<4) {
|
||||
g.fillRect(0,y,g.getWidth(),y+19);
|
||||
g.setColor(g.theme.bg);
|
||||
if (d.rssi > -70) wasNew = true;
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
g.setFontAlign(-1,-1);
|
||||
g.drawString(n,0,y);
|
||||
g.setFontAlign(1,-1);
|
||||
g.drawString(0|d.rssi,g.getWidth()-1,y);
|
||||
});
|
||||
g.flip();
|
||||
Bangle.setLCDBrightness(wasNew);
|
||||
}, 1200);
|
||||
}, 1500);
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "blescanner",
|
||||
"name": "BLE Scanner",
|
||||
"shortName":"BLE Scan",
|
||||
"version":"0.01",
|
||||
"description": "Scans for bluetooth devices nearby and shows their names on the screen ordered by signal strength. The most recently discovered items are highlighted.",
|
||||
"icon": "app.png",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"tags": "tool,bluetooth",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"blescanner.app.js","url":"app.js"},
|
||||
{"name":"blescanner.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
|
@ -64,3 +64,6 @@
|
|||
0.55: Add toLocalISOString polyfill for pre-2v15 firmwares
|
||||
Only add boot info comments if settings.bootDebug was set
|
||||
If settings.bootDebug is set, output timing for each section of .boot0
|
||||
0.56: Settings.log = 0,1,2,3 for off,display, log, both
|
||||
0.57: Handle the whitelist being disabled
|
||||
0.58: "Make Connectable" temporarily bypasses the whitelist
|
||||
|
|
|
|||
|
|
@ -32,14 +32,12 @@ if (s.ble!==false) {
|
|||
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
|
||||
}
|
||||
}
|
||||
if (s.log==2) { // logging to file
|
||||
boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
`;
|
||||
} if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
|
||||
// settings.log 0-off, 1-display, 2-log, 3-both
|
||||
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log>=2) { boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
LoopbackB.on('data',function(d) {_DBGLOG.write(d);${(s.log==3)?"Terminal.write(d);":""}});
|
||||
LoopbackA.setConsole(true);\n`;
|
||||
else if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
||||
} else if (s.log==1) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
||||
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
||||
/* If not programmable add our own handler for Bluetooth data
|
||||
to allow Gadgetbridge commands to be received*/
|
||||
|
|
@ -56,10 +54,10 @@ Bluetooth.on('line',function(l) {
|
|||
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
|
||||
});\n`;
|
||||
} else {
|
||||
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
|
||||
if (s.log>=2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
LoopbackB.on('data',function(d) {_DBGLOG.write(d);${(s.log==3)?"Terminal.write(d);":""}});
|
||||
if (!NRF.getSecurityStatus().connected) LoopbackA.setConsole();\n`;
|
||||
else if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
||||
else if (s.log==1) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
||||
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
|
||||
}
|
||||
// we just reset, so BLE should be on.
|
||||
|
|
@ -81,7 +79,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
|
|||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist && !(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||
// ================================================== FIXING OLDER FIRMWARES
|
||||
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
|
||||
|
|
@ -107,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,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.55",
|
||||
"version": "0.58",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial release.
|
||||
0.02: Added compatibility to OpenTracks and added HRM Location
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# BLE GATT HRM Service
|
||||
|
||||
Adds the GATT HRM Service to advertise the current HRM over Bluetooth.
|
||||
|
||||
## Usage
|
||||
|
||||
This boot code runs in the background and has no user interface.
|
||||
|
||||
## Creator
|
||||
|
||||
[Another Stranger](https://github.com/anotherstranger)
|
||||
|
||||
## Aknowledgements
|
||||
|
||||
Special thanks to [Jonathan Jefferies](https://github.com/jjok) for creating the
|
||||
bootgattbat app, which was the inspiration for this App!
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,70 @@
|
|||
(() => {
|
||||
function setupHRMAdvertising() {
|
||||
/*
|
||||
* This function prepares BLE heart rate Advertisement.
|
||||
*/
|
||||
|
||||
NRF.setAdvertising(
|
||||
{
|
||||
0x180d: undefined
|
||||
},
|
||||
{
|
||||
// We need custom Advertisement settings for Apps like OpenTracks
|
||||
connectable: true,
|
||||
discoverable: true,
|
||||
scannable: true,
|
||||
whenConnected: true,
|
||||
}
|
||||
);
|
||||
|
||||
NRF.setServices({
|
||||
0x180D: { // heart_rate
|
||||
0x2A37: { // heart_rate_measurement
|
||||
notify: true,
|
||||
value: [0x06, 0],
|
||||
},
|
||||
0x2A38: { // Sensor Location: Wrist
|
||||
value: 0x02,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
function updateBLEHeartRate(hrm) {
|
||||
/*
|
||||
* Send updated heart rate measurement via BLE
|
||||
*/
|
||||
if (hrm === undefined || hrm.confidence < 50) return;
|
||||
try {
|
||||
NRF.updateServices({
|
||||
0x180D: {
|
||||
0x2A37: {
|
||||
value: [0x06, hrm.bpm],
|
||||
notify: true
|
||||
},
|
||||
0x2A38: {
|
||||
value: 0x02,
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes("BLE restart")) {
|
||||
/*
|
||||
* BLE has to restart after service setup.
|
||||
*/
|
||||
NRF.disconnect();
|
||||
}
|
||||
else if (error.message.includes("UUID 0x2a37")) {
|
||||
/*
|
||||
* Setup service if it wasn't setup correctly for some reason
|
||||
*/
|
||||
setupHRMAdvertising();
|
||||
} else {
|
||||
console.log("[bootgatthrm]: Unexpected error occured while updating HRM over BLE! Error: " + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupHRMAdvertising();
|
||||
Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm); });
|
||||
})();
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "bootgatthrm",
|
||||
"name": "BLE GATT HRM Service",
|
||||
"shortName": "BLE HRM Service",
|
||||
"version": "0.02",
|
||||
"description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n",
|
||||
"icon": "bluetooth.png",
|
||||
"type": "bootloader",
|
||||
"tags": "hrm,health,ble,bluetooth,gatt",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gatthrm.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: New config options such as step, meridian, short/long formats, custom prefix/suffix
|
||||
0.03: Allows showing the month in short or long format by setting `"shortMonth"` to true or false
|
||||
0.04: Improves touchscreen drag handling for background apps such as Pattern Launcher
|
||||
0.05: Fixes step count not resetting after a new day starts
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
# Box Clock
|
||||
|
||||
Box Clock is a customizable clock app for Bangle.js 2 that features an interactive drag and drop interface and easy JSON configuration.
|
||||
|
||||
## Unique Features
|
||||
|
||||
__Drag & Drop:__
|
||||
|
||||
This intuitive feature allows you to reposition any element (box) on the clock face with ease. Tap on the box(s) you want to move and the border will show, drag into position and tap outside of the boxes to finish placing. **Note:** Roll the tip of your finger slowly on the screen for fine adjustments.
|
||||
|
||||
__Double Tap to Save:__
|
||||
|
||||
After you've found the perfect position for your boxes, you can save their positions with a quick double tap on the background. This makes it easy to ensure your custom layout is stored for future use. A save icon will appear momentarily to confirm that your configuration has been saved.
|
||||
|
||||
__JSON Configuration:__
|
||||
|
||||
Each box can be customized extensively via a simple JSON configuration. You can add a custom text string to your configuration with the "string" parameter and you can match system theme colors by using "fg", "bg", "fg2", "bg2", "fgH", or "bgH" for any of the color parameters.
|
||||
|
||||
## Config File Structure
|
||||
|
||||
Here's an example of what a configuration might contain:
|
||||
|
||||
```
|
||||
{
|
||||
"customBox": {
|
||||
"string": "Your text here",
|
||||
"font": "CustomFont", // Custom fonts must be removed in setUI
|
||||
"fontSize": 1,
|
||||
"outline": 2,
|
||||
"color": "#FF9900", // Use 6 or 3 digit hex color codes
|
||||
"outlineColor": "bgH", // Or match system theme colors like this
|
||||
"border": 65535, // Or use 16-bit RGB565 like this
|
||||
"xPadding": 1,
|
||||
"yPadding": -4,
|
||||
"xOffset": 0,
|
||||
"yOffset": 3,
|
||||
"boxPos": { "x": 0.5, "y": 0.5 },
|
||||
"prefix": "", // Adds a string to the beginning of the main string
|
||||
"suffix": "", // Adds a string to the end of the main string
|
||||
"disableSuffix": true, // Use to remove DayOfMonth suffix only
|
||||
"short": false, // Use long format of time, meridian, date, or DoW
|
||||
"shortMonth": false // Use long format of month within date
|
||||
|
||||
},
|
||||
"bg": { // Can also be removed for no background
|
||||
"img": "YourImageName.img"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
__Breakdown of Parameters:__
|
||||
|
||||
* **Box Name:** The name of your text box. Box Clock includes functional support for "time", "date", "meridian" (AM/PM), "dow" (Day of Week), "batt" (Battery), and "step" (Step count). You can add additional custom boxes with unique titles.
|
||||
|
||||
* **string:** The text string to be displayed inside the box. This is only required for custom Box Names.
|
||||
|
||||
* **font:** The font name given to g.setFont().
|
||||
|
||||
* **fontSize:** The size of the font.
|
||||
|
||||
* **outline:** The thickness of the outline around the text.
|
||||
|
||||
* **color:** The color of the text.
|
||||
|
||||
* **outlineColor:** The color of the text outline.
|
||||
|
||||
* **border:** The color of the box border when moving.
|
||||
|
||||
* **xPadding, yPadding:** Additional padding around the text inside the box.
|
||||
|
||||
* **xOffset, yOffset:** Offsets the text position within the box.
|
||||
|
||||
* **boxPos:** Initial position of the box on the screen. Values are fractions of the screen width (x) and height (y), so { "x": 0.5, "y": 0.5 } would be in the middle of the screen.
|
||||
|
||||
* **prefix:** Adds a string to the beginning of the main string. For example, you can set "prefix": "Steps: " to display "Steps: 100" for the step count.
|
||||
|
||||
* **suffix:** Adds a string to the end of the main string. For example, you can set "suffix": "%" to display "80%" for the battery percentage.
|
||||
|
||||
* **disableSuffix:** Applies only to the "date" box. Set to true to disable the DayOfMonth suffix. This is used to remove the "st","nd","rd", or "th" from the DayOfMonth number.
|
||||
|
||||
* **short:** Set to false to get the long format value of time, meridian, date, or DayOfWeek. Short formats are used by default if not specified.
|
||||
|
||||
* **shortMonth:** Applies only to the "date" box. Set to false to get the long format value of the month. Short format is used by default if not specified.
|
||||
|
||||
* **bg:** This specifies a custom background image, with the img property defining the name of the image file on the Bangle.js storage.
|
||||
|
||||
## Multiple Configurations
|
||||
|
||||
__Settings Menu:__
|
||||
|
||||
The app includes a settings menu that allows you to switch between different configurations. The selected configuration is stored as a number in the default `boxclk.json` file using the selectedConfig property.
|
||||
|
||||
If the selectedConfig property is not present or is set to 0, the app will use the default configuration. To create additional configurations, create separate JSON files with the naming convention `boxclk-N.json`, where `N` is the configuration number. The settings menu will list all available configurations.
|
||||
|
||||
## Example Configs:
|
||||
|
||||
To easily try out other configs, download and place the JSON configs and/or background images from below onto your Bangle.js storage. Then go to the Box Clock settings menu to select the new config number. You can also modify them to suit your personal preferences.
|
||||
|
||||
__Space Theme:__
|
||||
|
||||
- **Config:** [boxclk-1.json](https://github.com/espruino/BangleApps/tree/master/apps/boxclk/boxclk-1.json)
|
||||
- **Background:** [boxclk.space.img](https://github.com/espruino/BangleApps/tree/master/apps/boxclk/boxclk.space.img) ([Original Source](https://www.pixilart.com/art/fallin-from-outer-space-sr2e0c1a705749a))
|
||||
|
||||
__System Color Theme:__
|
||||
|
||||
- **Config:** [boxclk-2.json](https://github.com/espruino/BangleApps/tree/master/apps/boxclk/boxclk-2.json)
|
||||
|
||||
## Compatibility
|
||||
|
||||
This app was built and tested with Bangle.js 2.
|
||||
|
||||
## Feedback
|
||||
|
||||
If you have any issues or suggestions, please open an issue on this GitHub repository. Contributions to improve the application are also welcomed.
|
||||
|
||||
## Creator
|
||||
|
||||
[stweedo](https://github.com/stweedo)
|
||||
|
|
@ -0,0 +1,415 @@
|
|||
{
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 1. Module dependencies and initial configurations
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let storage = require("Storage");
|
||||
let locale = require("locale");
|
||||
let widgets = require("widget_utils");
|
||||
let date = new Date();
|
||||
let bgImage;
|
||||
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
|
||||
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
|
||||
// Add a condition to check if the file exists, if it does not, default to 'boxclk.json'
|
||||
if (!storage.read(fileName)) {
|
||||
fileName = 'boxclk.json';
|
||||
}
|
||||
let boxesConfig = storage.readJSON(fileName, 1) || {};
|
||||
let boxes = {};
|
||||
let boxPos = {};
|
||||
let isDragging = {};
|
||||
let wasDragging = {};
|
||||
let doubleTapTimer = null;
|
||||
let g_setColor;
|
||||
|
||||
let saveIcon = require("heatshrink").decompress(atob("mEwwkEogA/AHdP/4AK+gWVDBQWNAAIuVGBAIB+UQdhMfGBAHBCxUAgIXHIwPyCxQwEJAgXB+MAl/zBwQGBn8ggQjBGAQXG+EA/4XI/8gBIQXTGAMPC6n/C6HzkREBC6YACC6QAFC57aHCYIXOOgLsEn4XPABIX/C6vykQAEl6/WgCQBC5imFAAT2BC5gCBI4oUCC5x0IC/4X/C4K8Bl4XJ+TCCC4wKBABkvC4tEEoMQCxcBB4IWEC4XyDBUBFwIXGJAIAOIwowDABoWGGB4uHDBwWJAH4AzA"));
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 2. Graphical and visual configurations
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let w = g.getWidth();
|
||||
let h = g.getHeight();
|
||||
let totalWidth, totalHeight;
|
||||
let drawTimeout;
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 3. Touchscreen Handlers
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let touchHandler;
|
||||
let dragHandler;
|
||||
let movementDistance = 0;
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 4. Font loading function
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let loadCustomFont = function() {
|
||||
Graphics.prototype.setFontBrunoAce = function() {
|
||||
// Actual height 23 (24 - 2)
|
||||
return this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('ABMHwADBh4DKg4bKgIPDAYUfAYV/AYX/AQMD/gmC+ADBn/AByE/GIU8AYUwLxcfAYX/8AnB//4JIP/FgMP4F+CQQBBjwJBFYRbBAd43DHoJpBh/g/xPEK4ZfDgEEORKDDAY8////wADLfZrTCgITBnhEBAYJMBAYMPw4DCM4QDjhwDCjwDBn0+AYMf/gDBh/4AYMH+ADBLpc4ToK/NGYZfnAYcfL4U/x5fBW4LvB/7vC+LvBgHAsBfIn76Cn4WBcYQDFEgJ+CQQYDyH4L/BAZbHLNYjjCAZc8ngDunycBZ4KkBa4KwBnEHY4UB+BfMgf/ZgMH/4XBc4cf4F/gE+ZgRjwAYcfj5jBM4U4M4RQBM4UA8BjIngDFEYJ8BAYUDAYQvCM4ZxBC4V+AYQvBnkBQ4M8gabBJQPAI4WAAYM/GYQaBAYJKCnqyCn5OCn4aBAYIaBAYJPCU4IABnBhIuDXCFAMD+Z/BY4IDBQwOPwEfv6TDAYUPAcwrDAYQ7BAYY/BI4cD8bLCK4RfEAA0BRYTeDcwIrFn0Pw43Bg4DugYDBjxBBU4SvDMYMH/5QBgP/LAQAP8EHN4UPwADHB4YAHA'))),
|
||||
46,
|
||||
atob("CBEdChgYGhgaGBsaCQ=="),
|
||||
32|65536
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 5. Initial settings of boxes and their positions
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
for (let key in boxesConfig) {
|
||||
if (key === 'bg' && boxesConfig[key].img) {
|
||||
bgImage = storage.read(boxesConfig[key].img);
|
||||
} else if (key !== 'selectedConfig') {
|
||||
boxes[key] = Object.assign({}, boxesConfig[key]);
|
||||
}
|
||||
}
|
||||
|
||||
let boxKeys = Object.keys(boxes);
|
||||
|
||||
boxKeys.forEach((key) => {
|
||||
let boxConfig = boxes[key];
|
||||
boxPos[key] = {
|
||||
x: w * boxConfig.boxPos.x,
|
||||
y: h * boxConfig.boxPos.y
|
||||
};
|
||||
isDragging[key] = false;
|
||||
wasDragging[key] = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 6. Text and drawing functions
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// Overwrite the setColor function to allow the
|
||||
// use of (x) in g.theme.x as a string
|
||||
// in your JSON config ("fg", "bg", "fg2", "bg2", "fgH", "bgH")
|
||||
let modSetColor = function() {
|
||||
// Save the original setColor function
|
||||
g_setColor = g.setColor;
|
||||
// Overwrite setColor with the new function
|
||||
g.setColor = function(color) {
|
||||
if (typeof color === "string" && color in g.theme) {
|
||||
g_setColor.call(g, g.theme[color]);
|
||||
} else {
|
||||
g_setColor.call(g, color);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let restoreSetColor = function() {
|
||||
// Restore the original setColor function
|
||||
if (g_setColor) {
|
||||
g.setColor = g_setColor;
|
||||
}
|
||||
};
|
||||
|
||||
// Overwrite the drawString function
|
||||
let g_drawString = g.drawString;
|
||||
g.drawString = function(box, str, x, y) {
|
||||
outlineText(box, str, x, y);
|
||||
g.setColor(box.color);
|
||||
g_drawString.call(g, str, x, y);
|
||||
};
|
||||
|
||||
let outlineText = function(box, str, x, y) {
|
||||
let px = box.outline;
|
||||
let dx = [-px, 0, px, -px, px, -px, 0, px];
|
||||
let dy = [-px, -px, -px, 0, 0, px, px, px];
|
||||
g.setColor(box.outlineColor);
|
||||
for (let i = 0; i < dx.length; i++) {
|
||||
g_drawString.call(g, str, x + dx[i], y + dy[i]);
|
||||
}
|
||||
};
|
||||
|
||||
let calcBoxSize = function(boxItem) {
|
||||
g.reset();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont(boxItem.font, boxItem.fontSize);
|
||||
let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline;
|
||||
let fontHeight = g.getFontHeight() + 2 * boxItem.outline;
|
||||
totalWidth = strWidth + 2 * boxItem.xPadding;
|
||||
totalHeight = fontHeight + 2 * boxItem.yPadding;
|
||||
};
|
||||
|
||||
let calcBoxPos = function(boxKey) {
|
||||
return {
|
||||
x1: boxPos[boxKey].x - totalWidth / 2,
|
||||
y1: boxPos[boxKey].y - totalHeight / 2,
|
||||
x2: boxPos[boxKey].x + totalWidth / 2,
|
||||
y2: boxPos[boxKey].y + totalHeight / 2
|
||||
};
|
||||
};
|
||||
|
||||
let displaySaveIcon = function() {
|
||||
draw(boxes);
|
||||
g.drawImage(saveIcon, w / 2 - 24, h / 2 - 24);
|
||||
// Display save icon for 2 seconds
|
||||
setTimeout(() => {
|
||||
g.clearRect(w / 2 - 24, h / 2 - 24, w / 2 + 24, h / 2 + 24);
|
||||
draw(boxes);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 7. String forming helper functions
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let isBool = function(val, defaultVal) {
|
||||
return typeof val !== 'undefined' ? Boolean(val) : defaultVal;
|
||||
};
|
||||
|
||||
let getDate = function(short, shortMonth, disableSuffix) {
|
||||
const date = new Date();
|
||||
const dayOfMonth = date.getDate();
|
||||
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
|
||||
const year = date.getFullYear();
|
||||
let suffix;
|
||||
if ([1, 21, 31].includes(dayOfMonth)) {
|
||||
suffix = "st";
|
||||
} else if ([2, 22].includes(dayOfMonth)) {
|
||||
suffix = "nd";
|
||||
} else if ([3, 23].includes(dayOfMonth)) {
|
||||
suffix = "rd";
|
||||
} else {
|
||||
suffix = "th";
|
||||
}
|
||||
let dayOfMonthStr = disableSuffix ? dayOfMonth : dayOfMonth + suffix;
|
||||
return month + " " + dayOfMonthStr + (short ? '' : (", " + year)); // not including year for short version
|
||||
};
|
||||
|
||||
let getDayOfWeek = function(date, short) {
|
||||
return locale.dow(date, short ? 1 : 0);
|
||||
};
|
||||
|
||||
locale.meridian = function(date, short) {
|
||||
let hours = date.getHours();
|
||||
let meridian = hours >= 12 ? 'PM' : 'AM';
|
||||
return short ? meridian[0] : meridian;
|
||||
};
|
||||
|
||||
let modString = function(boxItem, data) {
|
||||
let prefix = boxItem.prefix || '';
|
||||
let suffix = boxItem.suffix || '';
|
||||
return prefix + data + suffix;
|
||||
};
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 8. Main draw function
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let draw = (function() {
|
||||
let updatePerMinute = true; // variable to track the state of time display
|
||||
|
||||
return function(boxes) {
|
||||
date = new Date();
|
||||
g.clear();
|
||||
if (bgImage) {
|
||||
g.drawImage(bgImage, 0, 0);
|
||||
}
|
||||
if (boxes.time) {
|
||||
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0));
|
||||
updatePerMinute = isBool(boxes.time.short, true);
|
||||
}
|
||||
if (boxes.meridian) {
|
||||
boxes.meridian.string = modString(boxes.meridian, locale.meridian(date, isBool(boxes.meridian.short, true)));
|
||||
}
|
||||
if (boxes.date) {
|
||||
boxes.date.string = (
|
||||
modString(boxes.date,
|
||||
getDate(isBool(boxes.date.short, true),
|
||||
isBool(boxes.date.shortMonth, true),
|
||||
isBool(boxes.date.disableSuffix, false)
|
||||
)));
|
||||
}
|
||||
if (boxes.dow) {
|
||||
boxes.dow.string = modString(boxes.dow, getDayOfWeek(date, isBool(boxes.dow.short, true)));
|
||||
}
|
||||
if (boxes.batt) {
|
||||
boxes.batt.string = modString(boxes.batt, E.getBattery());
|
||||
}
|
||||
if (boxes.step) {
|
||||
boxes.step.string = modString(boxes.step, Bangle.getHealthStatus("day").steps);
|
||||
}
|
||||
boxKeys.forEach((boxKey) => {
|
||||
let boxItem = boxes[boxKey];
|
||||
calcBoxSize(boxItem);
|
||||
const pos = calcBoxPos(boxKey);
|
||||
if (isDragging[boxKey]) {
|
||||
g.setColor(boxItem.border);
|
||||
g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2);
|
||||
}
|
||||
g.drawString(
|
||||
boxItem,
|
||||
boxItem.string,
|
||||
boxPos[boxKey].x + boxItem.xOffset,
|
||||
boxPos[boxKey].y + boxItem.yOffset
|
||||
);
|
||||
});
|
||||
if (!Object.values(isDragging).some(Boolean)) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
let interval = updatePerMinute ? 60000 - (Date.now() % 60000) : 1000;
|
||||
drawTimeout = setTimeout(() => draw(boxes), interval);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 9. Helper function for touch event
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let touchInText = function(e, boxItem, boxKey) {
|
||||
calcBoxSize(boxItem);
|
||||
const pos = calcBoxPos(boxKey);
|
||||
return e.x >= pos.x1 &&
|
||||
e.x <= pos.x2 &&
|
||||
e.y >= pos.y1 &&
|
||||
e.y <= pos.y2;
|
||||
};
|
||||
|
||||
let deselectAllBoxes = function() {
|
||||
Object.keys(isDragging).forEach((boxKey) => {
|
||||
isDragging[boxKey] = false;
|
||||
});
|
||||
restoreSetColor();
|
||||
widgets.show();
|
||||
widgets.swipeOn();
|
||||
modSetColor();
|
||||
};
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 10. Setup function to configure event handlers
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let setup = function() {
|
||||
// ------------------------------------
|
||||
// Define the touchHandler function
|
||||
// ------------------------------------
|
||||
touchHandler = function(zone, e) {
|
||||
wasDragging = Object.assign({}, isDragging);
|
||||
let boxTouched = false;
|
||||
boxKeys.forEach((boxKey) => {
|
||||
if (touchInText(e, boxes[boxKey], boxKey)) {
|
||||
isDragging[boxKey] = true;
|
||||
wasDragging[boxKey] = true;
|
||||
boxTouched = true;
|
||||
}
|
||||
});
|
||||
if (!boxTouched) {
|
||||
if (!Object.values(isDragging).some(Boolean)) { // check if no boxes are being dragged
|
||||
deselectAllBoxes();
|
||||
if (doubleTapTimer) {
|
||||
clearTimeout(doubleTapTimer);
|
||||
doubleTapTimer = null;
|
||||
// Save boxesConfig on double tap outside of any box and when no boxes are being dragged
|
||||
Object.keys(boxPos).forEach((boxKey) => {
|
||||
boxesConfig[boxKey].boxPos.x = (boxPos[boxKey].x / w).toFixed(3);
|
||||
boxesConfig[boxKey].boxPos.y = (boxPos[boxKey].y / h).toFixed(3);
|
||||
});
|
||||
storage.write(fileName, JSON.stringify(boxesConfig));
|
||||
displaySaveIcon();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// if any box is being dragged, just deselect all without saving
|
||||
deselectAllBoxes();
|
||||
}
|
||||
}
|
||||
if (Object.values(wasDragging).some(Boolean) || !boxTouched) {
|
||||
draw(boxes);
|
||||
}
|
||||
doubleTapTimer = setTimeout(() => {
|
||||
doubleTapTimer = null;
|
||||
}, 500); // Increase or decrease this value based on the desired double tap timing
|
||||
movementDistance = 0;
|
||||
};
|
||||
|
||||
// ------------------------------------
|
||||
// Define the dragHandler function
|
||||
// ------------------------------------
|
||||
dragHandler = function(e) {
|
||||
// Check if any box is being dragged
|
||||
if (!Object.values(isDragging).some(Boolean)) return;
|
||||
// Calculate the movement distance
|
||||
movementDistance += Math.abs(e.dx) + Math.abs(e.dy);
|
||||
// Check if the movement distance exceeds a threshold
|
||||
if (movementDistance > 1) {
|
||||
boxKeys.forEach((boxKey) => {
|
||||
if (isDragging[boxKey]) {
|
||||
widgets.hide();
|
||||
let boxItem = boxes[boxKey];
|
||||
calcBoxSize(boxItem);
|
||||
let newX = boxPos[boxKey].x + e.dx;
|
||||
let newY = boxPos[boxKey].y + e.dy;
|
||||
if (newX - totalWidth / 2 >= 0 &&
|
||||
newX + totalWidth / 2 <= w &&
|
||||
newY - totalHeight / 2 >= 0 &&
|
||||
newY + totalHeight / 2 <= h ) {
|
||||
boxPos[boxKey].x = newX;
|
||||
boxPos[boxKey].y = newY;
|
||||
}
|
||||
const pos = calcBoxPos(boxKey);
|
||||
g.clearRect(pos.x1, pos.y1, pos.x2, pos.y2);
|
||||
}
|
||||
});
|
||||
draw(boxes);
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on('touch', touchHandler);
|
||||
Bangle.on('drag', dragHandler);
|
||||
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Remove event handlers, stop draw timer, remove custom font if used
|
||||
Bangle.removeListener('touch', touchHandler);
|
||||
Bangle.removeListener('drag', dragHandler);
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
delete Graphics.prototype.setFontBrunoAce;
|
||||
// Restore original drawString function (no outlines)
|
||||
g.drawString = g_drawString;
|
||||
restoreSetColor();
|
||||
widgets.show();
|
||||
}
|
||||
});
|
||||
loadCustomFont();
|
||||
draw(boxes);
|
||||
};
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* 11. Main execution part
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
Bangle.loadWidgets();
|
||||
widgets.swipeOn();
|
||||
modSetColor();
|
||||
setup();
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.0 KiB |