Merge updates from official repository
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
# Needs to be ignored because it uses ESM export/import
|
||||
apps/gipy/pkg/gps.js
|
||||
apps/gipy/pkg/gps.d.ts
|
||||
apps/gipy/pkg/gps_bg.wasm.d.ts
|
||||
|
||||
# Needs to be ignored because it includes broken JS
|
||||
apps/health/chart.min.js
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ module.exports = {
|
|||
"getSerial": "readonly",
|
||||
"getTime": "readonly",
|
||||
"global": "readonly",
|
||||
"globalThis": "readonly",
|
||||
"HIGH": "readonly",
|
||||
"I2C1": "readonly",
|
||||
"Infinity": "readonly",
|
||||
|
|
|
|||
|
|
@ -5,3 +5,5 @@ updates:
|
|||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "gfwilliams"
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ and which gives information about the app for the Launcher.
|
|||
"src":"-myappid", // source file
|
||||
"type":"widget/clock/app/bootloader/...", // optional, default "app"
|
||||
// see 'type' in 'metadata.json format' below for more options/info
|
||||
"version":"1.23",
|
||||
"version":"0.01",
|
||||
// added by BangleApps loader on upload based on metadata.json
|
||||
"files:"file1,file2,file3",
|
||||
// added by BangleApps loader on upload - lists all files
|
||||
|
|
@ -249,7 +249,7 @@ and which gives information about the app for the Launcher.
|
|||
{ "id": "appid", // 7 character app id
|
||||
"name": "Readable name", // readable name
|
||||
"shortName": "Short name", // short name for launcher
|
||||
"version": "0v01", // the version of this app
|
||||
"version": "0.01", // the version of this app
|
||||
"description": "...", // long description (can contain markdown)
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
|
||||
|
|
@ -270,7 +270,8 @@ and which gives information about the app for the Launcher.
|
|||
// 'notify' - provides 'notify' library for showing notifications
|
||||
// 'locale' - provides 'locale' library for language-specific date/distance/etc
|
||||
// (a version of 'locale' is included in the firmware)
|
||||
"tags": "", // comma separated tag list for searching
|
||||
// 'defaultconfig' - a set of apps that will can be installed and will wipe out all previously installed apps
|
||||
"tags": "", // comma separated tag list for searching (don't include uppercase or spaces)
|
||||
// common types are:
|
||||
// 'clock' - it's a clock
|
||||
// 'widget' - it is (or provides) a widget
|
||||
|
|
@ -289,6 +290,7 @@ and which gives information about the app for the Launcher.
|
|||
"dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets
|
||||
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
|
||||
"provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message'
|
||||
"provides_features" : ["welcome"] // optional, this app provides some feature, used to ensure two aren't installed at once. Currently just 'welcome'
|
||||
"default" : true, // set if an app is the default implementer of something (a widget/module/etc)
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
|
|
|
|||
21
android.html
|
|
@ -85,6 +85,7 @@
|
|||
<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="health" data-tooltip="Apps for your health">Health</label>
|
||||
<label class="chip tooltip" filterid="fonts" data-tooltip="Extra fonts for non-latin languages">Fonts</label>
|
||||
<label class="chip tooltip" filterid="favourites" data-tooltip="Apps that you've liked ❤️">Favourites</label>
|
||||
</div>
|
||||
<div class="sort-nav hidden">
|
||||
|
|
@ -159,6 +160,10 @@
|
|||
<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-nopacket">
|
||||
<i class="form-icon"></i> File Upload Compatibility mode (disables binary packet upload)
|
||||
</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/>
|
||||
|
|
@ -187,14 +192,18 @@
|
|||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
<h3>Device info</h3>
|
||||
<div id="more-deviceinfo-content"></div>
|
||||
<h3>Device info</h3>
|
||||
<div id="more-deviceinfo-content"></div>
|
||||
<div class="editor--terminal">
|
||||
<div class="editor__canvas" style="position:relative;height:20rem;display:none;"></div>
|
||||
<button class="btn" id="terminalEnable">Enable Terminal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="floating hidden">
|
||||
<!-- Install button, hidden by default -->
|
||||
<!-- PWA Install button, hidden by default -->
|
||||
<div id="installContainer" class="hidden">
|
||||
<button id="butInstall" type="button">
|
||||
Install
|
||||
|
|
@ -203,7 +212,7 @@
|
|||
</footer>
|
||||
|
||||
<script src="webtools/puck.js"></script>
|
||||
<script src="webtools/heatshrink.js"></script>
|
||||
<script src="webtools/heatshrink.js"></script>
|
||||
<script src="core/lib/marked.min.js"></script>
|
||||
<script src="core/lib/espruinotools.js"></script>
|
||||
<script src="core/js/utils.js"></script>
|
||||
|
|
@ -416,7 +425,7 @@ if (el) el.addEventListener("click", event=>{
|
|||
if (webrtc) showWebRTCID(webrtc.peerId);
|
||||
else {
|
||||
webrtc = webrtcInit({
|
||||
bridge:true,
|
||||
bridge:true,
|
||||
onStatus : function(s) {
|
||||
showToast(s);
|
||||
},
|
||||
|
|
@ -432,7 +441,7 @@ if (el) el.addEventListener("click", event=>{
|
|||
onPortDisconnect : function(serialPort) {
|
||||
},
|
||||
onPortWrite : function(data, cb) {
|
||||
Puck.write(data, cb);
|
||||
Puck.write(data, cb);
|
||||
}
|
||||
});
|
||||
connection.on("data", function(d) {
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Display time, even on Thursday
|
||||
0.06: Fix light theme issue, where widgets would end up on a light strip
|
||||
0.07: Minor code improvements
|
||||
0.08: Support Fast Loading
|
||||
|
|
|
|||
|
|
@ -1,146 +1,162 @@
|
|||
// get 12 hour status, code from barclock
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
{
|
||||
// get 12 hour status, code from barclock
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
|
||||
// define background
|
||||
var imgBg = require("heatshrink").decompress(atob("2GwgJC/AH4A/AH4A/AH4A/AH4A/ACcGAhAV/Cp3gvdug+Gj0AgeABYMBAQMIggVEg/w/9/h/Gn8As3ACpk559zznmseAs0B13nq/Rie+uodCIIUZw9hzFmv+AgcCmco7MRilow1ACpN8gFhwMilFRCoMowgVEIIVhIINhwFg4GiCpfw/dhx/mn4uBCoXRhWktAVFTIVhw9mj8YseDkUnqPEoeuugVEAAlgSgICBACAVC8AUQCQQVSAEsD/4ASeYgA/ACkHNiK5Cj4VR/AVBng+RCQVwCqMOAQPhIKOHgEB44VR8YVBx4VR+eAgOfCqPxwEDCqX5CoKvS/PAgc/YqQVU/gV/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/CsMfCqP4CoOfCqP54EBx4VR+OAgPPCqPzwEA44VR4cAgHhCqMHCoNwAQIAPjwCBngVRvgCBV6XwCoMHCqPAHyIA/AEigEf4IAOkAEDoAPJWAtA+PHv+Al6uPCofAGAgALoHz51/8AVT+IVS+4VPpMR73woH27n/8Eh8+ZmadIqsoyGICofAkMUktJFZAVBzgVBv34YgMhi8RkIVJnGQIIN8/H34FB8kJiIVIkVEyGQkF8/Pj4GBkhBKCoOexEQvHx8fBgMXzMxTJkICoXCVx8AggDGABsD/4AB/AVQAH4APA"));
|
||||
// define background
|
||||
const imgBg = require("heatshrink").decompress(atob("2GwgJC/AH4A/AH4A/AH4A/AH4A/ACcGAhAV/Cp3gvdug+Gj0AgeABYMBAQMIggVEg/w/9/h/Gn8As3ACpk559zznmseAs0B13nq/Rie+uodCIIUZw9hzFmv+AgcCmco7MRilow1ACpN8gFhwMilFRCoMowgVEIIVhIINhwFg4GiCpfw/dhx/mn4uBCoXRhWktAVFTIVhw9mj8YseDkUnqPEoeuugVEAAlgSgICBACAVC8AUQCQQVSAEsD/4ASeYgA/ACkHNiK5Cj4VR/AVBng+RCQVwCqMOAQPhIKOHgEB44VR8YVBx4VR+eAgOfCqPxwEDCqX5CoKvS/PAgc/YqQVU/gV/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/CsMfCqP4CoOfCqP54EBx4VR+OAgPPCqPzwEA44VR4cAgHhCqMHCoNwAQIAPjwCBngVRvgCBV6XwCoMHCqPAHyIA/AEigEf4IAOkAEDoAPJWAtA+PHv+Al6uPCofAGAgALoHz51/8AVT+IVS+4VPpMR73woH27n/8Eh8+ZmadIqsoyGICofAkMUktJFZAVBzgVBv34YgMhi8RkIVJnGQIIN8/H34FB8kJiIVIkVEyGQkF8/Pj4GBkhBKCoOexEQvHx8fBgMXzMxTJkICoXCVx8AggDGABsD/4AB/AVQAH4APA"));
|
||||
|
||||
// define fonts
|
||||
// reg number first char 48 28 by 41
|
||||
var fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
// tiny font for percentage first char 48 6 by 8
|
||||
//var fontTiny = atob("AH6BgYF+ACFB/wEBAGGDhYlxAEKBkZFuAAx0hP8EAPqRkZGOAH6RkZFOAICHmKDAAG6RkZFuAHKJiYl+AAAAAAAAAAAAAAAA");
|
||||
// date font first char 48 12 by 15
|
||||
var fontDate = atob("AAAAAfv149wAeADwAeADwAeADvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAPHn9/AAAAAAP0A9wweGDwweGDwweGDvAL8AAAAAAAAAAAgwOGDwweGDwweGDvHp98AAAAA/gB6AAwAGAAwAGAAwAGAPHj9/AAAAAfgF6BwweGDwweGDwweGDgHoB+AAAAAfv169wweGDwweGDwweGDgHoB+AAAAAAAAAAgAGAAwAGAAwAGAAvHh9/AAAAAfv169wweGDwweGDwweGDvHr9+AAAAAfgF6BwweGDwweGDwweGDvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
// define fonts
|
||||
// reg number first char 48 28 by 41
|
||||
const fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
// tiny font for percentage first char 48 6 by 8
|
||||
//var fontTiny = atob("AH6BgYF+ACFB/wEBAGGDhYlxAEKBkZFuAAx0hP8EAPqRkZGOAH6RkZFOAICHmKDAAG6RkZFuAHKJiYl+AAAAAAAAAAAAAAAA");
|
||||
// date font first char 48 12 by 15
|
||||
const fontDate = atob("AAAAAfv149wAeADwAeADwAeADvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAPHn9/AAAAAAP0A9wweGDwweGDwweGDvAL8AAAAAAAAAAAgwOGDwweGDwweGDvHp98AAAAA/gB6AAwAGAAwAGAAwAGAPHj9/AAAAAfgF6BwweGDwweGDwweGDgHoB+AAAAAfv169wweGDwweGDwweGDgHoB+AAAAAAAAAAgAGAAwAGAAwAGAAvHh9/AAAAAfv169wweGDwweGDwweGDvHr9+AAAAAfgF6BwweGDwweGDwweGDvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
|
||||
// define days of the week images
|
||||
var imgMon = E.toArrayBuffer(atob("Ig8BgHwfD5AvB8HD8z8wMPzPzMQzM/M/DMz8z8c7f7f7z////3Oz+3+PzPzPw/M/M/D8z8z8PzPzPw/vB8/n/8H3/A=="));
|
||||
var imgTue = E.toArrayBuffer(atob("Ig8BwDv9wDAOfmgf/5+Z///n5n/5+fmf/n5+Z//fv9oH////Af37/b/+fn5n/5+fmf/n5+Z/+fn5n/5/g+gfn+D8AA=="));
|
||||
var imgWed = E.toArrayBuffer(atob("Ig8Bf7gHgM/NA9Az8z/z8PzP/Pw/M/8/D8z/z8c7QPf7z+A//3O3/3+MzP/PwzM/8/D8z/z8PzP/PxAtA9A4B4B4DA=="));
|
||||
var imgThu = E.toArrayBuffer(atob("Ig8BgHf7f6Ac/M/P/z8z8//PzPzz8/M/PPz8z8+/QLf7/+A///v3+3+8/PzPzz8/M/PPz8z88/PzPzz8/vB/P3/8HA=="));
|
||||
var imgFri = E.toArrayBuffer(atob("Ig8B/wDwP7+geg/P5/5+c/n/n5z+f+fnP5/5+c/oHoF7/AfAf/7/7/+/n/k/z+f+R/P5/5j8/n/nHz+/++PP7//8+A=="));
|
||||
var imgSat = E.toArrayBuffer(atob("Ig8B4DwDwDgOgXAJ/5+f/n/n5/+f+fn55/5+fnoHoF/fAfAf//+b/f3/5n5+f/mfn5/+Z+fn//n5+eAef358B7//nA=="));
|
||||
var imgSun = E.toArrayBuffer(atob("Ig8BwHf7D7Ac/MHD/z8wMP/PzMQ/8/M/D/z8z8QPf7f6A/////83+3+/zPzPz/M/M/P8z8z8//PzPwA/B8/oD8H3/A=="));
|
||||
// define days of the week images
|
||||
const imgMon = E.toArrayBuffer(atob("Ig8BgHwfD5AvB8HD8z8wMPzPzMQzM/M/DMz8z8c7f7f7z////3Oz+3+PzPzPw/M/M/D8z8z8PzPzPw/vB8/n/8H3/A=="));
|
||||
const imgTue = E.toArrayBuffer(atob("Ig8BwDv9wDAOfmgf/5+Z///n5n/5+fmf/n5+Z//fv9oH////Af37/b/+fn5n/5+fmf/n5+Z/+fn5n/5/g+gfn+D8AA=="));
|
||||
const imgWed = E.toArrayBuffer(atob("Ig8Bf7gHgM/NA9Az8z/z8PzP/Pw/M/8/D8z/z8c7QPf7z+A//3O3/3+MzP/PwzM/8/D8z/z8PzP/PxAtA9A4B4B4DA=="));
|
||||
const imgThu = E.toArrayBuffer(atob("Ig8BgHf7f6Ac/M/P/z8z8//PzPzz8/M/PPz8z8+/QLf7/+A///v3+3+8/PzPzz8/M/PPz8z88/PzPzz8/vB/P3/8HA=="));
|
||||
const imgFri = E.toArrayBuffer(atob("Ig8B/wDwP7+geg/P5/5+c/n/n5z+f+fnP5/5+c/oHoF7/AfAf/7/7/+/n/k/z+f+R/P5/5j8/n/nHz+/++PP7//8+A=="));
|
||||
const imgSat = E.toArrayBuffer(atob("Ig8B4DwDwDgOgXAJ/5+f/n/n5/+f+fn55/5+fnoHoF/fAfAf//+b/f3/5n5+f/mfn5/+Z+fn//n5+eAef358B7//nA=="));
|
||||
const imgSun = E.toArrayBuffer(atob("Ig8BwHf7D7Ac/MHD/z8wMP/PzMQ/8/M/D/z8z8QPf7f6A/////83+3+/zPzPz/M/M/P8z8z8//PzPwA/B8/oD8H3/A=="));
|
||||
|
||||
|
||||
|
||||
// define icons
|
||||
var imgSep = E.toArrayBuffer(atob("BhsBAAAAAA///////////////AAAAAAA"));
|
||||
//var imgPercent = E.toArrayBuffer(atob("BwcBuq7ffbqugA=="));
|
||||
var img24hr = E.toArrayBuffer(atob("EwgBj7vO53na73tcDtu9uDev7vA93g=="));
|
||||
var imgPM = E.toArrayBuffer(atob("EwgB+HOfdnPu1X3ar4dV9+q+/bfftg=="));
|
||||
// define icons
|
||||
const imgSep = E.toArrayBuffer(atob("BhsBAAAAAA///////////////AAAAAAA"));
|
||||
//var imgPercent = E.toArrayBuffer(atob("BwcBuq7ffbqugA=="));
|
||||
const img24hr = E.toArrayBuffer(atob("EwgBj7vO53na73tcDtu9uDev7vA93g=="));
|
||||
const imgPM = E.toArrayBuffer(atob("EwgB+HOfdnPu1X3ar4dV9+q+/bfftg=="));
|
||||
|
||||
//vars
|
||||
var separator = true;
|
||||
var is24hr = !is12Hour;
|
||||
var leadingZero = true;
|
||||
//vars
|
||||
let separator = true;
|
||||
let is24hr = !is12Hour;
|
||||
let leadingZero = true;
|
||||
|
||||
//the following 2 sections are used from waveclk to schedule minutely updates
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
//the following 2 sections are used from waveclk to schedule minutely updates
|
||||
// timeout used to update every minute
|
||||
let drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function drawBackground() {
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
g.clear();
|
||||
g.drawImage(imgBg,0,0);
|
||||
g.reset();
|
||||
}
|
||||
|
||||
function draw(){
|
||||
drawBackground();
|
||||
var date = new Date();
|
||||
var h = date.getHours(), m = date.getMinutes();
|
||||
var d = date.getDate(), w = date.getDay();
|
||||
g.reset();
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
|
||||
//draw 24 hr indicator and 12 hr specific behavior
|
||||
if (is24hr){
|
||||
g.drawImage(img24hr,32, 65);
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
}
|
||||
} else if (h > 12) {
|
||||
g.drawImage(imgPM,40, 70);
|
||||
h = h - 12;
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
} else if (h === 0) {
|
||||
// display 12:00 instead of 00:00 for 12 hr mode
|
||||
h = "12";
|
||||
}
|
||||
|
||||
//draw separator
|
||||
if (separator){
|
||||
g.drawImage(imgSep, 85,98);}
|
||||
|
||||
//draw day of week
|
||||
var imgW = null;
|
||||
if (w == 0) {imgW = imgSun;}
|
||||
if (w == 1) {imgW = imgMon;}
|
||||
if (w == 2) {imgW = imgTue;}
|
||||
if (w == 3) {imgW = imgWed;}
|
||||
if (w == 4) {imgW = imgThu;}
|
||||
if (w == 5) {imgW = imgFri;}
|
||||
if (w == 6) {imgW = imgSat;}
|
||||
g.drawImage(imgW, 85, 63);
|
||||
|
||||
|
||||
// draw nums
|
||||
// draw time
|
||||
g.setColor(0,0,0);
|
||||
g.setBgColor(1,1,1);
|
||||
g.setFontCustom(fontNum, 48, 28, 41);
|
||||
if (h<10) {
|
||||
if (leadingZero) {
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
}
|
||||
g.drawString(h, 25, 90, true);
|
||||
g.drawString(("0"+m).substr(-2), 92, 90, true);
|
||||
// draw date
|
||||
g.setFontCustom(fontDate, 48, 12, 15);
|
||||
g.drawString(("0"+d).substr(-2), 123,63, true);
|
||||
|
||||
// widget redraw
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* This watch is mostly dark, it does not make sense to respect the
|
||||
* light theme as you end up with a white strip at the top for the
|
||||
* widgets and black watch. So set the colours to the dark theme.
|
||||
*
|
||||
*/
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
draw();
|
||||
|
||||
//the following section is also from waveclk
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
// schedule a draw for the next minute
|
||||
let queueDraw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
};
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
let drawBackground = function() {
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.setColor(1, 1, 1);
|
||||
g.clear();
|
||||
g.drawImage(imgBg, 0, 0);
|
||||
g.reset();
|
||||
};
|
||||
|
||||
let draw = function() {
|
||||
drawBackground();
|
||||
let date = new Date();
|
||||
let h = date.getHours(),
|
||||
m = date.getMinutes();
|
||||
let d = date.getDate(),
|
||||
w = date.getDay();
|
||||
g.reset();
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.setColor(1, 1, 1);
|
||||
|
||||
//draw 24 hr indicator and 12 hr specific behavior
|
||||
if (is24hr){
|
||||
g.drawImage(img24hr,32, 65);
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
}
|
||||
} else if (h > 12) {
|
||||
g.drawImage(imgPM,40, 70);
|
||||
h = h - 12;
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
} else if (h === 0) {
|
||||
// display 12:00 instead of 00:00 for 12 hr mode
|
||||
h = "12";
|
||||
}
|
||||
|
||||
//draw separator
|
||||
if (separator) {
|
||||
g.drawImage(imgSep, 85, 98);
|
||||
}
|
||||
|
||||
//draw day of week
|
||||
let imgW = null;
|
||||
if (w == 0) {imgW = imgSun;}
|
||||
if (w == 1) {imgW = imgMon;}
|
||||
if (w == 2) {imgW = imgTue;}
|
||||
if (w == 3) {imgW = imgWed;}
|
||||
if (w == 4) {imgW = imgThu;}
|
||||
if (w == 5) {imgW = imgFri;}
|
||||
if (w == 6) {imgW = imgSat;}
|
||||
g.drawImage(imgW, 85, 63);
|
||||
|
||||
|
||||
// draw nums
|
||||
// draw time
|
||||
g.setColor(0, 0, 0);
|
||||
g.setBgColor(1, 1, 1);
|
||||
g.setFontCustom(fontNum, 48, 28, 41);
|
||||
if (h < 10) {
|
||||
if (leadingZero) {
|
||||
h = ("0" + h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
}
|
||||
g.drawString(h, 25, 90, true);
|
||||
g.drawString(("0" + m).substr(-2), 92, 90, true);
|
||||
// draw date
|
||||
g.setFontCustom(fontDate, 48, 12, 15);
|
||||
g.drawString(("0" + d).substr(-2), 123, 63, true);
|
||||
|
||||
// widget redraw
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
};
|
||||
|
||||
/**
|
||||
* This watch is mostly dark, it does not make sense to respect the
|
||||
* light theme as you end up with a white strip at the top for the
|
||||
* widgets and black watch. So set the colours to the dark theme.
|
||||
*
|
||||
*/
|
||||
g.setTheme({
|
||||
bg: "#000",
|
||||
fg: "#fff",
|
||||
dark: true
|
||||
}).clear();
|
||||
draw();
|
||||
|
||||
//the following section is also from waveclk
|
||||
let onLCDPower = on => {
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
};
|
||||
Bangle.on('lcdPower', onLCDPower);
|
||||
|
||||
Bangle.setUI({
|
||||
mode: "clock",
|
||||
remove: function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
Bangle.removeListener('lcdPower', onLCDPower);
|
||||
}
|
||||
});
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"shortName":"93 Dub",
|
||||
"icon": "93dub.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"version": "0.02",
|
||||
"description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.",
|
||||
"icon": "app.png",
|
||||
"tags": "Color,input,buttons,touch,UI",
|
||||
"tags": "color,input,buttons,touch,ui",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"UI4swatch_icon.png"},{"url":"UI4swatch_s1.png"}],
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
"version": "0.04",
|
||||
"description": "Wrist mounted ukulele chords",
|
||||
"icon": "app.png",
|
||||
"tags": "uke, chords",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"tags": "uke,chords",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"Uke.app.js","url":"app.js"},
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@
|
|||
var dateStr = require("locale").date(date);
|
||||
// draw time
|
||||
g.setFontAlign(0,0).setFont("Vector",48);
|
||||
g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
|
||||
g.clearRect(0,y-20,g.getWidth(),y+25); // clear the background
|
||||
g.drawString(timeStr,x,y);
|
||||
// draw date
|
||||
y += 35;
|
||||
y += 30;
|
||||
g.setFontAlign(0,0).setFont("6x8");
|
||||
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
|
||||
g.drawString(dateStr,x,y);
|
||||
|
|
@ -41,6 +41,8 @@
|
|||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({mode:"clock", remove:function() {
|
||||
// free any memory we allocated to allow fast loading
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
|||
|
|
@ -29,5 +29,4 @@
|
|||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -109,4 +109,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,3 +15,7 @@
|
|||
0.13: Show day of the week in date
|
||||
0.14: Fixed "Today" and "Yesterday" wrongly displayed for allDay events on some time zones
|
||||
0.15: Minor code improvements
|
||||
0.16: Correct date for all day events in negative timezones, improve locale display
|
||||
0.17: Fixed "Today" and "Tomorrow" labels displaying in non-current weeks
|
||||
0.18: Correct date in clockinfo for all-day events in negative timezones
|
||||
0.19: Change clockinfo title truncation to preserve images
|
||||
|
|
|
|||
|
|
@ -64,8 +64,12 @@
|
|||
|
||||
agenda.forEach((entry, i) => {
|
||||
|
||||
var title = entry.title.slice(0,12);
|
||||
var date = new Date(entry.timestamp*1000);
|
||||
var title = g.setFont("6x8").wrapString(entry.title,100)[0];
|
||||
// All day events are always in UTC and always start at 00:00:00, so we
|
||||
// need to "undo" the timezone offsetting to make sure that the day is
|
||||
// correct.
|
||||
var offset = entry.allDay ? new Date().getTimezoneOffset() * 60 : 0
|
||||
var date = new Date((entry.timestamp+offset)*1000);
|
||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
|
||||
var color = "#"+(0x1000000+Number(entry.color)).toString(16).padStart(6,"0");
|
||||
|
|
|
|||
|
|
@ -30,20 +30,26 @@ var settings = require("Storage").readJSON("agenda.settings.json",true)||{};
|
|||
|
||||
CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
|
||||
|
||||
function getDate(timestamp) {
|
||||
return new Date(timestamp*1000);
|
||||
function getDate(timestamp, allDay) {
|
||||
// All day events are always in UTC and always start at 00:00:00, so we
|
||||
// need to "undo" the timezone offsetting to make sure that the day is
|
||||
// correct.
|
||||
var offset = allDay ? new Date().getTimezoneOffset() * 60 : 0
|
||||
return new Date((timestamp+offset)*1000);
|
||||
}
|
||||
|
||||
function formatDay(date) {
|
||||
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
|
||||
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/,*\s*\d\d\d\d/,"");
|
||||
if (!settings.useToday) {
|
||||
return formattedDate;
|
||||
}
|
||||
const today = new Date(Date.now());
|
||||
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
|
||||
if (date.getDate() == today.getDate())
|
||||
return /*LANG*/"Today ";
|
||||
else {
|
||||
const tomorrow = new Date(Date.now() + 86400 * 1000);
|
||||
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
if (date.getDate() == tomorrow.getDate()) {
|
||||
return /*LANG*/"Tomorrow ";
|
||||
}
|
||||
return formattedDate;
|
||||
|
|
@ -57,8 +63,9 @@ function formatDateLong(date, includeDay, allDay) {
|
|||
}
|
||||
return shortTime;
|
||||
}
|
||||
|
||||
function formatDateShort(date, allDay) {
|
||||
return formatDay(date)+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
|
||||
return formatDay(date)+(allDay?"":" "+Locale.time(date,1)+Locale.meridian(date));
|
||||
}
|
||||
|
||||
var lines = [];
|
||||
|
|
@ -69,16 +76,19 @@ function showEvent(ev) {
|
|||
//var lines = [];
|
||||
if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10);
|
||||
var titleCnt = lines.length;
|
||||
var start = getDate(ev.timestamp);
|
||||
var end = getDate((+ev.timestamp) + (+ev.durationInSeconds));
|
||||
var start = getDate(ev.timestamp, ev.allDay);
|
||||
// All day events end at the midnight boundary of the following day. Here, we
|
||||
// subtract one second for all day events so the days display correctly.
|
||||
const allDayEndCorrection = ev.allDay ? 1 : 0;
|
||||
var end = getDate((+ev.timestamp) + (+ev.durationInSeconds) - allDayEndCorrection, ev.allDay);
|
||||
var includeDay = true;
|
||||
if (titleCnt) lines.push(""); // add blank line after title
|
||||
if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
|
||||
includeDay = false;
|
||||
if(includeDay && ev.allDay) {
|
||||
//single day all day (average to avoid getting previous day)
|
||||
if(!includeDay && ev.allDay) {
|
||||
//single day all day
|
||||
lines = lines.concat(
|
||||
g.wrapString(formatDateLong(new Date((start+end)/2), includeDay, ev.allDay), g.getWidth()-10));
|
||||
g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10));
|
||||
} else if(includeDay || ev.allDay) {
|
||||
lines = lines.concat(
|
||||
/*LANG*/"Start"+":",
|
||||
|
|
@ -137,7 +147,7 @@ function showList() {
|
|||
if (!ev) return;
|
||||
var isPast = false;
|
||||
var x = r.x+2, title = ev.title;
|
||||
var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
|
||||
var body = formatDateShort(getDate(ev.timestamp, ev.allDay),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
|
||||
if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
|
||||
if (title) g.setFontAlign(-1,-1).setFont(fontBig)
|
||||
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.15",
|
||||
"version": "0.19",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
|||
|
|
@ -68,4 +68,4 @@ function buildMainMenu() {
|
|||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -51,3 +51,9 @@
|
|||
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
|
||||
0.47: Fix wrap around when snoozed through midnight
|
||||
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.
|
||||
0.49: fix uncaught error if no scroller (Bangle 1). Would happen when trying
|
||||
to select an alarm in the main menu.
|
||||
0.50: Bangle.js 2: Long touch of alarm in main menu toggle it on/off. Touching the icon on
|
||||
the right will do the same.
|
||||
0.51: Fix long-touch to enable alarm/timer not updating time (fix #3804)
|
||||
0.52: Allow deletion of alarms once they sound (like timers and events)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
|
|||
- `Disable All` → Disable _all_ enabled alarms & timers
|
||||
- `Delete All` → Delete _all_ alarms & timers
|
||||
|
||||
On Bangle.js 2 it's possible to toggle alarms, timers and events from the main menu. This is done by clicking the indicator icons of corresponding entries. Or long pressing anywhere on them.
|
||||
|
||||
## Creator
|
||||
|
||||
- [Gordon Williams](https://github.com/gfwilliams)
|
||||
|
|
@ -29,6 +31,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
|
|||
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
|
||||
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
|
||||
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
|
||||
- [thyttan](https://github.com/thyttan) - Toggle alarms directly from main menu.
|
||||
|
||||
## Attributions
|
||||
|
||||
|
|
|
|||
|
|
@ -87,14 +87,24 @@ function showMainMenu(scroll, group, scrollback) {
|
|||
};
|
||||
const getGroups = settings.showGroup && !group;
|
||||
const groups = getGroups ? {} : undefined;
|
||||
var showAlarm;
|
||||
const getIcon = (e)=>{return e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff);};
|
||||
|
||||
alarms.forEach((e, index) => {
|
||||
showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
|
||||
const showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
|
||||
if(showAlarm) {
|
||||
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, undefined, scroller.scroll, group)
|
||||
const label = trimLabel(getLabel(e),40);
|
||||
menu[label] = {
|
||||
value: e.on,
|
||||
onchange: (v, touch) => {
|
||||
if (touch && (2==touch.type || 145<touch.x)) { // Long touch or touched icon.
|
||||
e.on = v;
|
||||
if (e.on) prepareForSave(e, index);
|
||||
saveAndReload();
|
||||
} else {
|
||||
setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller?scroller.scroll:undefined, group);
|
||||
}
|
||||
},
|
||||
format: v=>getIcon(e)
|
||||
};
|
||||
} else if (getGroups) {
|
||||
groups[e.group] = undefined;
|
||||
|
|
@ -102,7 +112,7 @@ function showMainMenu(scroll, group, scrollback) {
|
|||
});
|
||||
|
||||
if (!group) {
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller?scroller.scroll:undefined));
|
||||
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +293,6 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
|||
delete menu[/*LANG*/"Day"];
|
||||
delete menu[/*LANG*/"Month"];
|
||||
delete menu[/*LANG*/"Year"];
|
||||
delete menu[/*LANG*/"Delete After Expiration"];
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
|
|
@ -318,6 +327,14 @@ function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
|
|||
}
|
||||
}
|
||||
|
||||
function prepareForSave(alarm, alarmIndex) {
|
||||
if (alarm.timer) {
|
||||
prepareTimerForSave(alarm, alarmIndex, require("time_utils").decodeTime(alarm.timer));
|
||||
} else {
|
||||
prepareAlarmForSave(alarm, alarmIndex, require("time_utils").decodeTime(alarm.t));
|
||||
}
|
||||
}
|
||||
|
||||
function saveAndReload() {
|
||||
// Before saving revert the dow to the standard format (alarms only!)
|
||||
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||
|
|
@ -564,13 +581,7 @@ function enableAll(on) {
|
|||
if (confirm) {
|
||||
alarms.forEach((alarm, i) => {
|
||||
alarm.on = on;
|
||||
if (on) {
|
||||
if (alarm.timer) {
|
||||
prepareTimerForSave(alarm, i, require("time_utils").decodeTime(alarm.timer));
|
||||
} else {
|
||||
prepareAlarmForSave(alarm, i, require("time_utils").decodeTime(alarm.t));
|
||||
}
|
||||
}
|
||||
if (on) prepareForSave(alarm, i);
|
||||
});
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.48",
|
||||
"version": "0.52",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
|
|
|
|||
|
|
@ -48,4 +48,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,13 +2,26 @@ Alpine Navigator
|
|||
================
|
||||
App that performs GPS monitoring to track and display position relative to a given origin in realtime.
|
||||
|
||||

|
||||

|
||||
|
||||
[compass 5]
|
||||
|
||||
altitude
|
||||
[start 1] [current 2]
|
||||
|
||||
distance
|
||||
[from start 3] [track 4]
|
||||
|
||||
|
||||
[btn1 -- screen lock]
|
||||
[btn2 -- remove points]
|
||||
[btn3 -- pause]
|
||||
|
||||
Functions
|
||||
---------
|
||||
Note if you've not used GPS yet I suggest using one of the GPS apps to get your first fix and confirm as I've found that helps initially.
|
||||
Note if you've not used GPS yet, I suggest using one of the GPS apps to get your first fix and confirm, as I've found that helps initially.
|
||||
|
||||
The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. all your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit but I've kept it fairly conservative here as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
|
||||
The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. All your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit, but I've kept it fairly conservative here, as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
|
||||
|
||||
1. altitude at origin, this is displayed left of the centre.
|
||||
2. current altitude, displayed centre right
|
||||
|
|
@ -16,12 +29,12 @@ The GPS and magnetometer will be turned on and after a few moments, when the wat
|
|||
4. distance travelled, bottom right (meters)
|
||||
5. compass heading, at the top
|
||||
|
||||
For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time because the waypoints will be reduced when it reaches a set threshold so you may see the path smooth out slightly at intervals.
|
||||
For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time, because the waypoints will be reduced when it reaches a set threshold, so you may see the path smooth out slightly at intervals.
|
||||
|
||||
If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring but often just moving your wrist a bit is enough.
|
||||
If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring, but often just moving your wrist a bit is enough.
|
||||
|
||||
The buttons do the following:
|
||||
BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! soft and hard reset will both still work.
|
||||
BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! Soft and hard reset will both still work.
|
||||
BTN2: this removes all waypoints aside from the origin and your current location; sometimes during smaller journeys and walks, the GPS can give sporadic differences in locations because of the error margins of GPS and this can add noise to your route.
|
||||
BTN3: this will pause the GPS and magnetometer, useful for saving power for situations where you don't necessarily need to track parts of your route e.g. you're going indoors/shelter for some time. You'll know it's paused because the compass won't update it's reading and all the metrics will be blacked out on the screen.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Actually upload correct code
|
||||
0.03: Display sea-level pressure, too, and allow calibration
|
||||
0.04: Switch to using system code for pressure calibration
|
||||
0.05: Prompt before resetting calibration (stops long-press of button resetting calibration)
|
||||
|
|
@ -7,6 +7,7 @@ var R = Bangle.appRect;
|
|||
var y = R.y + R.h/2;
|
||||
var MEDIANLENGTH = 20;
|
||||
var avr = [];
|
||||
var updateDisplay = true;
|
||||
|
||||
function fmt(t) {
|
||||
if ((t > -100) && (t < 1000))
|
||||
|
|
@ -19,48 +20,57 @@ function fmt(t) {
|
|||
Bangle.on('pressure', function(e) {
|
||||
while (avr.length>MEDIANLENGTH) avr.pop();
|
||||
avr.unshift(e.altitude);
|
||||
let median = avr.slice().sort();
|
||||
if (!updateDisplay) return;
|
||||
let median = avr.slice().sort(), value;
|
||||
g.reset().clearRect(0,y-30,g.getWidth()-10,R.h);
|
||||
if (median.length>10) {
|
||||
var mid = median.length>>1;
|
||||
var value = E.sum(median.slice(mid-4,mid+5)) / 9;
|
||||
value = E.sum(median.slice(mid-4,mid+5)) / 9;
|
||||
} else {
|
||||
var value = median[median.length>>1];
|
||||
value = median[median.length>>1];
|
||||
}
|
||||
t = fmt(value);
|
||||
var t = fmt(value);
|
||||
|
||||
g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y);
|
||||
|
||||
let o = Bangle.getOptions();
|
||||
let sea = o.seaLevelPressure;
|
||||
t = sea.toFixed(1) + " " + e.temperature.toFixed(1);
|
||||
if (0) {
|
||||
/*if (0) {
|
||||
print("alt raw:", value.toFixed(1));
|
||||
print("temperature:", e.temperature);
|
||||
print("pressure:", e.pressure);
|
||||
print("sea pressure:", sea);
|
||||
}
|
||||
}*/
|
||||
g.setFont("Vector",25).setFontAlign(-1,0).drawString(t, 10, R.y+R.h - 35);
|
||||
});
|
||||
|
||||
function setPressure(m, a) {
|
||||
o = Bangle.getOptions();
|
||||
print(o);
|
||||
var o = Bangle.getOptions();
|
||||
//print(o);
|
||||
o.seaLevelPressure = o.seaLevelPressure * m + a;
|
||||
Bangle.setOptions(o);
|
||||
avr = [];
|
||||
}
|
||||
|
||||
print(g.getFonts());
|
||||
g.reset();
|
||||
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*/"STD", g.getWidth()-5, g.getHeight()/2);
|
||||
Bangle.setUI("updown", btn=> {
|
||||
if (!btn) setPressure(0, 1013.25);
|
||||
if (btn<0) setPressure(1, 1);
|
||||
if (btn>0) setPressure(1, -1);
|
||||
});
|
||||
function start() {
|
||||
g.reset();
|
||||
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.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"STD", g.getWidth()-5, g.getHeight()/2);
|
||||
updateDisplay = true;
|
||||
Bangle.setUI("updown", btn => {
|
||||
if (!btn) {
|
||||
updateDisplay = false;
|
||||
E.showPrompt(/*LANG*/"Set calibration to default?",{title:/*LANG*/"Altitude"}).then(function(reset) {
|
||||
start();
|
||||
if (reset) setPressure(0, 1013.25);
|
||||
});
|
||||
}
|
||||
if (btn<0) setPressure(1, 1);
|
||||
if (btn>0) setPressure(1, -1);
|
||||
});
|
||||
}
|
||||
start();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "altimeter",
|
||||
"name": "Altimeter",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors",
|
||||
|
|
|
|||
|
|
@ -5,3 +5,7 @@
|
|||
0.05: Fix support for dark theme + support widgets +
|
||||
add settings for widgets, order of drawing and hour hand length
|
||||
0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled
|
||||
0.07: Enable fast loading and queue updates to the second
|
||||
0.08: Restore redraw on charging event + fixup for safer fast-loading
|
||||
0.09: Add setting to show the weekday and not the year + add setting to hide the battery +
|
||||
changed to follow system them with setting for dark theme
|
||||
|
|
|
|||
|
|
@ -7,10 +7,19 @@
|
|||
* battery percentage (showing charge status with color)
|
||||
* turned off or swipeable widgets (choose in settings)
|
||||
|
||||

|
||||

|
||||
|
||||
*Default settings*
|
||||
|
||||

|
||||
|
||||
*Following system theme, with weekday shown and battery hidden*
|
||||
|
||||
## Settings
|
||||
|
||||
* whether to load widgets, or not; if widgets are loaded, they are swipeable from the top; if not, NO ACTIONS of widgets are available
|
||||
* date and battery can be printed both below hands (as if hands were physical) and above (more readable)
|
||||
* hour hand can be made slighly shorter to improve readability when minute hand is behind a number
|
||||
* show the weekday and not the year
|
||||
* hide the battery percentage; the font for the date is increased since there is more space
|
||||
* dark theme (enabled by default); disable to follow system theme
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 4.7 KiB |
|
|
@ -1,19 +1,29 @@
|
|||
{
|
||||
const defaultSettings = {
|
||||
loadWidgets : false,
|
||||
textAboveHands : false,
|
||||
shortHrHand : false
|
||||
shortHrHand : false,
|
||||
weekdayNoYear : false,
|
||||
noBattery : false,
|
||||
darkTheme : true
|
||||
};
|
||||
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
|
||||
|
||||
const origTheme = g.theme;
|
||||
if (settings.darkTheme) {
|
||||
g.setTheme({bg: "#000"});
|
||||
g.setTheme({fg: "#FFF"});
|
||||
}
|
||||
|
||||
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
||||
|
||||
const zahlpos=(function() {
|
||||
let z=[];
|
||||
let sk=1;
|
||||
for(let i=-10;i<50;i+=5){
|
||||
let win=i*2*Math.PI/60;
|
||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||
let win=i*2*Math.PI/60;
|
||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||
if(sk==3){xsk-=10;}
|
||||
if(sk==6){ysk-=10;}
|
||||
if(sk==9){xsk+=10;}
|
||||
|
|
@ -25,20 +35,17 @@ const zahlpos=(function() {
|
|||
return z;
|
||||
})();
|
||||
|
||||
let unlock = false;
|
||||
|
||||
function zeiger(len,dia,tim){
|
||||
const zeiger = function(len,dia,tim) {
|
||||
const x=c.x+ Math.cos(tim)*len/2,
|
||||
y=c.y + Math.sin(tim)*len/2,
|
||||
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
||||
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
||||
return pol;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function drawHands(d) {
|
||||
const drawHands = function(d) {
|
||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||
g.setColor(1,1,1);
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
if(h>12){
|
||||
h=h-12;
|
||||
|
|
@ -61,34 +68,70 @@ function drawHands(d) {
|
|||
g.fillPoly(sekz,true);
|
||||
}
|
||||
g.fillCircle(c.x,c.y,4);
|
||||
}
|
||||
};
|
||||
|
||||
function drawText(d) {
|
||||
g.setFont("Vector",10);
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
let dateStr = require("locale").date(d);
|
||||
g.drawString(dateStr, c.x, c.y+20, true);
|
||||
let batStr = Math.round(E.getBattery()/5)*5+"%";
|
||||
if (Bangle.isCharging()) {
|
||||
g.setBgColor(1,0,0);
|
||||
const drawText = function(d) {
|
||||
//g.setFont("Vector",10);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setColor(g.theme.fg);
|
||||
const dateStr = settings.weekdayNoYear
|
||||
? require("locale").dow(d, 1)+" "+d.getDate()+" "+require("locale").month(d, 1)
|
||||
: require("locale").date(d);
|
||||
const batStr = Math.round(E.getBattery()/5)*5+"%";
|
||||
if (settings.noBattery) {
|
||||
g.setFont("Vector",13);
|
||||
g.drawString(dateStr, c.x, c.y+25, true);
|
||||
} else {
|
||||
g.setFont("Vector",10);
|
||||
g.drawString(dateStr, c.x, c.y+20, true);
|
||||
if (Bangle.isCharging()) {
|
||||
g.setBgColor(1,0,0);
|
||||
}
|
||||
g.drawString(batStr, c.x, c.y+40, true);
|
||||
}
|
||||
g.drawString(batStr, c.x, c.y+40, true);
|
||||
}
|
||||
};
|
||||
|
||||
function drawNumbers() {
|
||||
const drawNumbers = function() {
|
||||
//draws the numbers on the screen
|
||||
g.setFont("Vector",20);
|
||||
g.setColor(1,1,1);
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
for(let i = 0;i<12;i++){
|
||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
|
||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function draw(){
|
||||
let drawTimeout;
|
||||
let queueMillis = 1000;
|
||||
let unlock = true;
|
||||
|
||||
const updateState = function() {
|
||||
if (Bangle.isLCDOn()) {
|
||||
if (!Bangle.isLocked()) {
|
||||
queueMillis = 1000;
|
||||
unlock = true;
|
||||
} else {
|
||||
queueMillis = 60000;
|
||||
unlock = false;
|
||||
}
|
||||
draw();
|
||||
} else {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const queueDraw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, queueMillis - (Date.now() % queueMillis));
|
||||
};
|
||||
|
||||
const draw = function() {
|
||||
// draw black rectangle in the middle to clear screen from scale and hands
|
||||
g.setColor(0,0,0);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
// prepare for drawing the text
|
||||
g.setFontAlign(0,0);
|
||||
|
|
@ -100,12 +143,13 @@ function draw(){
|
|||
} else {
|
||||
drawText(d); drawHands(d);
|
||||
}
|
||||
}
|
||||
queueDraw();
|
||||
};
|
||||
|
||||
//draws the scale once the app is startet
|
||||
function drawScale(){
|
||||
const drawScale = function() {
|
||||
// clear the screen
|
||||
g.setBgColor(0,0,0);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.clear();
|
||||
// draw the ticks of the scale
|
||||
for(let i=-14;i<47;i++){
|
||||
|
|
@ -113,40 +157,40 @@ function drawScale(){
|
|||
let d=2;
|
||||
if(i%5==0){d=5;}
|
||||
g.fillPoly(zeiger(300,d,win),true);
|
||||
g.setColor(0,0,0);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
g.setColor(1,1,1);
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//// main running sequence ////
|
||||
|
||||
// Show launcher when middle button pressed, and widgets that we're clock
|
||||
Bangle.setUI("clock");
|
||||
Bangle.setUI({
|
||||
mode: "clock",
|
||||
remove: function() {
|
||||
if (settings.darkTheme) g.setTheme(origTheme);
|
||||
Bangle.removeListener('lcdPower', updateState);
|
||||
Bangle.removeListener('lock', updateState);
|
||||
Bangle.removeListener('charging', draw);
|
||||
// We clear drawTimout after removing all listeners, because they can add one again
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
require("widget_utils").show();
|
||||
}
|
||||
});
|
||||
// Load widgets if needed, and make them show swipeable
|
||||
if (settings.loadWidgets) {
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn();
|
||||
} else if (global.WIDGETS) require("widget_utils").hide();
|
||||
// Clear the screen once, at startup
|
||||
drawScale();
|
||||
draw();
|
||||
|
||||
let secondInterval = setInterval(draw, 1000);
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
Bangle.on('lock',on=>{
|
||||
unlock = !on;
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
|
||||
draw(); // draw immediately
|
||||
});
|
||||
Bangle.on('charging',on=>{draw();});
|
||||
Bangle.on('lcdPower', updateState);
|
||||
Bangle.on('lock', updateState);
|
||||
Bangle.on('charging', draw); // Immediately redraw when charger (dis)connected
|
||||
|
||||
updateState();
|
||||
drawScale();
|
||||
draw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
{ "id": "andark",
|
||||
"name": "Analog Dark",
|
||||
"shortName":"AnDark",
|
||||
"version":"0.06",
|
||||
"version":"0.09",
|
||||
"description": "analog clock face without disturbing widgets",
|
||||
"icon": "andark_icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"allow_emulator": true,
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"andark_screen.png"}],
|
||||
"screenshots": [{"url":"andark_screen.png"},{"url":"andark_screen_light_weekday_nobatt.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"andark.app.js","url":"app.js"},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
const defaultSettings = {
|
||||
loadWidgets : false,
|
||||
textAboveHands : false,
|
||||
shortHrHand : false
|
||||
shortHrHand : false,
|
||||
weekdayNoYear : false,
|
||||
noBattery : false,
|
||||
darkTheme : true
|
||||
}
|
||||
let settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
|
||||
|
||||
|
|
@ -22,7 +25,19 @@
|
|||
value : !!settings.shortHrHand,
|
||||
onchange : v => { settings.shortHrHand=v; save();}
|
||||
},
|
||||
/*LANG*/'Show weekday not year': {
|
||||
value : !!settings.weekdayNoYear,
|
||||
onchange : v => { settings.weekdayNoYear=v; save();}
|
||||
},
|
||||
/*LANG*/'Hide the battery': {
|
||||
value : !!settings.noBattery,
|
||||
onchange : v => { settings.noBattery=v; save();}
|
||||
},
|
||||
/*LANG*/'Dark theme': {
|
||||
value : !!settings.darkTheme,
|
||||
onchange : v => { settings.darkTheme=v; save();}
|
||||
},
|
||||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -36,4 +36,14 @@
|
|||
0.34: Implement API for activity tracks fetching (Recorder app logs).
|
||||
0.35: Implement API to enable/disable acceleration data tracking.
|
||||
0.36: Move from wrapper function to {} and let - faster execution at boot
|
||||
Allow `calendar-` to take an array of items to remove
|
||||
Allow `calendar-` to take an array of items to remove
|
||||
0.37: Support Gadgetbridge canned responses
|
||||
0.38: Don't rewrite settings file on every boot!
|
||||
0.39: Move GB message handling into a library to reduce boot time from 40ms->13ms
|
||||
0.40: Ensure we send health 'activity' message to gadgetbridge (added 2v26)
|
||||
0.41: When using `actfetch`, fetch historical activity type too
|
||||
0.42: Add handling for android STREAM_MUSIC volume level info, emitting on
|
||||
arrival. (Needs Gadgetbridge nightly (either flavour) for now, or stable
|
||||
version 85 when it's out)
|
||||
0.43: Ensure listRecs doesn't list old-style recorded tracks (Otherwise Gadgetbridge fails parsing the filename)
|
||||
0.44: Pass HTTP request timeout to Gadgetbridge
|
||||
|
|
|
|||
|
|
@ -49,11 +49,21 @@ The boot code also provides some useful functions:
|
|||
* `body` the body of the HTTP request
|
||||
* `headers` an object of headers, eg `{HeaderOne : "headercontents"}`
|
||||
|
||||
`Bangle.http` returns a promise which contains:
|
||||
|
||||
```JS
|
||||
{
|
||||
t:"http",
|
||||
id: // the ID of this HTTP request
|
||||
resp: "...." // a string containing the response
|
||||
}
|
||||
```
|
||||
|
||||
eg:
|
||||
|
||||
```
|
||||
```JS
|
||||
Bangle.http("https://pur3.co.uk/hello.txt").then(data=>{
|
||||
console.log("Got ",data);
|
||||
console.log("Got ",data.resp);
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,350 +1,24 @@
|
|||
/* global GB */
|
||||
{
|
||||
let gbSend = function(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
let lastMsg; // for music messages - may not be needed now...
|
||||
let actInterval; // Realtime activity reporting interval when `act` is true
|
||||
let actHRMHandler; // For Realtime activity reporting
|
||||
let gpsState = {}; // keep information on GPS via Gadgetbridge
|
||||
|
||||
// this settings var is deleted after this executes to save memory
|
||||
let settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
//default alarm settings
|
||||
if (settings.rp == undefined) settings.rp = true;
|
||||
if (settings.as == undefined) settings.as = true;
|
||||
if (settings.vibrate == undefined) settings.vibrate = "..";
|
||||
require('Storage').writeJSON("android.settings.json", settings);
|
||||
// settings var is deleted after this executes to save memory
|
||||
let settings = Object.assign({rp:true,as:true,vibrate:".."},
|
||||
require("Storage").readJSON("android.settings.json",1)||{}
|
||||
);
|
||||
let _GB = global.GB;
|
||||
let fetchRecInterval;
|
||||
global.GB = (event) => {
|
||||
global.GB = e => {
|
||||
// feed a copy to other handlers if there were any
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},event));
|
||||
|
||||
|
||||
/* TODO: Call handling, fitness */
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() {
|
||||
Object.assign(event,{t:"add",positive:true, negative:true});
|
||||
// Detect a weird GadgetBridge bug and fix it
|
||||
// For some reason SMS messages send two GB notifications, with different sets of info
|
||||
if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
|
||||
// Mutate the other message
|
||||
event.id = lastMsg.id;
|
||||
}
|
||||
lastMsg = event;
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
|
||||
// {t:"find", n:bool} // find my phone
|
||||
"find" : function() {
|
||||
if (Bangle.findDeviceInterval) {
|
||||
clearInterval(Bangle.findDeviceInterval);
|
||||
delete Bangle.findDeviceInterval;
|
||||
}
|
||||
if (event.n) // Ignore quiet mode: we always want to find our watch
|
||||
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
|
||||
},
|
||||
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
|
||||
"musicstate" : function() {
|
||||
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
|
||||
},
|
||||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
Object.assign(event, {
|
||||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
// {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
|
||||
"alarm" : function() {
|
||||
//wipe existing GB alarms
|
||||
var sched;
|
||||
try { sched = require("sched"); } catch (e) {}
|
||||
if (!sched) return; // alarms may not be installed
|
||||
var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
|
||||
for (var i = 0; i < gbalarms.length; i++)
|
||||
sched.setAlarm(gbalarms[i].id, undefined);
|
||||
var alarms = sched.getAlarms();
|
||||
var time = new Date();
|
||||
var currentTime = time.getHours() * 3600000 +
|
||||
time.getMinutes() * 60000 +
|
||||
time.getSeconds() * 1000;
|
||||
for (var j = 0; j < event.d.length; j++) {
|
||||
// prevents all alarms from going off at once??
|
||||
var dow = event.d[j].rep;
|
||||
var rp = false;
|
||||
if (!dow) {
|
||||
dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
} else {
|
||||
rp = true;
|
||||
}
|
||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||
var a = require("sched").newDefaultAlarm();
|
||||
a.id = "gb"+j;
|
||||
a.appid = "gbalarms";
|
||||
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
|
||||
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||
a.rp = rp;
|
||||
a.last = last;
|
||||
alarms.push(a);
|
||||
}
|
||||
sched.setAlarms(alarms);
|
||||
sched.reload();
|
||||
},
|
||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||
//add and remove events based on activity on phone (pebble-like)
|
||||
// {t:"calendar", id:int, type:int, timestamp:seconds, durationInSeconds, title:string, description:string,location:string,calName:string.color:int,allDay:bool
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
var i = cal.findIndex(e=>e.id==event.id);
|
||||
if(i<0)
|
||||
cal.push(event);
|
||||
else
|
||||
cal[i] = event;
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
// {t:"calendar-", id:int}
|
||||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
if (Array.isArray(event.id))
|
||||
cal = cal.filter(e=>!event.id.includes(e.id));
|
||||
else
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
//triggered by GB, send all ids
|
||||
// { t:"force_calendar_sync_start" }
|
||||
"force_calendar_sync_start" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
|
||||
},
|
||||
// {t:"http",resp:"......",[id:"..."]}
|
||||
"http":function() {
|
||||
//get the promise and call the promise resolve
|
||||
if (Bangle.httpRequest === undefined) return;
|
||||
var request=Bangle.httpRequest[event.id];
|
||||
if (request === undefined) return; //already timedout or wrong id
|
||||
delete Bangle.httpRequest[event.id];
|
||||
clearTimeout(request.t); //t = timeout variable
|
||||
if(event.err!==undefined) //if is error
|
||||
request.j(event.err); //r = reJect function
|
||||
else
|
||||
request.r(event); //r = resolve function
|
||||
},
|
||||
// {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
|
||||
"gps": function() {
|
||||
if (!settings.overwriteGps) return;
|
||||
// modify event for using it as Bangle GPS event
|
||||
delete event.t;
|
||||
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"}
|
||||
"is_gps_active": function() {
|
||||
gbSend({ t: "gps_power", status: Bangle.isGPSOn() });
|
||||
},
|
||||
// {t:"act", hrm:bool, stp:bool, int:int}
|
||||
"act": function() {
|
||||
if (actInterval) clearInterval(actInterval);
|
||||
actInterval = undefined;
|
||||
if (actHRMHandler)
|
||||
actHRMHandler = undefined;
|
||||
Bangle.setHRMPower(event.hrm,"androidact");
|
||||
if (!(event.hrm || event.stp)) return;
|
||||
if (!isFinite(event.int)) event.int=1;
|
||||
var lastSteps = Bangle.getStepCount();
|
||||
var lastBPM = 0;
|
||||
actHRMHandler = function(e) {
|
||||
lastBPM = e.bpm;
|
||||
};
|
||||
Bangle.on('HRM',actHRMHandler);
|
||||
actInterval = setInterval(function() {
|
||||
var steps = Bangle.getStepCount();
|
||||
gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
|
||||
lastSteps = steps;
|
||||
}, event.int*1000);
|
||||
},
|
||||
// {t:"actfetch", ts:long}
|
||||
"actfetch": function() {
|
||||
gbSend({t: "actfetch", state: "start"});
|
||||
var actCount = 0;
|
||||
var actCb = function(r) {
|
||||
// The health lib saves the samples at the start of the 10-minute block
|
||||
// However, GB expects them at the end of the block, so let's offset them
|
||||
// here to keep a consistent API in the health lib
|
||||
var sampleTs = r.date.getTime() + 600000;
|
||||
if (sampleTs >= event.ts) {
|
||||
gbSend({
|
||||
t: "act",
|
||||
ts: sampleTs,
|
||||
stp: r.steps,
|
||||
hrm: r.bpm,
|
||||
mov: r.movement
|
||||
});
|
||||
actCount++;
|
||||
}
|
||||
}
|
||||
if (event.ts != 0) {
|
||||
require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
|
||||
} else {
|
||||
require("health").readFullDatabase(actCb);
|
||||
}
|
||||
gbSend({t: "actfetch", state: "end", count: actCount});
|
||||
},
|
||||
//{t:"listRecs", id:"20230616a"}
|
||||
"listRecs": function() {
|
||||
let recs = require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).map(s => s.slice(12, 21));
|
||||
if (event.id.length > 2) { // Handle if there was no id supplied. Then we send a list all available recorder logs back.
|
||||
let firstNonsyncedIdx = recs.findIndex((logId) => logId > event.id);
|
||||
if (-1 == firstNonsyncedIdx) {
|
||||
recs = []
|
||||
} else {
|
||||
recs = recs.slice(firstNonsyncedIdx);
|
||||
}
|
||||
}
|
||||
gbSend({t:"actTrksList", list: recs}); // TODO: split up in multiple transmissions?
|
||||
},
|
||||
//{t:"fetchRec", id:"20230616a"}
|
||||
"fetchRec": function() {
|
||||
// TODO: Decide on what names keys should have.
|
||||
if (fetchRecInterval) {
|
||||
clearInterval(fetchRecInterval);
|
||||
fetchRecInterval = undefined;
|
||||
}
|
||||
if (event.id=="stop") {
|
||||
return
|
||||
} else {
|
||||
let log = require("Storage").open("recorder.log"+event.id+".csv","r");
|
||||
let lines = "init";// = log.readLine();
|
||||
let pkgcnt = 0;
|
||||
gbSend({t:"actTrk", log:event.id, lines:"erase", cnt:pkgcnt}); // "erase" will prompt Gadgetbridge to erase the contents of a already fetched log so we can rewrite it without keeping lines from the previous (probably failed) fetch.
|
||||
let sendlines = ()=>{
|
||||
lines = log.readLine();
|
||||
for (var i = 0; i < 3; i++) {
|
||||
let line = log.readLine();
|
||||
if (line) lines += line;
|
||||
}
|
||||
pkgcnt++;
|
||||
gbSend({t:"actTrk", log:event.id, lines:lines, cnt:pkgcnt});
|
||||
if (!lines && fetchRecInterval) {
|
||||
clearInterval(fetchRecInterval);
|
||||
fetchRecInterval = undefined;
|
||||
}
|
||||
}
|
||||
fetchRecInterval = setInterval(sendlines, 50)
|
||||
}
|
||||
},
|
||||
"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);
|
||||
},
|
||||
"cards" : function() {
|
||||
// we receive all, just override what we have
|
||||
if (Array.isArray(event.d))
|
||||
require("Storage").writeJSON("android.cards.json", event.d);
|
||||
},
|
||||
"accelsender": function () {
|
||||
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
|
||||
load();
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},e));
|
||||
Bangle.emit("GB",e);
|
||||
require("android").gbHandler(e);
|
||||
};
|
||||
// HTTP request handling - see the readme
|
||||
// options = {id,timeout,xpath}
|
||||
Bangle.http = (url,options)=>{
|
||||
options = options||{};
|
||||
if (!NRF.getSecurityStatus().connected)
|
||||
return Promise.reject(/*LANG*/"Not connected to Bluetooth");
|
||||
if (Bangle.httpRequest === undefined)
|
||||
Bangle.httpRequest={};
|
||||
if (options.id === undefined) {
|
||||
// try and create a unique ID
|
||||
do {
|
||||
options.id = Math.random().toString().substr(2);
|
||||
} while( Bangle.httpRequest[options.id]!==undefined);
|
||||
}
|
||||
//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;
|
||||
gbSend(req);
|
||||
//create the promise
|
||||
var promise = new Promise(function(resolve,reject) {
|
||||
//save the resolve function in the dictionary and create a timeout (30 seconds default)
|
||||
Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
|
||||
//if after "timeoutMillisec" it still hasn't answered -> reject
|
||||
delete Bangle.httpRequest[options.id];
|
||||
reject("Timeout");
|
||||
},options.timeout||30000)};
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
Bangle.http = (url,options)=>require("android").httpHandler(url,options);
|
||||
// Battery monitor
|
||||
let sendBattery = function() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||
let sendBattery = function() { require("android").gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||
Bangle.on("charging", sendBattery);
|
||||
NRF.on("connect", () => setTimeout(function() {
|
||||
sendBattery();
|
||||
gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
|
||||
require("android").gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
|
||||
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
|
||||
}, 2000));
|
||||
NRF.on("disconnect", () => {
|
||||
|
|
@ -358,81 +32,24 @@
|
|||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking - if 'realtime' data is sent with 'rt:1', but let's still send our activity log every 10 mins
|
||||
Bangle.on('health', h=>{
|
||||
gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement });
|
||||
require("android").gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement, act: h.activity }); // h.activity added in 2v26
|
||||
});
|
||||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play/pause/next/previous/volumeup/volumedown
|
||||
gbSend({ t: "music", n:cmd });
|
||||
require("android").gbSend({ t: "music", n:cmd });
|
||||
};
|
||||
// Message response
|
||||
Bangle.messageResponse = (msg,response) => {
|
||||
if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
if (msg.id=="call") return require("android").gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
|
||||
if (isFinite(msg.id)) return require("android").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 });
|
||||
if (isFinite(msg.id)) return require("android").gbSend({ t: "notify", n:"MUTE", id: msg.id });
|
||||
};
|
||||
// GPS overwrite logic
|
||||
if (settings.overwriteGps) { // if the overwrite option is set..
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
Bangle.moveGPSPower = (state) => {
|
||||
if (Bangle.isGPSOn()){
|
||||
let orig = Bangle._PWR.GPS;
|
||||
delete Bangle._PWR.GPS;
|
||||
origSetGPSPower(state);
|
||||
Bangle._PWR.GPS = orig;
|
||||
}
|
||||
};
|
||||
|
||||
// work around Serial1 for GPS not working when connected to something
|
||||
let serialTimeout;
|
||||
let wrap = function(f){
|
||||
return (s)=>{
|
||||
if (serialTimeout) clearTimeout(serialTimeout);
|
||||
origSetGPSPower(1, "androidgpsserial");
|
||||
f(s);
|
||||
serialTimeout = setTimeout(()=>{
|
||||
serialTimeout = undefined;
|
||||
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) => {
|
||||
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;
|
||||
}).bind(gpsState);
|
||||
// allow checking for GPS via GadgetBridge
|
||||
Bangle.isGPSOn = () => {
|
||||
return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
|
||||
};
|
||||
// stop GPS on boot if not activated
|
||||
setTimeout(()=>{
|
||||
if (!Bangle.isGPSOn()) gbSend({ t: "gps_power", status: false });
|
||||
},3000);
|
||||
}
|
||||
|
||||
if (settings.overwriteGps) require("android").overwriteGPS();
|
||||
// remove settings object so it's not taking up RAM
|
||||
delete settings;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,394 @@
|
|||
exports.gbSend = function(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
let lastMsg, // for music messages - may not be needed now...
|
||||
gpsState = {}, // keep information on GPS via Gadgetbridge
|
||||
settings = Object.assign({rp:true,as:true,vibrate:".."},
|
||||
require("Storage").readJSON("android.settings.json",1)||{}
|
||||
);
|
||||
|
||||
exports.gbHandler = (event) => {
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() {
|
||||
print("notify",event);
|
||||
Object.assign(event,{t:"add",positive:true, negative:true});
|
||||
// Detect a weird GadgetBridge bug and fix it
|
||||
// For some reason SMS messages send two GB notifications, with different sets of info
|
||||
if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
|
||||
// Mutate the other message
|
||||
event.id = lastMsg.id;
|
||||
}
|
||||
lastMsg = event;
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
|
||||
// {t:"find", n:bool} // find my phone
|
||||
"find" : function() {
|
||||
if (Bangle.findDeviceInterval) {
|
||||
clearInterval(Bangle.findDeviceInterval);
|
||||
delete Bangle.findDeviceInterval;
|
||||
}
|
||||
if (event.n) // Ignore quiet mode: we always want to find our watch
|
||||
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
|
||||
},
|
||||
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
|
||||
"musicstate" : function() {
|
||||
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
|
||||
},
|
||||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
},
|
||||
// {t:"audio", v:(percentage of max volume for android STREAM_MUSIC)}
|
||||
"audio" : function() {
|
||||
Bangle.emit("musicVolume", event.v);
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end/start/outgoing","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
Object.assign(event, {
|
||||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"canned_responses_sync" : function() {
|
||||
require("Storage").writeJSON("replies.json", event.d);
|
||||
},
|
||||
// {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
|
||||
"alarm" : function() {
|
||||
//wipe existing GB alarms
|
||||
var sched;
|
||||
try { sched = require("sched"); } catch (e) {}
|
||||
if (!sched) return; // alarms may not be installed
|
||||
var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
|
||||
for (var i = 0; i < gbalarms.length; i++)
|
||||
sched.setAlarm(gbalarms[i].id, undefined);
|
||||
var alarms = sched.getAlarms();
|
||||
var time = new Date();
|
||||
var currentTime = time.getHours() * 3600000 +
|
||||
time.getMinutes() * 60000 +
|
||||
time.getSeconds() * 1000;
|
||||
for (var j = 0; j < event.d.length; j++) {
|
||||
// prevents all alarms from going off at once??
|
||||
var dow = event.d[j].rep;
|
||||
var rp = false;
|
||||
if (!dow) {
|
||||
dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
} else {
|
||||
rp = true;
|
||||
}
|
||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||
var a = require("sched").newDefaultAlarm();
|
||||
a.id = "gb"+j;
|
||||
a.appid = "gbalarms";
|
||||
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
|
||||
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||
a.rp = rp;
|
||||
a.last = last;
|
||||
alarms.push(a);
|
||||
}
|
||||
sched.setAlarms(alarms);
|
||||
sched.reload();
|
||||
},
|
||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||
//add and remove events based on activity on phone (pebble-like)
|
||||
// {t:"calendar", id:int, type:int, timestamp:seconds, durationInSeconds, title:string, description:string,location:string,calName:string.color:int,allDay:bool
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
var i = cal.findIndex(e=>e.id==event.id);
|
||||
if(i<0)
|
||||
cal.push(event);
|
||||
else
|
||||
cal[i] = event;
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
// {t:"calendar-", id:int}
|
||||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
if (Array.isArray(event.id))
|
||||
cal = cal.filter(e=>!event.id.includes(e.id));
|
||||
else
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
//triggered by GB, send all ids
|
||||
// { t:"force_calendar_sync_start" }
|
||||
"force_calendar_sync_start" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
exports.gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
|
||||
},
|
||||
// {t:"http",resp:"......",[id:"..."]}
|
||||
"http":function() {
|
||||
//get the promise and call the promise resolve
|
||||
if (Bangle.httpRequest === undefined) return;
|
||||
var request=Bangle.httpRequest[event.id];
|
||||
if (request === undefined) return; //already timedout or wrong id
|
||||
delete Bangle.httpRequest[event.id];
|
||||
clearTimeout(request.t); //t = timeout variable
|
||||
if(event.err!==undefined) //if is error
|
||||
request.j(event.err); //r = reJect function
|
||||
else
|
||||
request.r(event); //r = resolve function
|
||||
},
|
||||
// {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
|
||||
"gps": function() {
|
||||
if (!settings.overwriteGps) return;
|
||||
// modify event for using it as Bangle GPS event
|
||||
delete event.t;
|
||||
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"}
|
||||
"is_gps_active": function() {
|
||||
exports.gbSend({ t: "gps_power", status: Bangle.isGPSOn() });
|
||||
},
|
||||
// {t:"act", hrm:bool, stp:bool, int:int}
|
||||
"act": function() {
|
||||
if (exports.actInterval) clearInterval(exports.actInterval);
|
||||
exports.actInterval = undefined;
|
||||
if (exports.actHRMHandler)
|
||||
exports.actHRMHandler = undefined;
|
||||
Bangle.setHRMPower(event.hrm,"androidact");
|
||||
if (!(event.hrm || event.stp)) return;
|
||||
if (!isFinite(event.int)) event.int=1;
|
||||
var lastSteps = Bangle.getStepCount();
|
||||
var lastBPM = 0;
|
||||
exports.actHRMHandler = function(e) {
|
||||
lastBPM = e.bpm;
|
||||
};
|
||||
Bangle.on('HRM',exports.actHRMHandler);
|
||||
exports.actInterval = setInterval(function() {
|
||||
var steps = Bangle.getStepCount();
|
||||
exports.gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
|
||||
lastSteps = steps;
|
||||
}, event.int*1000);
|
||||
},
|
||||
// {t:"actfetch", ts:long}
|
||||
"actfetch": function() {
|
||||
exports.gbSend({t: "actfetch", state: "start"});
|
||||
var actCount = 0;
|
||||
var actCb = function(r) {
|
||||
// The health lib saves the samples at the start of the 10-minute block
|
||||
// However, GB expects them at the end of the block, so let's offset them
|
||||
// here to keep a consistent API in the health lib
|
||||
var sampleTs = r.date.getTime() + 600000;
|
||||
if (sampleTs >= event.ts) {
|
||||
exports.gbSend({
|
||||
t: "act",
|
||||
ts: sampleTs,
|
||||
stp: r.steps,
|
||||
hrm: r.bpm,
|
||||
mov: r.movement,
|
||||
act: r.activity
|
||||
});
|
||||
actCount++;
|
||||
}
|
||||
}
|
||||
if (event.ts != 0) {
|
||||
require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
|
||||
} else {
|
||||
require("health").readFullDatabase(actCb);
|
||||
}
|
||||
exports.gbSend({t: "actfetch", state: "end", count: actCount});
|
||||
},
|
||||
//{t:"listRecs", id:"20230616a"}
|
||||
"listRecs": function() {
|
||||
let recs = require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).map(s => s.slice(12, 21)).filter(s => s.length>7 /*ignore 'old' tracks without date*/);
|
||||
if (event.id && event.id.length > 2) { // Handle if there was no id supplied. Then we send a list all available recorder logs back.
|
||||
let firstNonsyncedIdx = recs.findIndex((logId) => logId > event.id);
|
||||
if (-1 == firstNonsyncedIdx) {
|
||||
recs = []
|
||||
} else {
|
||||
recs = recs.slice(firstNonsyncedIdx);
|
||||
}
|
||||
}
|
||||
exports.gbSend({t:"actTrksList", list: recs}); // TODO: split up in multiple transmissions?
|
||||
},
|
||||
//{t:"fetchRec", id:"20230616a"}
|
||||
"fetchRec": function() {
|
||||
// TODO: Decide on what names keys should have.
|
||||
if (exports.fetchRecInterval) {
|
||||
clearInterval(exports.fetchRecInterval);
|
||||
exports.fetchRecInterval = undefined;
|
||||
}
|
||||
if (event.id=="stop") {
|
||||
return;
|
||||
} else {
|
||||
let log = require("Storage").open("recorder.log"+event.id+".csv","r");
|
||||
let lines = "init";// = log.readLine();
|
||||
let pkgcnt = 0;
|
||||
exports.gbSend({t:"actTrk", log:event.id, lines:"erase", cnt:pkgcnt}); // "erase" will prompt Gadgetbridge to erase the contents of a already fetched log so we can rewrite it without keeping lines from the previous (probably failed) fetch.
|
||||
let sendlines = ()=>{
|
||||
lines = log.readLine();
|
||||
for (var i = 0; i < 3; i++) {
|
||||
let line = log.readLine();
|
||||
if (line) lines += line;
|
||||
}
|
||||
pkgcnt++;
|
||||
exports.gbSend({t:"actTrk", log:event.id, lines:lines, cnt:pkgcnt});
|
||||
if (!lines && exports.fetchRecInterval) {
|
||||
clearInterval(exports.fetchRecInterval);
|
||||
exports.fetchRecInterval = undefined;
|
||||
}
|
||||
};
|
||||
exports.fetchRecInterval = setInterval(sendlines, 50);
|
||||
}
|
||||
},
|
||||
"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);
|
||||
},
|
||||
"cards" : function() {
|
||||
// we receive all, just override what we have
|
||||
if (Array.isArray(event.d))
|
||||
require("Storage").writeJSON("android.cards.json", event.d);
|
||||
},
|
||||
"accelsender": function () {
|
||||
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
|
||||
load();
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
};
|
||||
|
||||
// HTTP request handling - see the readme
|
||||
// options = {id,timeout,xpath}
|
||||
exports.httpHandler = (url,options) => {
|
||||
options = options||{};
|
||||
if (!NRF.getSecurityStatus().connected)
|
||||
return Promise.reject(/*LANG*/"Not connected to Bluetooth");
|
||||
if (Bangle.httpRequest === undefined)
|
||||
Bangle.httpRequest={};
|
||||
if (options.id === undefined) {
|
||||
// try and create a unique ID
|
||||
do {
|
||||
options.id = Math.random().toString().substr(2);
|
||||
} while( Bangle.httpRequest[options.id]!==undefined);
|
||||
}
|
||||
//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;
|
||||
req.timeout = options.timeout || 30000;
|
||||
exports.gbSend(req);
|
||||
//create the promise
|
||||
var promise = new Promise(function(resolve,reject) {
|
||||
//save the resolve function in the dictionary and create a timeout (30 seconds default)
|
||||
Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
|
||||
//if after "timeoutMillisec" it still hasn't answered -> reject
|
||||
delete Bangle.httpRequest[options.id];
|
||||
reject("Timeout");
|
||||
},req.timeout+500)};
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
exports.overwriteGPS = () => { // if the overwrite option is set, call this on init..
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
Bangle.moveGPSPower = (state) => {
|
||||
if (Bangle.isGPSOn()){
|
||||
let orig = Bangle._PWR.GPS;
|
||||
delete Bangle._PWR.GPS;
|
||||
origSetGPSPower(state);
|
||||
Bangle._PWR.GPS = orig;
|
||||
}
|
||||
};
|
||||
|
||||
// work around Serial1 for GPS not working when connected to something
|
||||
let serialTimeout;
|
||||
let wrap = function(f){
|
||||
return (s)=>{
|
||||
if (serialTimeout) clearTimeout(serialTimeout);
|
||||
origSetGPSPower(1, "androidgpsserial");
|
||||
f(s);
|
||||
serialTimeout = setTimeout(()=>{
|
||||
serialTimeout = undefined;
|
||||
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) => {
|
||||
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
|
||||
require("android").gbSend({ t: "gps_power", status: pwr });
|
||||
return pwr;
|
||||
}).bind(gpsState);
|
||||
// allow checking for GPS via GadgetBridge
|
||||
Bangle.isGPSOn = () => {
|
||||
return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
|
||||
};
|
||||
// stop GPS on boot if not activated
|
||||
setTimeout(()=>{
|
||||
if (!Bangle.isGPSOn()) require("android").gbSend({ t: "gps_power", status: false });
|
||||
},3000);
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.36",
|
||||
"version": "0.44",
|
||||
"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",
|
||||
|
|
@ -13,7 +13,8 @@
|
|||
{"name":"android.app.js","url":"app.js"},
|
||||
{"name":"android.settings.js","url":"settings.js"},
|
||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
{"name":"android.boot.js","url":"boot.js"},
|
||||
{"name":"android","url":"lib.js"}
|
||||
],
|
||||
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
|
||||
"sortorder": -8
|
||||
|
|
|
|||
|
|
@ -94,4 +94,4 @@
|
|||
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
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
|
||||
0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
|
||||
0.07: Bangle.js 2 now gets estimated time + lat/lon from the browser (~3x faster fix)
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> BDS+GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> GPS+BDS+GLONASS
|
||||
<input type="radio" name="gnss_select" value="7"><i class="form-icon"></i> GPS+BDS+GLONASS
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
<script>
|
||||
var isB1; // is Bangle.js 1?
|
||||
var isB2; // is Bangle.js 2?
|
||||
var currentPosition;
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
|
|
@ -120,6 +121,7 @@
|
|||
}
|
||||
|
||||
// =================================================== Bangle.js 2 CASIC
|
||||
// https://www.espruino.com/Bangle.js2+Technical#gps
|
||||
|
||||
function CASIC_CHECKSUM(cmd) {
|
||||
var cs = 0;
|
||||
|
|
@ -128,6 +130,61 @@
|
|||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
||||
}
|
||||
|
||||
// Send a binary CASIC packet, eg: {classId:6, messageId:0, payload:[]}
|
||||
function CASIC_PKT(pkt) {
|
||||
pkt.payload = pkt.payload || [];
|
||||
var plen = pkt.payload.length;
|
||||
var msg = new Uint8Array(10+pkt.payload.length);
|
||||
msg.set([0xBA,0xCE,
|
||||
plen, // LENGTH
|
||||
0x00,
|
||||
pkt.classId, // CLASS ID
|
||||
pkt.messageId]); // MESSAGE ID
|
||||
msg.set(pkt.payload, 6);
|
||||
var dv = new DataView(msg.buffer);
|
||||
// checksum
|
||||
var ckSum = 0;
|
||||
for (i = -4; i < plen; i+=4)
|
||||
ckSum = 0|(ckSum+dv.getUint32(6+i, true));
|
||||
dv.setUint32(6+plen, ckSum, true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Send AID_INI message, {lat,lon,alt}
|
||||
function AID_INI(pos) {
|
||||
var msg = new Uint8Array(56);
|
||||
var dv = new DataView(msg.buffer);
|
||||
/*
|
||||
double xOrLat, yOrLon, zOrAlt;
|
||||
double tow; // 24
|
||||
float df; // 32
|
||||
float posAcc; // 36
|
||||
float tAcc; // 40
|
||||
float fAcc; // 44
|
||||
unsigned int res; // 48
|
||||
unsigned short int wn; // 52
|
||||
unsigned char timeSource; // 54
|
||||
unsigned char flags; // 55
|
||||
*/
|
||||
var ms = Date.now();
|
||||
var wk = (ms-new Date("1980-01-06T00:00:00Z")) / 604800000;
|
||||
var wn = Math.floor(wk); // week number
|
||||
var tow = (wk-wn) * 604800; // seconds in week
|
||||
dv.setFloat64(0, pos.lat, true); // xOrLat
|
||||
dv.setFloat64(8, pos.lon, true); // yOrLon
|
||||
dv.setFloat64(16, pos.alt, true); // zOrAlt
|
||||
dv.setFloat64(24, tow, true); // tow
|
||||
dv.setFloat32(32, 0, true); // df
|
||||
dv.setFloat32(36, 0, true); // posAcc
|
||||
dv.setFloat32(40, 0, true); // tAcc
|
||||
dv.setFloat32(44, 0, true); // fAcc
|
||||
dv.setUint32(48, 0, true); // res
|
||||
dv.setUint16(52, wn, true); // wn
|
||||
dv.setUint8(54,0); // timeSource
|
||||
dv.setUint8(55, 0x23); // flags ( lat/lon and clock valid, no drift data )
|
||||
return CASIC_PKT({classId:0x0B, messageId:0x01, payload:msg});
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
function jsFromBase64(b64) {
|
||||
|
|
@ -140,7 +197,6 @@
|
|||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
}
|
||||
if (isB2) { // CASIC
|
||||
|
||||
// Select what GNSS System to use for decreased fix time.
|
||||
var radios = document.getElementsByName('gnss_select');
|
||||
var gnss_select="1";
|
||||
|
|
@ -150,11 +206,11 @@
|
|||
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!)
|
||||
// If the browser let us have the current location, give it to the GPS chip to get a faster fix
|
||||
if (currentPosition) {
|
||||
js += `\x10Serial1.write([${AID_INI(currentPosition).join(",")}])\n`;
|
||||
}
|
||||
// Serial1.println("$PCAS06,0*1B") gets the current firmware version
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||
}
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
|
|
@ -184,8 +240,20 @@
|
|||
document.getElementById("banglejs1-info").style = isB1?"":"display:none";
|
||||
document.getElementById("banglejs2-info").style = isB2?"":"display:none";
|
||||
document.getElementById("upload-wrap").style = "";
|
||||
|
||||
if (isB2) {
|
||||
// get current position for AGPS improvement
|
||||
navigator.geolocation.getCurrentPosition(function(position) {
|
||||
currentPosition = {
|
||||
lat : position.coords.latitude,
|
||||
lon : position.coords.longitude,
|
||||
alt : 0|position.coords.altitude
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"shortName": "AGPS",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Fix icon, utilize sched, show running timer on app relaunch
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIKHgwFKo0gAofmsALEGR0H/+f//+gEP/4ACAoXAn4FDAQn8g0DAoX4g0BAoXx4E4AoXhAoN/8EP4AzBn/4h/IC4M//kPzgjBz/+h+MAoMfj0PNYUfh4FDh8HAo0wg/454RBmBDBAoRnBCIIjCAAMPF4IFDHYOIgEBj5HBzkAIIPAIIIFBn4hBLIU+AoPgwEQvwFBOIX8CgP5w0RAoSJC/AsB/0EJwIgB/+Aj/wAoN/VgPgQwQFBwBKCXAQWBAAfgAoocCAoQcCAAPAj7XEcYIABcYLIBAAJBBA=="))
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
// Tea Timer Application for Bangle.js 2 using sched library
|
||||
|
||||
let timerDuration = (() => {
|
||||
let file = require("Storage").open("ateatimer.data", "r");
|
||||
let data = file.read(4); // Assuming 4 bytes for storage
|
||||
return data ? parseInt(data, 10) : 4 * 60; // Default to 4 minutes
|
||||
})();
|
||||
let timeRemaining = timerDuration;
|
||||
let timerRunning = false;
|
||||
|
||||
function saveDefaultDuration() {
|
||||
let file = require("Storage").open("ateatimer.data", "w");
|
||||
file.write(timerDuration.toString());
|
||||
}
|
||||
|
||||
function drawTime() {
|
||||
g.clear();
|
||||
g.setFont("Vector", 40);
|
||||
g.setFontAlign(0, 0); // Center align
|
||||
|
||||
const minutes = Math.floor(Math.abs(timeRemaining) / 60);
|
||||
const seconds = Math.abs(timeRemaining) % 60;
|
||||
const sign = timeRemaining < 0 ? "-" : "";
|
||||
const timeStr = `${sign}${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
|
||||
g.drawString(timeStr, g.getWidth() / 2, g.getHeight() / 2);
|
||||
|
||||
// Draw Increase button (triangle pointing up)
|
||||
g.fillPoly([
|
||||
g.getWidth() / 2, g.getHeight() / 2 - 80, // Top vertex
|
||||
g.getWidth() / 2 - 20, g.getHeight() / 2 - 60, // Bottom-left vertex
|
||||
g.getWidth() / 2 + 20, g.getHeight() / 2 - 60 // Bottom-right vertex
|
||||
]);
|
||||
|
||||
// Draw Decrease button (triangle pointing down)
|
||||
g.fillPoly([
|
||||
g.getWidth() / 2, g.getHeight() / 2 + 80, // Bottom vertex
|
||||
g.getWidth() / 2 - 20, g.getHeight() / 2 + 60, // Top-left vertex
|
||||
g.getWidth() / 2 + 20, g.getHeight() / 2 + 60 // Top-right vertex
|
||||
]);
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (timerRunning) return;
|
||||
if (timeRemaining == 0) return;
|
||||
timerRunning = true;
|
||||
|
||||
// Save the default duration on timer start
|
||||
timerDuration = timeRemaining;
|
||||
saveDefaultDuration();
|
||||
scheduleTimer();
|
||||
|
||||
// Start the secondary timer to update the display
|
||||
setInterval(updateDisplay, 1000);
|
||||
}
|
||||
|
||||
function scheduleTimer() {
|
||||
// Schedule a new timer using the sched library
|
||||
require("sched").setAlarm("ateatimer", {
|
||||
msg: "Tea is ready!",
|
||||
timer: timeRemaining * 1000, // Convert to milliseconds
|
||||
vibrate: ".." // Default vibration pattern
|
||||
});
|
||||
|
||||
// Ensure the scheduler updates
|
||||
require("sched").reload();
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
// Cancel the existing timer
|
||||
require("sched").setAlarm("ateatimer", undefined);
|
||||
require("sched").reload();
|
||||
|
||||
timerRunning = false;
|
||||
timeRemaining = timerDuration;
|
||||
drawTime();
|
||||
}
|
||||
|
||||
function adjustTime(amount) {
|
||||
if (-amount > timeRemaining) {
|
||||
// Return if result will be negative
|
||||
return;
|
||||
}
|
||||
timeRemaining += amount;
|
||||
timeRemaining = Math.max(0, timeRemaining); // Ensure time doesn't go negative
|
||||
if (timerRunning) {
|
||||
// Update the existing timer with the new remaining time
|
||||
let alarm = require("sched").getAlarm("ateatimer");
|
||||
if (alarm) {
|
||||
// Cancel the current alarm
|
||||
require("sched").setAlarm("ateatimer", undefined);
|
||||
|
||||
// Set a new alarm with the updated time
|
||||
scheduleTimer();
|
||||
}
|
||||
}
|
||||
|
||||
drawTime();
|
||||
}
|
||||
|
||||
function handleTouch(x, y) {
|
||||
const centerY = g.getHeight() / 2;
|
||||
|
||||
if (y < centerY - 40) {
|
||||
// Increase button area
|
||||
adjustTime(60);
|
||||
} else if (y > centerY + 40) {
|
||||
// Decrease button area
|
||||
adjustTime(-60);
|
||||
} else {
|
||||
// Center area
|
||||
if (!timerRunning) {
|
||||
startTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the display every second
|
||||
function updateDisplay() {
|
||||
if (timerRunning) {
|
||||
let alarm = require("sched").getAlarm("ateatimer");
|
||||
timeRemaining = Math.floor(require("sched").getTimeToAlarm(alarm) / 1000);
|
||||
drawTime();
|
||||
if (timeRemaining <= 0) {
|
||||
timeRemaining = 0;
|
||||
clearInterval(updateDisplay);
|
||||
timerRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle physical button press for resetting timer
|
||||
setWatch(() => {
|
||||
if (timerRunning) {
|
||||
resetTimer();
|
||||
} else {
|
||||
startTimer();
|
||||
}
|
||||
}, BTN1, { repeat: true, edge: "falling" });
|
||||
|
||||
// Handle touch
|
||||
Bangle.on("touch", (zone, xy) => {
|
||||
handleTouch(xy.x, xy.y, false);
|
||||
});
|
||||
|
||||
let isRunning = require("sched").getAlarm("ateatimer");
|
||||
if (isRunning) {
|
||||
timerRunning = true;
|
||||
// Start the timer to update the display
|
||||
setInterval(updateDisplay, 1000);
|
||||
} else {
|
||||
// Draw the initial timer display
|
||||
drawTime();
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{ "duration": 240 }
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "ateatimer",
|
||||
"name": "A Tea Timer",
|
||||
"shortName":"A Tea Timer",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"description": "Simple app for setting timers for tea. Touch up and down to change time, and time or button to start counting. When timer is running, button will stop timer and reset counter to last used value.",
|
||||
"tags": "timer",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"ateatimer.app.js","url":"app.js"},
|
||||
{"name":"ateatimer.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"dependencies": {"scheduler":"type"}
|
||||
}
|
||||
|
|
@ -1,2 +1,7 @@
|
|||
1.00: initial release
|
||||
1.01: added tap event to scroll METAR and toggle seconds display
|
||||
1.02: continue showing METAR during AVWX updates (show update status next to time)
|
||||
re-try GPS fix if it takes too long
|
||||
bug fix
|
||||
don't attempt to update METAR if Bluetooth is NOT connected
|
||||
toggle seconds display on front double-taps (if un-locked) to avoid accidential enabling
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const APP_NAME = 'aviatorclk';
|
|||
const horizontalCenter = g.getWidth()/2;
|
||||
const mainTimeHeight = 38;
|
||||
const secondaryFontHeight = 22;
|
||||
require("Font8x16").add(Graphics); // tertiary font
|
||||
const dateColour = ( g.theme.dark ? COLOUR_YELLOW : COLOUR_BLUE );
|
||||
const UTCColour = ( g.theme.dark ? COLOUR_LIGHT_CYAN : COLOUR_DARK_CYAN );
|
||||
const separatorColour = ( g.theme.dark ? COLOUR_LIGHT_GREY : COLOUR_DARK_GREY );
|
||||
|
|
@ -37,6 +38,7 @@ var settings = Object.assign({
|
|||
var drawTimeout;
|
||||
var secondsInterval;
|
||||
var avwxTimeout;
|
||||
var gpsTimeout;
|
||||
|
||||
var AVWXrequest;
|
||||
var METAR = '';
|
||||
|
|
@ -92,16 +94,51 @@ function drawAVWX() {
|
|||
if (! avwxTimeout) { avwxTimeout = setTimeout(updateAVWX, 5 * 60000); }
|
||||
}
|
||||
|
||||
// show AVWX update status
|
||||
function showUpdateAVWXstatus(status) {
|
||||
let y = Bangle.appRect.y + 10;
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.clearRect(0, y, horizontalCenter - 54, y + 16);
|
||||
if (status) {
|
||||
g.setFontAlign(0, -1).setFont("8x16").setColor( g.theme.dark ? COLOUR_ORANGE : COLOUR_DARK_YELLOW );
|
||||
g.drawString(status, horizontalCenter - 71, y, true);
|
||||
}
|
||||
}
|
||||
|
||||
// re-try if the GPS doesn't return a fix in time
|
||||
function GPStookTooLong() {
|
||||
Bangle.setGPSPower(false, APP_NAME);
|
||||
if (gpsTimeout) clearTimeout(gpsTimeout);
|
||||
gpsTimeout = undefined;
|
||||
|
||||
showUpdateAVWXstatus('X');
|
||||
|
||||
if (! avwxTimeout) { avwxTimeout = setTimeout(updateAVWX, 5 * 60000); }
|
||||
}
|
||||
|
||||
// update the METAR info
|
||||
function updateAVWX() {
|
||||
if (avwxTimeout) clearTimeout(avwxTimeout);
|
||||
avwxTimeout = undefined;
|
||||
if (gpsTimeout) clearTimeout(gpsTimeout);
|
||||
gpsTimeout = undefined;
|
||||
|
||||
METAR = '\nGetting GPS fix';
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
if (! NRF.getSecurityStatus().connected) {
|
||||
// if Bluetooth is NOT connected, try again in 5min
|
||||
showUpdateAVWXstatus('X');
|
||||
avwxTimeout = setTimeout(updateAVWX, 5 * 60000);
|
||||
return;
|
||||
}
|
||||
|
||||
showUpdateAVWXstatus('GPS');
|
||||
if (! METAR) {
|
||||
METAR = '\nUpdating METAR';
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
}
|
||||
drawAVWX();
|
||||
|
||||
gpsTimeout = setTimeout(GPStookTooLong, 30 * 60000);
|
||||
Bangle.setGPSPower(true, APP_NAME);
|
||||
Bangle.on('GPS', fix => {
|
||||
// prevent multiple, simultaneous requests
|
||||
|
|
@ -109,12 +146,18 @@ function updateAVWX() {
|
|||
|
||||
if ('fix' in fix && fix.fix != 0 && fix.satellites >= 4) {
|
||||
Bangle.setGPSPower(false, APP_NAME);
|
||||
if (gpsTimeout) clearTimeout(gpsTimeout);
|
||||
gpsTimeout = undefined;
|
||||
|
||||
let lat = fix.lat;
|
||||
let lon = fix.lon;
|
||||
|
||||
METAR = '\nRequesting METAR';
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
showUpdateAVWXstatus('AVWX');
|
||||
if (! METAR) {
|
||||
METAR = '\nUpdating METAR';
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
}
|
||||
drawAVWX();
|
||||
|
||||
// get latest METAR from nearest airport (via AVWX API)
|
||||
|
|
@ -146,6 +189,7 @@ function updateAVWX() {
|
|||
METARts = undefined;
|
||||
}
|
||||
|
||||
showUpdateAVWXstatus('');
|
||||
drawAVWX();
|
||||
AVWXrequest = undefined;
|
||||
|
||||
|
|
@ -155,6 +199,7 @@ function updateAVWX() {
|
|||
METAR = 'ERR: ' + error;
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
showUpdateAVWXstatus('');
|
||||
drawAVWX();
|
||||
AVWXrequest = undefined;
|
||||
});
|
||||
|
|
@ -268,10 +313,10 @@ Bangle.on('tap', data => {
|
|||
case 'bottom':
|
||||
scrollAVWX(1);
|
||||
break;
|
||||
case 'left':
|
||||
case 'right':
|
||||
// toggle seconds display on double taps left or right
|
||||
if (data.double) {
|
||||
case 'front':
|
||||
// toggle seconds display on double tap on front/watch-face
|
||||
// (if watch is un-locked)
|
||||
if (data.double && ! Bangle.isLocked()) {
|
||||
if (settings.showSeconds) {
|
||||
clearInterval(secondsInterval);
|
||||
let y = Bangle.appRect.y + mainTimeHeight - 3;
|
||||
|
|
@ -295,7 +340,7 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
|
||||
// draw static separator line
|
||||
y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight;
|
||||
let y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight;
|
||||
g.setColor(separatorColour);
|
||||
g.drawLine(0, y, g.getWidth(), y);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "aviatorclk",
|
||||
"name": "Aviator Clock",
|
||||
"shortName":"AV8R Clock",
|
||||
"version":"1.01",
|
||||
"version":"1.02",
|
||||
"description": "A clock for aviators, with local time and UTC - and the latest METAR for the nearest airport",
|
||||
"icon": "aviatorclk.png",
|
||||
"screenshots": [{ "url": "screenshot.png" }, { "url": "screenshot2.png" }],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New app! (settings, boot.js).
|
||||
0.02: Fix settings defaulting brightness to 0
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# BackLite
|
||||
### This app needs the latest settings app update (v 0.80), to ensure that setting the brightness to `0` does not default to `1`.
|
||||
|
||||
BackLite is an app which greatly conserves battery life by only turning the backlight on when you long press the button from a locked state.
|
||||
|
||||
Modern watches have a dedicated button to turn the backlight on, so as not to waste battery in an already light environment. This app recreates that functionality for the Bangle.js, which only has one button.
|
||||
|
||||
#### Warning: This app overwrites the LCD brightness setting in `Bangle.js LCD settings`. If it is changed, the app will basically lose functionality. It auto-fixes itself every boot, so if you change the brightness, just reboot :)
|
||||
# Usage
|
||||
When you unlock with a press of the button, or any other way you unlock the watch, the backlight will not turn on, as most of the time you are able to read it, due to the transreflective display on the Bangle.js 2.
|
||||
|
||||
If you press and hold the button to unlock the watch (for around half a second), the backlight will turn on for 5 seconds - just enough to see what you need to see. After that, it will turn off again.
|
||||
|
||||
Some apps like `Light Switch Widget` will prevent this app from working properly.
|
||||
# Settings
|
||||
`Brightness` - The LCD brightness when unlocked with a long press.
|
||||
# Creator
|
||||
RKBoss6
|
||||
|
||||
TODO: Add a setting for long press time, or light duration
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
let getSettings = function(){
|
||||
return Object.assign({
|
||||
// default values
|
||||
brightness: 0.3,
|
||||
|
||||
}, require('Storage').readJSON("BackLite.settings.json", true) || {});
|
||||
};
|
||||
|
||||
|
||||
//Set LCD to zero every reboot
|
||||
let s = require("Storage").readJSON("setting.json", 1) || {};
|
||||
s.brightness = 0;
|
||||
require("Storage").writeJSON("setting.json", s);
|
||||
//remove large settings object from memory
|
||||
delete s;
|
||||
const longPressTime=400; //(ms)
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
Bangle.setLCDBrightness(0);
|
||||
|
||||
if (!isLocked) {
|
||||
// Just unlocked — give a short delay and check if BTN1 is still pressed
|
||||
setTimeout(() => {
|
||||
if (digitalRead(BTN1)) {
|
||||
//set brightness until. locked.
|
||||
Bangle.setLCDBrightness(getSettings().brightness);
|
||||
} else {
|
||||
Bangle.setLCDBrightness(0);
|
||||
}
|
||||
}, longPressTime); // Slight delay to allow unlock to settle
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "backlite",
|
||||
"name": "BackLite",
|
||||
"version": "0.02",
|
||||
"description": "Conserves battery life by turning the backlight on only on a long press of the button from a locked state. **Requires the latest settings update (v0.80)**",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"backlite.boot.js","url":"boot.js"},
|
||||
{"name":"backlite.settings.js","url":"settings.js"}
|
||||
|
||||
],
|
||||
"data": [{"name":"BackLite.settings.json"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
(function(back) {
|
||||
var FILE = "BackLite.settings.json";
|
||||
|
||||
var settings = require("Storage").readJSON(FILE, 1) || {};
|
||||
|
||||
if (!isFinite(settings.brightness)) settings.brightness = 0.3;
|
||||
|
||||
function writeSettings() {
|
||||
require("Storage").writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "BackLite" },
|
||||
"< Back": back, // fallback if run standalone
|
||||
"Brightness": {
|
||||
value: settings.brightness,
|
||||
min: 0.1, max: 1,
|
||||
step: 0.1,
|
||||
onchange: v => {
|
||||
settings.brightness = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Don't fire if the app uses swipes already.
|
||||
0.03: Only count defined handlers in the handler array.
|
||||
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs
|
||||
a refresh by deselecting and reselecting the "Messages" app throught Back Swipe
|
||||
settings.
|
||||
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs a refresh by deselecting and reselecting the "Messages" app throught Back Swipe settings.
|
||||
0.05: React on swipes before the active app (for the most part) by using `prependListener`.
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
// if we're in an app that has a back button, run the callback for it
|
||||
if (global.BACK && countHandlers("swipe")<=settings.standardNumSwipeHandlers && countHandlers("drag")<=settings.standardNumDragHandlers) {
|
||||
global.BACK();
|
||||
E.stopEventPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,5 +57,5 @@
|
|||
}
|
||||
|
||||
// Listen to left to right swipe
|
||||
Bangle.on("swipe", goBack);
|
||||
Bangle.prependListener("swipe", goBack);
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "backswipe",
|
||||
"name": "Back Swipe",
|
||||
"shortName":"BackSwipe",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New app!
|
||||
0.02: Minor code improvements
|
||||
0.03: Remove clearing of the screen (will break running apps) and fix lint errors
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
"id": "banglebridge",
|
||||
"name": "BangleBridge",
|
||||
"shortName": "BangleBridge",
|
||||
"version": "0.02",
|
||||
"description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App",
|
||||
"version": "0.03",
|
||||
"description": "Widget that allows Bangle.js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App (**Note:** this has nothing to do with Gadgetbridge)",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
(() => {
|
||||
/**
|
||||
* Widget measurements
|
||||
* Description:
|
||||
* Description:
|
||||
* name: connection.wid.js
|
||||
*icon: conectionIcon.icon
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
//Font
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
//Sensors code
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Jorge
|
||||
*/
|
||||
function accel() {
|
||||
|
|
@ -35,8 +35,7 @@
|
|||
});
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag;
|
||||
//acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag;
|
||||
data[3] = accelN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
|
@ -45,8 +44,7 @@
|
|||
function btt() {
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
bttS = E.getBattery(); //return String
|
||||
//bttS = E.getBattery(); //return String
|
||||
data[2] = E.getBattery();
|
||||
}, 15 * 1000);
|
||||
|
||||
|
|
@ -65,9 +63,9 @@
|
|||
|
||||
setInterval(function () {
|
||||
|
||||
compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" +
|
||||
/*compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" +
|
||||
"B: " + compssN.dx + " ## " + compssN.dy + " ## " + compssN.dz + " ## " + "\n" +
|
||||
"C: " + compssN.heading; //return String
|
||||
"C: " + compssN.heading; *///return String
|
||||
data[4] = compssN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
|
@ -86,8 +84,8 @@
|
|||
|
||||
setInterval(function () {
|
||||
|
||||
gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" +
|
||||
"C: " + gpsN.satellites + " ## " + gpsN.fix; //return String
|
||||
/*gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" +
|
||||
"C: " + gpsN.satellites + " ## " + gpsN.fix; *///return String
|
||||
// work out how to display the current time
|
||||
var d = new Date();
|
||||
var year = d.getFullYear();
|
||||
|
|
@ -150,7 +148,7 @@
|
|||
//console.log("Index ==> "+ index);
|
||||
msr[indexFinal] = nueva;
|
||||
|
||||
item = nueva;
|
||||
//item = nueva;
|
||||
lastInsert = indexFinal;
|
||||
|
||||
}
|
||||
|
|
@ -180,7 +178,7 @@
|
|||
|
||||
hrmN = normalize(hrmN);
|
||||
var roundedRate = parseFloat(hrmN).toFixed(2);
|
||||
hrmS = String.valueOf(roundedRate); //return String
|
||||
//hrmS = String.valueOf(roundedRate); //return String
|
||||
//console.log("array----->" + msr);
|
||||
data[0] = roundedRate;
|
||||
|
||||
|
|
@ -205,7 +203,7 @@
|
|||
|
||||
setInterval(function () {
|
||||
|
||||
stepS = String.valueOf(stepN); //return String
|
||||
//stepS = String.valueOf(stepN); //return String
|
||||
data[1] = stepN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
|
@ -240,12 +238,11 @@
|
|||
g.setFont("Vector", 45);
|
||||
g.drawString(prueba,100,200);*/
|
||||
if (flip == 1) { //when off
|
||||
|
||||
|
||||
flip = 0;
|
||||
//Bangle.buzz(1000);
|
||||
g.clear();
|
||||
} else { //when on
|
||||
|
||||
|
||||
flip = 1;
|
||||
g.setFont("Vector", 30);
|
||||
g.drawString(data[0], 65, 180);
|
||||
|
|
@ -283,7 +280,7 @@
|
|||
com: data[4],
|
||||
gps: data[5]
|
||||
};
|
||||
/* g.clear();
|
||||
/*
|
||||
g.drawString(compssS,100,200);
|
||||
*/
|
||||
|
||||
|
|
@ -293,7 +290,7 @@
|
|||
//draw();
|
||||
|
||||
}, 5 * 1000);
|
||||
|
||||
|
||||
WIDGETS["banglebridge"]={
|
||||
area: "tl",
|
||||
width: 10,
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
0.03: Add software back button on main menu
|
||||
0.04: Minor code improvements
|
||||
0.05: Minor code improvements
|
||||
0.06: Fix grammar, add international language support
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ let exerciseType = {
|
|||
// add new exercises here:
|
||||
const exerciseTypes = [{
|
||||
"id": "pushup",
|
||||
"name": "push ups",
|
||||
"name": /*LANG*/"Push-ups",
|
||||
"useYaxis": true,
|
||||
"useZaxis": false,
|
||||
"threshold": 2500,
|
||||
|
|
@ -34,7 +34,7 @@ const exerciseTypes = [{
|
|||
},
|
||||
{
|
||||
"id": "curl",
|
||||
"name": "curls",
|
||||
"name": /*LANG*/"Curls",
|
||||
"useYaxis": true,
|
||||
"useZaxis": false,
|
||||
"threshold": 2500,
|
||||
|
|
@ -44,7 +44,7 @@ const exerciseTypes = [{
|
|||
},
|
||||
{
|
||||
"id": "situp",
|
||||
"name": "sit ups",
|
||||
"name": /*LANG*/"Sit-ups",
|
||||
"useYaxis": false,
|
||||
"useZaxis": true,
|
||||
"threshold": 3500,
|
||||
|
|
@ -88,7 +88,7 @@ function showMainMenu() {
|
|||
menu["--------"] = {
|
||||
value: ""
|
||||
};
|
||||
menu["Last:"] = {
|
||||
menu[/*LANG*/"Last:"] = {
|
||||
value: exerciseCounter + " " + exerciseType.name
|
||||
};
|
||||
}
|
||||
|
|
@ -189,10 +189,10 @@ function isValidExercise(slope, t) {
|
|||
if (p1 > 0 && p2 < 0) {
|
||||
if (lastZeroPassCameFromPositive == false) {
|
||||
lastExerciseHalfCompletionTime = t;
|
||||
console.log(t, exerciseName + " half complete...");
|
||||
console.log(t, exerciseName + /*LANG*/" half complete...");
|
||||
|
||||
layout.progress.label = "½";
|
||||
layout.recording.label = "TRAINING";
|
||||
layout.recording.label = /*LANG*/"TRAINING";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
|
|
@ -204,7 +204,7 @@ function isValidExercise(slope, t) {
|
|||
if (lastZeroPassCameFromPositive == true) {
|
||||
const tDiffLastExercise = t - lastExerciseCompletionTime;
|
||||
const tDiffStart = t - tStart;
|
||||
console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
console.log(t, exerciseName + /*LANG*/" maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
|
||||
// check minimal time between exercises:
|
||||
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
|
||||
|
|
@ -222,7 +222,7 @@ function isValidExercise(slope, t) {
|
|||
|
||||
layout.count.label = exerciseCounter;
|
||||
layout.progress.label = "";
|
||||
layout.recording.label = "Good!";
|
||||
layout.recording.label =/*LANG*/"Good!";
|
||||
|
||||
g.clear();
|
||||
layout.render();
|
||||
|
|
@ -230,26 +230,26 @@ function isValidExercise(slope, t) {
|
|||
if (settings.buzz)
|
||||
Bangle.buzz(200, 0.5);
|
||||
} else {
|
||||
console.log(t, exerciseName + " too quick for duration time threshold!"); // thresholdMinDurationTime
|
||||
console.log(t, exerciseName + /*LANG*/" too quick for duration time threshold!"); // thresholdMinDurationTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go slower!";
|
||||
layout.recording.label = /*LANG*/"Go slower!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
} else {
|
||||
console.log(t, exerciseName + " top slow for time threshold!"); // thresholdMaxTime
|
||||
console.log(t, exerciseName + /*LANG*/" top slow for time threshold!"); // thresholdMaxTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go faster!";
|
||||
layout.recording.label = /*LANG*/"Go faster!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
} else {
|
||||
console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime
|
||||
console.log(t, exerciseName + /*LANG*/" too quick for time threshold!"); // thresholdMinTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go slower!";
|
||||
layout.recording.label = /*LANG*/"Go slower!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
|
|
@ -337,7 +337,7 @@ function startTraining() {
|
|||
type: "txt",
|
||||
id: "recording",
|
||||
font: "6x8:2",
|
||||
label: "TRAINING",
|
||||
label: /*LANG*/"TRAINING",
|
||||
bgCol: "#f00",
|
||||
pad: 5,
|
||||
fillx: 1
|
||||
|
|
@ -345,7 +345,7 @@ function startTraining() {
|
|||
]
|
||||
}, {
|
||||
btns: [{
|
||||
label: "STOP",
|
||||
label: /*LANG*/"STOP",
|
||||
cb: () => {
|
||||
stopTraining();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{ "id": "banglexercise",
|
||||
"name": "BanglExercise",
|
||||
"shortName":"BanglExercise",
|
||||
"version": "0.05",
|
||||
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
|
||||
"version": "0.06",
|
||||
"description": "Automatically tracks exercises while wearing the Bangle.js watch.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "sport",
|
||||
"tags": "health",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@
|
|||
}
|
||||
require("ClockFace_menu").addItems(menu, save, items);
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New app introduced to the app loader!
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Battery Booster
|
||||
|
||||
A Bangle.js app designed to optimize battery life through smart screen and power management features.
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Auto Soft-Off
|
||||
- Automatically puts the watch into soft-off mode after 3 hours (10,800,000 ms) of being locked
|
||||
- This feature is activated when the watch is locked and cancelled when unlocked
|
||||
|
||||
### 2. Dynamic Screen Timeout
|
||||
- Sets LCD timeout to 2 seconds when the watch is locked
|
||||
- Extends LCD timeout to 10 seconds when the screen is touched
|
||||
- Helps preserve battery life while maintaining usability
|
||||
|
||||
### 3. Adaptive Brightness Control
|
||||
- Automatically adjusts screen brightness based on the time of day
|
||||
- Uses a sinusoidal pattern that follows natural daylight:
|
||||
- Peak brightness at noon
|
||||
- Lowest brightness at midnight
|
||||
- Gradual transitions in between
|
||||
- Updates brightness every hour
|
||||
|
||||
## How It Works
|
||||
The app runs in the background and manages three main aspects of power consumption:
|
||||
1. Screen timeout duration
|
||||
2. Automatic soft-off functionality
|
||||
3. Time-based brightness adjustment
|
||||
|
||||
This combination of features helps extend battery life while maintaining a good user experience.
|
||||
|
After Width: | Height: | Size: 1012 B |
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
let softOffTimeout;
|
||||
Bangle.on("lock", (on) => {
|
||||
if (on) {
|
||||
softOffTimeout = setTimeout(() => Bangle.softOff(), 10800000);
|
||||
Bangle.setLCDTimeout(2);
|
||||
}
|
||||
else {
|
||||
if (softOffTimeout) clearTimeout(softOffTimeout);
|
||||
}
|
||||
});
|
||||
Bangle.on("touch", () => {
|
||||
Bangle.setLCDTimeout(10);
|
||||
});
|
||||
setInterval(() => {
|
||||
let getBrightness = (hour) => {
|
||||
let radians = (Math.PI / 12) * (hour - 6);
|
||||
let brightness = Math.sin(radians) / 2 + 0.5;
|
||||
return brightness;
|
||||
};
|
||||
|
||||
const d = new Date();
|
||||
let hour = d.getHours();
|
||||
Bangle.setLCDBrightness(getBrightness(hour));
|
||||
}, 3600000);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "batterybooster",
|
||||
"name": "Battery Booster",
|
||||
"icon": "app.png",
|
||||
"version": "0.01",
|
||||
"description": "A bootloader app which adds scripts to boost battery life of your Bangle.js 2",
|
||||
"type": "bootloader",
|
||||
"tags": "tools,system",
|
||||
"readme": "README.md",
|
||||
"supports": [
|
||||
"BANGLEJS2"
|
||||
],
|
||||
"storage": [
|
||||
{
|
||||
"name": "batterybooster.boot.js",
|
||||
"url": "boot.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"description": "A fully featured watch face with a playable game on the side.",
|
||||
"readme":"README.md",
|
||||
"type": "clock",
|
||||
"tags": "clock, game",
|
||||
"tags": "clock,game",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"bblobface.app.js","url":"app.js"},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
0.01: It works somehow, early version for testers and feedback :)
|
||||
0.02: Changed almost all code with Frederic version of Pong and adjusted to be a BrickBreaker!, still Alpha
|
||||
0.03: Rewrote the whole thing to have less code and better graphics, now it works.
|
||||
0.04: Rewrote part of the code to coupe with the flickering and added better logic to handle the graphics.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# BrickBreaker
|
||||
|
||||
A simple BreakOut clone for the Banglejs
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||

|
||||
|
||||
Buttons 1 and 3 to move the BrickBreaker!
|
||||
|
||||
Button 2 to pause and start the game again.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This game was created to learn JS and how to interact with Banglejs, meaning that it may not be perfect :).
|
||||
|
||||
Built with love with base on the tutorial: 2D breakout game using pure JavaScript
|
||||
https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript
|
||||
|
||||
Started on 2020 but rewrote all in 2025 and this is the version I got without having issues with Memory Exhaustion.
|
||||
|
||||
And yes, for Bangle 1, old school!
|
||||
|
||||
## Creator
|
||||
|
||||
Israel Ochoa <isuraeru at gmail.com>
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAAAAAAAAAVQAAAAAAAAAAAAAFWUAAAAAAAAAAAABVuUAAAAAAAAAAAAVa/lAAAAAAAAAAABWv/lQAAAAAAAAAAVb/+VAAAAAAAAAABW//lUAAAAAAAAAABX/6VAAAAAAAAAAAAW/lUAAAAAAAAAAAAFpVAAAAAAAAAAAAAFVQAAAAAAAAAAAAABUAAAFVVVVQVVQAAAQAAAFVVVVQVVQAAAAAAAFv//5QW5QAAAAAAAG///9QW9QAAAAAAAG///9QW9QAAAAAAAFqqqpQWpQAAAAAAAFVVVVQVVQAAAAAAAFVVVVQVVQAAAAAAAAAAAAAAAAAAAABVVVVQFVVVVQAAAAFVVVVUVVVVVQAAAAFqqqqUWqqqpQAAAAFv//+UW///9QAAAAFv//+UW///9QAAAAFv//+UWv//5QAAAAFVVVVUVVVVVQAAAABVVVVUFVVVVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVVVVUFVVVVQVVQABaqqqUFqqqpQWpQABv//+UF///9QW5QABv//+UG///9QW5QABv//+UG///9QW5QABaqqqUFqqqpQWlQABVVVVUFVVVVQVVQAAVVVVABVVVVABUAAAAAAAAAAAAAAAAAA==")
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
(function () {
|
||||
var BALL_RADIUS = 3;
|
||||
var PADDLE_WIDTH = 26;
|
||||
var PADDLE_HEIGHT = 6;
|
||||
var BRICK_ROWS = 2;
|
||||
var BRICK_HEIGHT = 8;
|
||||
var BRICK_PADDING = 4;
|
||||
var BRICK_OFFSET_TOP = 60;
|
||||
var BRICK_OFFSET_LEFT = 2;
|
||||
var SPEED_MULTIPLIER = 1.1;
|
||||
var PADDLE_SPEED = 12;
|
||||
|
||||
var ball, paddle, interval;
|
||||
var bricks = [];
|
||||
var BRICK_WIDTH, BRICK_COLS;
|
||||
var score = 0;
|
||||
var level = 1;
|
||||
var highScore = 0;
|
||||
var paused = false;
|
||||
var gameOver = false;
|
||||
var lives = 3;
|
||||
var paddleMove = 0;
|
||||
|
||||
var storage = require("Storage");
|
||||
|
||||
function loadHighScore() {
|
||||
var saved = storage.readJSON("breakout_highscore.json", 1);
|
||||
highScore = saved && saved.highScore ? saved.highScore : 0;
|
||||
}
|
||||
|
||||
function saveHighScore() {
|
||||
if (score > highScore) {
|
||||
highScore = score;
|
||||
storage.writeJSON("breakout_highscore.json", { highScore });
|
||||
}
|
||||
}
|
||||
|
||||
function initBricks() {
|
||||
bricks = [];
|
||||
for (var i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
|
||||
bricks.push(1);
|
||||
}
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
var screenWidth = g.getWidth();
|
||||
BRICK_COLS = Math.min(5, Math.floor((screenWidth - BRICK_OFFSET_LEFT + BRICK_PADDING) / (15 + BRICK_PADDING)));
|
||||
BRICK_WIDTH = Math.floor((screenWidth - BRICK_OFFSET_LEFT - (BRICK_COLS - 1) * BRICK_PADDING) / BRICK_COLS);
|
||||
|
||||
ball = {
|
||||
x: screenWidth / 2,
|
||||
y: g.getHeight() - 40,
|
||||
dx: (Math.random() > 0.5 ? 1 : -1) * 3,
|
||||
dy: -3,
|
||||
radius: BALL_RADIUS,
|
||||
prevPos: null
|
||||
};
|
||||
|
||||
paddle = {
|
||||
x: screenWidth / 2 - PADDLE_WIDTH / 2,
|
||||
y: g.getHeight() - 20,
|
||||
width: PADDLE_WIDTH,
|
||||
height: PADDLE_HEIGHT,
|
||||
prevPos: null
|
||||
};
|
||||
lives = 3;
|
||||
score = 0;
|
||||
level = 1;
|
||||
gameOver = false;
|
||||
paused = false;
|
||||
loadHighScore();
|
||||
initBricks();
|
||||
}
|
||||
|
||||
function drawLives() {
|
||||
var heartSize = 6;
|
||||
var spacing = 2;
|
||||
var startX = g.getWidth() - (lives * (heartSize + spacing)) - 2;
|
||||
var y = 32;
|
||||
g.setColor(1, 0, 0);
|
||||
for (var i = 0; i < lives; i++) {
|
||||
var x = startX + i * (heartSize + spacing);
|
||||
g.fillPoly([x + 3, y, x + 6, y + 3, x + 3, y + 6, x, y + 3], true);
|
||||
}
|
||||
}
|
||||
|
||||
function drawBricks() {
|
||||
g.setColor(0, 1, 0);
|
||||
for (var i = 0; i < bricks.length; i++) {
|
||||
if (bricks[i]) {
|
||||
var c = i % BRICK_COLS;
|
||||
var r = Math.floor(i / BRICK_COLS);
|
||||
var brickX = BRICK_OFFSET_LEFT + c * (BRICK_WIDTH + BRICK_PADDING);
|
||||
var brickY = BRICK_OFFSET_TOP + r * (BRICK_HEIGHT + BRICK_PADDING);
|
||||
g.fillRect(brickX, brickY, brickX + BRICK_WIDTH - 1, brickY + BRICK_HEIGHT - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawBall() {
|
||||
g.setColor(1, 1, 1);
|
||||
g.fillCircle(ball.x, ball.y, ball.radius);
|
||||
g.setColor(0.7, 0.7, 0.7);
|
||||
g.fillCircle(ball.x - 0.5, ball.y - 0.5, ball.radius - 1);
|
||||
}
|
||||
|
||||
function drawPaddle() {
|
||||
g.setColor(0, 1, 1);
|
||||
g.fillRect(paddle.x, paddle.y, paddle.x + paddle.width - 1, paddle.y + paddle.height - 1);
|
||||
}
|
||||
|
||||
function drawHUD() {
|
||||
g.setColor(0, 0, 0).fillRect(0, 0, g.getWidth(), BRICK_OFFSET_TOP - 1);
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFont("6x8", 1);
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString("Score: " + score, 2, 22);
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString("High: " + highScore, g.getWidth() / 2, 22);
|
||||
g.setFontAlign(1, -1);
|
||||
g.drawString("Lvl: " + level, g.getWidth() - 2, 22);
|
||||
drawLives();
|
||||
if (paused) {
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString("PAUSED", g.getWidth() / 2, g.getHeight() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (paddle.prevPos) {
|
||||
g.setColor(0, 0, 0).fillRect(paddle.prevPos.x - 1, paddle.prevPos.y - 1, paddle.prevPos.x + paddle.width + 1, paddle.prevPos.y + paddle.height + 1);
|
||||
}
|
||||
if (ball.prevPos) {
|
||||
g.setColor(0, 0, 0).fillCircle(ball.prevPos.x, ball.prevPos.y, ball.radius + 1);
|
||||
}
|
||||
drawHUD();
|
||||
drawBall();
|
||||
drawPaddle();
|
||||
g.flip();
|
||||
ball.prevPos = { x: ball.x, y: ball.y };
|
||||
paddle.prevPos = { x: paddle.x, y: paddle.y, width: paddle.width, height: paddle.height };
|
||||
}
|
||||
|
||||
function showGameOver() {
|
||||
g.clear();
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(1, 0, 0);
|
||||
g.drawString("GAME OVER", g.getWidth() / 2, g.getHeight() / 2 - 20);
|
||||
g.setFont("6x8", 1);
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawString("Score: " + score, g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.drawString("High: " + highScore, g.getWidth() / 2, g.getHeight() / 2 + 12);
|
||||
g.drawString("BTN2 = Restart", g.getWidth() / 2, g.getHeight() / 2 + 28);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function collisionDetection() {
|
||||
for (var i = 0; i < bricks.length; i++) {
|
||||
if (bricks[i]) {
|
||||
var c = i % BRICK_COLS;
|
||||
var r = Math.floor(i / BRICK_COLS);
|
||||
var brickX = BRICK_OFFSET_LEFT + c * (BRICK_WIDTH + BRICK_PADDING);
|
||||
var brickY = BRICK_OFFSET_TOP + r * (BRICK_HEIGHT + BRICK_PADDING);
|
||||
if (ball.x + ball.radius > brickX && ball.x - ball.radius < brickX + BRICK_WIDTH && ball.y + ball.radius > brickY && ball.y - ball.radius < brickY + BRICK_HEIGHT) {
|
||||
ball.dy = -ball.dy;
|
||||
bricks[i] = 0;
|
||||
score += 10;
|
||||
g.setColor(0, 0, 0).fillRect(brickX, brickY, brickX + BRICK_WIDTH - 1, brickY + BRICK_HEIGHT - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function allBricksCleared() {
|
||||
for (var i = 0; i < bricks.length; i++) {
|
||||
if (bricks[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function resetAfterLifeLost() {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
ball.x = g.getWidth() / 2;
|
||||
ball.y = g.getHeight() - 40;
|
||||
ball.dx = (Math.random() > 0.5 ? 1 : -1) * 3;
|
||||
ball.dy = -3;
|
||||
paddle.x = g.getWidth() / 2 - PADDLE_WIDTH / 2;
|
||||
ball.prevPos = null;
|
||||
paddle.prevPos = null;
|
||||
g.clear();
|
||||
drawBricks();
|
||||
draw();
|
||||
setTimeout(() => { interval = setInterval(update, 50); }, 1000);
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (paused || gameOver) return;
|
||||
if (paddleMove) {
|
||||
paddle.x += paddleMove * PADDLE_SPEED;
|
||||
if (paddle.x < 0) paddle.x = 0;
|
||||
if (paddle.x + paddle.width > g.getWidth()) {
|
||||
paddle.x = g.getWidth() - paddle.width;
|
||||
}
|
||||
}
|
||||
ball.x += ball.dx;
|
||||
ball.y += ball.dy;
|
||||
if (ball.x + ball.radius > g.getWidth() || ball.x - ball.radius < 0) {
|
||||
ball.dx = -ball.dx;
|
||||
}
|
||||
if (ball.y - ball.radius < 0) {
|
||||
ball.dy = -ball.dy;
|
||||
}
|
||||
if (ball.y + ball.radius >= paddle.y && ball.x >= paddle.x && ball.x <= paddle.x + paddle.width) {
|
||||
ball.dy = -ball.dy;
|
||||
ball.y = paddle.y - ball.radius;
|
||||
}
|
||||
if (ball.y + ball.radius > g.getHeight()) {
|
||||
lives--;
|
||||
if (lives > 0) {
|
||||
resetAfterLifeLost();
|
||||
return;
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
saveHighScore();
|
||||
gameOver = true;
|
||||
setTimeout(showGameOver, 50);
|
||||
return;
|
||||
}
|
||||
}
|
||||
collisionDetection();
|
||||
if (allBricksCleared()) {
|
||||
level++;
|
||||
ball.dx = (Math.random() > 0.5 ? 1 : -1) * Math.abs(ball.dx) * SPEED_MULTIPLIER;
|
||||
ball.dy *= SPEED_MULTIPLIER;
|
||||
initBricks();
|
||||
ball.prevPos = null;
|
||||
paddle.prevPos = null;
|
||||
g.clear();
|
||||
drawBricks();
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function showGetReady(callback) {
|
||||
g.clear();
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(1, 1, 0);
|
||||
g.drawString("GET READY!", g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.flip();
|
||||
setTimeout(() => {
|
||||
g.clear();
|
||||
drawBricks();
|
||||
draw();
|
||||
callback();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
initGame();
|
||||
showGetReady(() => {
|
||||
interval = setInterval(update, 50);
|
||||
});
|
||||
}
|
||||
|
||||
setWatch(() => {
|
||||
if (gameOver) {
|
||||
startGame();
|
||||
return;
|
||||
}
|
||||
paused = !paused;
|
||||
if (paused) {
|
||||
drawHUD();
|
||||
g.flip();
|
||||
} else {
|
||||
g.clear();
|
||||
drawBricks();
|
||||
draw();
|
||||
}
|
||||
}, BTN2, { repeat: true, edge: "rising" });
|
||||
|
||||
setWatch(() => { paddleMove = -1; }, BTN1, { repeat: true, edge: "rising" });
|
||||
setWatch(() => { paddleMove = 0; }, BTN1, { repeat: true, edge: "falling" });
|
||||
setWatch(() => { paddleMove = 1; }, BTN3, { repeat: true, edge: "rising" });
|
||||
setWatch(() => { paddleMove = 0; }, BTN3, { repeat: true, edge: "falling" });
|
||||
|
||||
startGame();
|
||||
})();
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "bbreaker",
|
||||
"name": "BrickBreaker",
|
||||
"shortName":"BrickBreaker",
|
||||
"icon": "bbreaker.png",
|
||||
"version":"0.04",
|
||||
"description": "A simple BreakOut clone for the Banglejs",
|
||||
"tags": "game",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"bbreaker.app.js","url":"app.js"},
|
||||
{"name":"bbreaker.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -5,7 +5,7 @@
|
|||
"version": "0.01",
|
||||
"description": "Aerobic fitness test created by Léger & Lambert",
|
||||
"icon": "beeptest.png",
|
||||
"tags": "Health",
|
||||
"tags": "health",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
|
|
|
|||
|
|
@ -23,4 +23,4 @@
|
|||
};
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
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)
|
||||
0.06: Fix local unit setting
|
||||
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)
|
||||
0.06: Fix local unit setting
|
||||
0.07: Minor code improvements
|
||||
0.08: Ensure graphics state is reset at the start of a draw (such as if a widget has altered state)
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTE
|
|||
var SATinView = 0;
|
||||
|
||||
function drawFix(dat) {
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
g.reset().clearRect(0,screenYstart,screenW,screenH);
|
||||
|
||||
var v = '';
|
||||
var u='';
|
||||
|
|
@ -234,7 +234,6 @@ function drawFix(dat) {
|
|||
drawSats('View:' + SATinView);
|
||||
}
|
||||
}
|
||||
g.reset();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -404,12 +403,12 @@ function onGPS(fix) {
|
|||
}
|
||||
|
||||
function updateClock() {
|
||||
drawTime();
|
||||
g.reset();
|
||||
drawTime();
|
||||
|
||||
if ( emulator ) {
|
||||
max.spd++; max.alt++;
|
||||
const d=new Date();
|
||||
const d=new Date();
|
||||
sec=d.getSeconds();
|
||||
onGPS(lf);
|
||||
}
|
||||
|
|
@ -499,7 +498,7 @@ function nextMode() {
|
|||
|
||||
function start() {
|
||||
Bangle.setBarometerPower(1); // needs some time...
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
g.reset().clearRect(0,screenYstart,screenW,screenH);
|
||||
onGPS(lf);
|
||||
Bangle.setGPSPower(1);
|
||||
Bangle.on('GPS', onGPS);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bikespeedo",
|
||||
"name": "Bike Speedometer (beta)",
|
||||
"shortName": "Bike Speedometer",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"Screenshot.png"}],
|
||||
|
|
|
|||
|
|
@ -68,4 +68,4 @@
|
|||
|
||||
E.showMenu(appMenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
0.10: New app introduced to the app loader!
|
||||
0.20: skipped (internal revision)
|
||||
0.30: skipped (internal revision)
|
||||
0.40: added functionality for customizing colors
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Binary LED Clock
|
||||
|
||||
A binary watch with LEDs, showing time and date.
|
||||
|
||||
From top to bottom the watch face shows four rows of leds:
|
||||
|
||||
* hours (red leds)
|
||||
* minutes (green leds)
|
||||
* day (yellow leds, top row)
|
||||
* month (yellow leds, bottom row)
|
||||
|
||||
The colors are default colors and can be changed at the settings page, also, the outer ring can be disabled.
|
||||
|
||||
The rightmost LED represents 1, the second 2, the third 4, the next 8 and so on, i.e. values are the powers of two.
|
||||
|
||||
As usual, luminous leds represent a logical one, and "dark" leds a logcal '0'. Dark means the color of the background.
|
||||
Widgets aren't affected and are shown as normal.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4n/AAIHBqut8FPgH4sspk1T885/feoMI74TB1Fc51Dmfg28gKmMCrNSAgMlyo5BgV7uQIKgEhiMRkECAYMSgErolLBBIXBqIKBqEFAYMVgF0olEuAIIC4ORBQOQhIDBjMA2gOB2AIIF7JfXR67X0lvdHwQII7vSa4/TmYKBBBEtmc9a40NmYKBBBIbBmfQa4oOEBBAXFF65fXR64A/AG8IvN4AgOG62ABAuHy4IGgEHiMXAgNu91gBAtxiNwBAsAhMRjIEB73ucIIIEyMRyAIFF7BfXAH6/IttoKxRoIgEG93mQxSYIgEN93tWxTIIF7BfXAH4AGw93u/A44IDhl8vQRFBogXB0ECuGoBAcKxRxBC53Hhlyk8ggVyuQGBvlwhgNBk98BAN6I4UgC4N4BwWgAwWsC4fAk4IB0AvBAgIQBBwUIkQOBAwQXCJIIEBI4UAkQXE48sAwgXJF40mgAvDvRtCC4pfEC4WCPYJdBDYNyC4wAX"))
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,182 @@
|
|||
//Binary LED Clock (BLC) by aeMKai
|
||||
|
||||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||
|
||||
const SETTINGSFILE = "BinaryClk.settings.json";
|
||||
|
||||
// variables defined from settings
|
||||
let HourCol;
|
||||
let MinCol;
|
||||
let DayCol;
|
||||
let MonCol;
|
||||
let RingOn;
|
||||
|
||||
// color arrays
|
||||
// !!! don't change order unless change oder in BinaryClk.settings.js !!!
|
||||
// !!! order must correspond to each other between arrays !!!
|
||||
let LED_Colors = ["#FFF", "#F00", "#0F0", "#00F", "#FF0", "#F0F", "#0FF", "#000"];
|
||||
let LED_ColorNames = ["white", "red", "green", "blue", "yellow", "magenta", "cyan", "black"];
|
||||
|
||||
// load settings
|
||||
let loadSettings = function()
|
||||
{
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
// get name from setting, find index of name and assign corresponding color code by index
|
||||
HourCol = LED_Colors[LED_ColorNames.indexOf(def(settings.HourCol, "red"))];
|
||||
MinCol = LED_Colors[LED_ColorNames.indexOf(def(settings.MinCol, "green"))];
|
||||
DayCol = LED_Colors[LED_ColorNames.indexOf(def(settings.DayCol, "yellow"))];
|
||||
MonCol = LED_Colors[LED_ColorNames.indexOf(def(settings.MonCol, "yellow"))];
|
||||
RingOn = def(settings.RingOn, true);
|
||||
|
||||
delete settings; // settings in local var -> no more required
|
||||
}
|
||||
|
||||
let drawTimeout;
|
||||
|
||||
// actually draw the watch face
|
||||
let draw = function()
|
||||
{
|
||||
// Bangle.js2 -> 176x176
|
||||
var x_rgt = g.getWidth();
|
||||
var y_bot = g.getHeight();
|
||||
//var x_cntr = x_rgt / 2;
|
||||
var y_cntr = y_bot / 18*7;
|
||||
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
||||
|
||||
var white = "#FFF";
|
||||
var black = "#000";
|
||||
var bord_col = white;
|
||||
var col_off = black;
|
||||
|
||||
var col = new Array(HourCol, MinCol, DayCol, MonCol); // each #rgb
|
||||
if (g.theme.dark)
|
||||
{
|
||||
bord_col = white;
|
||||
col_off = black;
|
||||
}
|
||||
else
|
||||
{
|
||||
bord_col = black;
|
||||
col_off = white;
|
||||
}
|
||||
|
||||
let pwr2 = [1, 2, 4, 8, 16, 32]; // array with powers of 2, because poweroperator '**' doesnt work
|
||||
// maybe also faster
|
||||
|
||||
|
||||
var no_lines = 4; // 4 rows: hour (hr), minute (min), day (day), month (mon)
|
||||
var no_hour = 5;
|
||||
var no_min = 6;
|
||||
var no_day = 5;
|
||||
var no_mon = 4;
|
||||
|
||||
// arrays: [hr, min, day, mon]
|
||||
let msbits = [no_hour-1, no_min-1, no_day-1, no_mon-1]; // MSB = No bits - 1
|
||||
let rad = [13, 13, 9, 9]; // radiuses for each row
|
||||
var x_dist = 29;
|
||||
let y_dist = [0, 35, 75, 100]; // y-position from y_centr for each row from top
|
||||
// don't calc. automatic as for x, because of different spaces
|
||||
var x_offs_rgt = 15; // offset from right border (layout)
|
||||
var y_offs_cntr = 25; // vertical offset from center
|
||||
|
||||
////////////////////////////////////////
|
||||
// compute bit-pattern from time/date and draw leds
|
||||
////////////////////////////////////////
|
||||
|
||||
// date-time-array: 4x6 bit
|
||||
//var idx_hour = 0;
|
||||
//var idx_min = 1;
|
||||
//var idx_day = 2;
|
||||
//var idx_mon = 3;
|
||||
var dt_bit_arr = [[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]];
|
||||
|
||||
var date_time = new Date();
|
||||
var hour = date_time.getHours(); // 0..23
|
||||
var min = date_time.getMinutes(); // 0..59
|
||||
var day = date_time.getDate(); // 1..31
|
||||
var mon = date_time.getMonth() + 1; // GetMonth() -> 0..11
|
||||
|
||||
let dt_array = [hour, min, day, mon];
|
||||
|
||||
var line_cnt = 0;
|
||||
var cnt = 0;
|
||||
var bit_cnt = 0;
|
||||
|
||||
while (line_cnt < no_lines)
|
||||
{
|
||||
|
||||
////////////////////////////////////////
|
||||
// compute bit-pattern
|
||||
bit_cnt = msbits[line_cnt];
|
||||
|
||||
while (bit_cnt >= 0)
|
||||
{
|
||||
if (dt_array[line_cnt] >= pwr2[bit_cnt])
|
||||
{
|
||||
dt_array[line_cnt] -= pwr2[bit_cnt];
|
||||
dt_bit_arr[line_cnt][bit_cnt] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
dt_bit_arr[line_cnt][bit_cnt] = 0;
|
||||
}
|
||||
bit_cnt--;
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// draw leds (and border, if enabled)
|
||||
cnt = 0;
|
||||
|
||||
while (cnt <= msbits[line_cnt])
|
||||
{
|
||||
if (RingOn) // draw outer ring, if enabled
|
||||
{
|
||||
g.setColor(bord_col);
|
||||
g.drawCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-y_offs_cntr+y_dist[line_cnt], rad[line_cnt]);
|
||||
}
|
||||
if (dt_bit_arr[line_cnt][cnt] == 1)
|
||||
{
|
||||
g.setColor(col[line_cnt]);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColor(col_off);
|
||||
}
|
||||
g.fillCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-y_offs_cntr+y_dist[line_cnt], rad[line_cnt]-1);
|
||||
cnt++;
|
||||
}
|
||||
line_cnt++;
|
||||
}
|
||||
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function()
|
||||
{
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
// Init the settings of the app
|
||||
loadSettings();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI(
|
||||
{
|
||||
mode : "clock",
|
||||
remove : function()
|
||||
{
|
||||
// Called to unload all of the clock app
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
draw();
|
||||
setTimeout(Bangle.drawWidgets,0);
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
// Change settings for BinaryClk
|
||||
|
||||
(function(back){
|
||||
|
||||
// color array -- don't change order unless change oder in BinaryClk.js
|
||||
let LED_ColorNames = ["white", "red", "green", "blue", "yellow", "magenta", "cyan", "black"];
|
||||
|
||||
var FILE = "BinaryClk.settings.json";
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
HourCol: "red",
|
||||
MinCol: "green",
|
||||
DayCol: "yellow",
|
||||
MonCol: "yellow",
|
||||
RingOn: true,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings(){
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(startvalue, writer, values) {
|
||||
return{
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
writer(values[v]);
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(settings[name], v => settings[name] = v, values);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
var mainmenu = {
|
||||
"" : {
|
||||
"title" : "BinaryCLK"
|
||||
},
|
||||
"< Back" : () => back(),
|
||||
'Color Hour.:': stringInSettings("HourCol", LED_ColorNames),
|
||||
'Color Minute:': stringInSettings("MinCol", LED_ColorNames),
|
||||
'Color Day': stringInSettings("DayCol", LED_ColorNames),
|
||||
'Color Month:': stringInSettings("MonCol", LED_ColorNames),
|
||||
'LED ring on/off': {
|
||||
value: (settings.RingOn !== undefined ? settings.RingOn : true),
|
||||
onchange: v => {
|
||||
settings.RingOn = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Show submenues
|
||||
//var submenu1 = {
|
||||
//"": {
|
||||
// "title": "Show sub1..."
|
||||
//},
|
||||
//"< Back": () => E.showMenu(mainmenu),
|
||||
//"ItemName": stringInSettings("settingsVar", ["Yes", "No", "DontCare"]),
|
||||
//};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"HourCol": "red",
|
||||
"MinCol": "green",
|
||||
"DayCol": "yellow",
|
||||
"MonCol": "yellow",
|
||||
"RingOn": true
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id":"blc",
|
||||
"name":"Binary LED Clock",
|
||||
"version": "0.40",
|
||||
"description": "a binary LED-Clock with time and date and customizable LED-colors",
|
||||
"icon":"blc-icon.png",
|
||||
"screenshots": [{"url":"screenshot_blc_1.bmp"},{"url":"screenshot_blc_2.bmp"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"blc.app.js","url":"blc.js"},
|
||||
{"name":"blc.settings.js","url":"blc.settings.js"},
|
||||
{"name":"blc.img","url":"blc-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"blc.settings.json"}]
|
||||
}
|
||||
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
|
@ -70,5 +70,3 @@
|
|||
};
|
||||
return ci;
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,3 +72,8 @@
|
|||
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
|
||||
0.62: Handle setting for configuring BLE privacy
|
||||
0.63: Only set BLE `display:1` if we have a passkey
|
||||
0.64: Automatically create .widcache and .clkinfocache to speed up loads
|
||||
Bangle.loadWidgets overwritten with fast version on success
|
||||
Refuse to work on firmware <2v16 and remove old polyfills
|
||||
0.65: Only display interpreter errors if log is nonzero
|
||||
0.66: Ensure __FILE__ is set even after a fresh boot (fix #3857)
|
||||
|
|
@ -23,7 +23,8 @@ if (!_clkApp) {
|
|||
require("Storage").writeJSON("setting.json", s);
|
||||
}
|
||||
}
|
||||
if (s.clock) __FILE__=s.clock;
|
||||
delete s;
|
||||
if (!_clkApp) _clkApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, global.BTN2||BTN, {repeat:false,edge:"falling"});`;
|
||||
eval(_clkApp);
|
||||
delete _clkApp;
|
||||
delete _clkApp;
|
||||