Merge branch 'espruino:master' into master
12
README.md
|
@ -384,14 +384,18 @@ Example `settings.js`
|
|||
```js
|
||||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
function get(key, def) { return require('Settings').get('myappid', key, def); }
|
||||
function set(key, value) { require('Settings').set('myappid', key, value); }
|
||||
let settings = require('Storage').readJSON('myappid.json',1)||{};
|
||||
if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('myappid.json', settings);
|
||||
}
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
'Monkeys': {
|
||||
value: get('monkeys', 12),
|
||||
onchange: (m) => set('monkeys', m)
|
||||
value: settings.monkeys,
|
||||
onchange: (m) => {save('monkeys', m)}
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
|
|
602
apps.json
|
@ -1,9 +1,9 @@
|
|||
[
|
||||
{
|
||||
"id": "fwupdate",
|
||||
"name": "Firmware Update (BETA)",
|
||||
"version": "0.01",
|
||||
"description": "Uploads new Espruino firmwares to Bangle.js 2",
|
||||
"name": "Firmware Update",
|
||||
"version": "0.02",
|
||||
"description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tools,system",
|
||||
|
@ -11,12 +11,12 @@
|
|||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": [],
|
||||
"sortorder": -20
|
||||
"sortorder": 20
|
||||
},
|
||||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.36",
|
||||
"version": "0.39",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
@ -29,10 +29,55 @@
|
|||
],
|
||||
"sortorder": -10
|
||||
},
|
||||
{
|
||||
"id": "hebrew_calendar",
|
||||
"name": "Hebrew Calendar",
|
||||
"shortName": "HebCal",
|
||||
"version": "0.04",
|
||||
"description": "lists the date according to the hebrew calendar",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": false,
|
||||
"tags": "tool,locale",
|
||||
"supports": [
|
||||
"BANGLEJS",
|
||||
"BANGLEJS2"
|
||||
],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{
|
||||
"name": "hebrew_calendar.app.js",
|
||||
"url": "app.js"
|
||||
},
|
||||
{
|
||||
"name": "hebrewDate",
|
||||
"url": "hebrewDate.js"
|
||||
},
|
||||
{
|
||||
"name": "hebrew_calendar.img",
|
||||
"url": "app-icon.js",
|
||||
"evaluate": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "golfscore",
|
||||
"name": "Golf Score",
|
||||
"shortName":"golfscore",
|
||||
"version":"0.02",
|
||||
"description": "keeps track of strokes during a golf game",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoors",
|
||||
"allow_emulator": true,
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"golfscore.app.js","url":"app.js"},
|
||||
{"name":"golfscore.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.07",
|
||||
"version": "0.13",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -47,14 +92,15 @@
|
|||
{"name":"messages","url":"lib.js"}
|
||||
],
|
||||
"data": [{"name":"messages.json"},{"name":"messages.settings.json"}],
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}],
|
||||
"sortorder": -9
|
||||
},
|
||||
{
|
||||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.04",
|
||||
"description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.",
|
||||
"version": "0.05",
|
||||
"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",
|
||||
"dependencies": {"messages":"app"},
|
||||
|
@ -70,8 +116,8 @@
|
|||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.03",
|
||||
"description": "(BETA) App to display notifications from iOS devices",
|
||||
"version": "0.07",
|
||||
"description": "Display notifications/music/etc from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
"dependencies": {"messages":"app"},
|
||||
|
@ -87,7 +133,7 @@
|
|||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"version": "0.08",
|
||||
"description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
|
||||
"description": "Logs health data and provides an app to view it (requires firmware 2v10.100 or later)",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
|
@ -104,7 +150,7 @@
|
|||
"id": "launch",
|
||||
"name": "Launcher",
|
||||
"shortName": "Launcher",
|
||||
"version": "0.08",
|
||||
"version": "0.10",
|
||||
"description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"icon": "app.png",
|
||||
"type": "launch",
|
||||
|
@ -112,14 +158,16 @@
|
|||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"launch.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]},
|
||||
{"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]}
|
||||
{"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]},
|
||||
{"name":"launch.settings.js","url":"settings.js","supports":["BANGLEJS2"]}
|
||||
],
|
||||
"data": [{"name":"launch.json"}],
|
||||
"sortorder": -10
|
||||
},
|
||||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.33",
|
||||
"version": "0.37",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
@ -135,7 +183,7 @@
|
|||
{
|
||||
"id": "about",
|
||||
"name": "About",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system",
|
||||
|
@ -170,7 +218,7 @@
|
|||
{
|
||||
"id": "locale",
|
||||
"name": "Languages",
|
||||
"version": "0.10",
|
||||
"version": "0.14",
|
||||
"description": "Translations for different countries",
|
||||
"icon": "locale.png",
|
||||
"type": "locale",
|
||||
|
@ -236,16 +284,17 @@
|
|||
"id": "mywelcome",
|
||||
"name": "Customised Welcome",
|
||||
"shortName": "My Welcome",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
|
||||
"icon": "app.png",
|
||||
"tags": "start,welcome",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"screenshots": [{"url":"bangle1-customized-welcome-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"mywelcome.boot.js","url":"boot.js"},
|
||||
{"name":"mywelcome.app.js","url":"app.js"},
|
||||
{"name":"mywelcome.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]},
|
||||
{"name":"mywelcome.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]},
|
||||
{"name":"mywelcome.settings.js","url":"settings.js"},
|
||||
{"name":"mywelcome.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
|
@ -254,8 +303,8 @@
|
|||
{
|
||||
"id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"version": "0.24",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android. This will eventually be replaced by the 'Android' app.",
|
||||
"version": "0.25",
|
||||
"description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android' Bangle.js app instead.",
|
||||
"icon": "app.png",
|
||||
"type": "widget",
|
||||
"tags": "tool,system,android,widget",
|
||||
|
@ -269,6 +318,20 @@
|
|||
],
|
||||
"data": [{"name":"gbridge.json"}]
|
||||
},
|
||||
{ "id": "gbdebug",
|
||||
"name": "Gadgetbridge Debug",
|
||||
"shortName":"GB Debug",
|
||||
"version":"0.01",
|
||||
"description": "Debug info for Gadgetbridge. Run this app and when Gadgetbridge messages arrive they are displayed on-screen.",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gbdebug.app.js","url":"app.js"},
|
||||
{"name":"gbdebug.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mclock",
|
||||
"name": "Morphing Clock",
|
||||
|
@ -449,22 +512,22 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "mandlebrotclock",
|
||||
"name": "Mandlebrot Clock",
|
||||
"id": "mandelbrotclock",
|
||||
"name": "Mandelbrot Clock",
|
||||
"version": "0.01",
|
||||
"description": "A mandlebrot set themed clock cool",
|
||||
"icon": "mandlebrotclock.png",
|
||||
"screenshots": [{ "url": "screenshot_mandlebrotclock.png" }],
|
||||
"description": "A mandelbrot set themed clock cool",
|
||||
"icon": "mandelbrotclock.png",
|
||||
"screenshots": [{ "url": "screenshot_mandelbrotclock.png" }],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{ "name": "mandlebrotclock.app.js", "url": "mandlebrotclock.js" },
|
||||
{ "name": "mandelbrotclock.app.js", "url": "mandelbrotclock.js" },
|
||||
{
|
||||
"name": "mandlebrotclock.img",
|
||||
"url": "mandlebrotclock-icon.js",
|
||||
"name": "mandelbrotclock.img",
|
||||
"url": "mandelbrotclock-icon.js",
|
||||
"evaluate": true
|
||||
}
|
||||
]
|
||||
|
@ -496,7 +559,7 @@
|
|||
"icon": "clock-impword.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -685,10 +748,11 @@
|
|||
{
|
||||
"id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"version": "0.26",
|
||||
"version": "0.27",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
|
@ -781,7 +845,7 @@
|
|||
{
|
||||
"id": "weather",
|
||||
"name": "Weather",
|
||||
"version": "0.11",
|
||||
"version": "0.13",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
@ -872,7 +936,7 @@
|
|||
"id": "widbatpc",
|
||||
"name": "Battery Level Widget (with percentage)",
|
||||
"shortName": "Battery Widget",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
@ -1084,7 +1148,7 @@
|
|||
{
|
||||
"id": "qrcode",
|
||||
"name": "Custom QR Code",
|
||||
"version": "0.02",
|
||||
"version": "0.04",
|
||||
"description": "Use this to upload a customised QR code to Bangle.js",
|
||||
"icon": "app.png",
|
||||
"tags": "qrcode",
|
||||
|
@ -1635,14 +1699,15 @@
|
|||
"id": "rtorch",
|
||||
"name": "Red Torch",
|
||||
"shortName": "RedTorch",
|
||||
"version": "0.01",
|
||||
"description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets",
|
||||
"version": "0.02",
|
||||
"description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or on Bangle 1 press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,torch",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"rtorch.app.js","url":"app.js"},
|
||||
{"name":"rtorch.wid.js","url":"widget.js"},
|
||||
{"name":"rtorch.wid.js","url":"widget.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"rtorch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
@ -1712,7 +1777,7 @@
|
|||
"id": "cliock",
|
||||
"name": "Commandline-Clock",
|
||||
"shortName": "CLI-Clock",
|
||||
"version": "0.14",
|
||||
"version": "0.15",
|
||||
"description": "Simple CLI-Styled Clock",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_cli.png"}],
|
||||
|
@ -1894,6 +1959,19 @@
|
|||
{"name":"widmp.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widmpsh",
|
||||
"name": "Moon Phase Widget Southern Hemisphere",
|
||||
"version": "0.01",
|
||||
"description": "Display the current moon phase in blueish for the southern hemisphere in eight phases",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,tools",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widmpsh.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "minionclk",
|
||||
"name": "Minion clock",
|
||||
|
@ -1914,11 +1992,12 @@
|
|||
"id": "openstmap",
|
||||
"name": "OpenStreetMap",
|
||||
"shortName": "OpenStMap",
|
||||
"version": "0.09",
|
||||
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
|
||||
"version": "0.11",
|
||||
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoors,gps",
|
||||
"tags": "outdoors,gps,osm",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": [
|
||||
|
@ -1948,11 +2027,12 @@
|
|||
"id": "chronowid",
|
||||
"name": "Chrono Widget",
|
||||
"shortName": "Chrono Widget",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Chronometer (timer) which runs as widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"chronowid.wid.js","url":"widget.js"},
|
||||
|
@ -2041,12 +2121,12 @@
|
|||
"id": "numerals",
|
||||
"name": "Numerals Clock",
|
||||
"shortName": "Numerals Clock",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "A simple big numerals clock",
|
||||
"icon": "numerals.png",
|
||||
"type": "clock",
|
||||
"tags": "numerals,clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-numerals-screenshot.png"}],
|
||||
"storage": [
|
||||
|
@ -2086,6 +2166,20 @@
|
|||
{"name":"snake.img","url":"snake-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "snek",
|
||||
"name": "The snek game",
|
||||
"shortName":"Snek",
|
||||
"version": "0.02",
|
||||
"description": "A snek game where you control a snek to eat all the apples!",
|
||||
"screenshots": [{"url":"screenshot_snek.png"}],
|
||||
"icon": "snek.png",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"tags": "game,fun",
|
||||
"storage": [
|
||||
{"name":"snek.app.js","url":"snek.js"},
|
||||
{"name":"snek.img","url":"snek.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "calculator",
|
||||
"name": "Calculator",
|
||||
|
@ -2335,7 +2429,7 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Simple calendar",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
|
@ -2345,8 +2439,10 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"calendar.app.js","url":"calendar.js"},
|
||||
{"name":"calendar.settings.js","url":"settings.js"},
|
||||
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"calendar.json"}]
|
||||
},
|
||||
{
|
||||
"id": "hidjoystick",
|
||||
|
@ -2584,12 +2680,12 @@
|
|||
"id": "widviz",
|
||||
"name": "Widget Visibility Widget",
|
||||
"shortName": "Viz Widget",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Swipe left to hide top bar widgets, swipe right to redisplay.",
|
||||
"icon": "eye.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widviz.wid.js","url":"widget.js"}
|
||||
]
|
||||
|
@ -3201,7 +3297,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.05",
|
||||
"version": "0.07",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
@ -3212,8 +3308,11 @@
|
|||
"storage": [
|
||||
{"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]},
|
||||
{"name":"dtlaunch.settings.js","url":"settings-b1.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"dtlaunch.settings.js","url":"settings-b2.js", "supports": ["BANGLEJS2"]},
|
||||
{"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"dtlaunch.json"}]
|
||||
},
|
||||
{
|
||||
"id": "HRV",
|
||||
|
@ -3722,7 +3821,7 @@
|
|||
"id": "gbmusic",
|
||||
"name": "Gadgetbridge Music Controls",
|
||||
"shortName": "Music Controls",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
|
||||
|
@ -3797,10 +3896,11 @@
|
|||
"id": "qmsched",
|
||||
"name": "Quiet Mode Schedule and Widget",
|
||||
"shortName": "Quiet Mode",
|
||||
"version": "0.03",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||
"version": "0.05",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}],
|
||||
"screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"},
|
||||
{"url":"screenshot_b2_main.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_lcd.png"}],
|
||||
"tags": "tool,widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
@ -3914,11 +4014,11 @@
|
|||
{
|
||||
"id": "thermom",
|
||||
"name": "Thermometer",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Displays the current temperature in degree Celsius, updated every 20 seconds",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"thermom.app.js","url":"app.js"},
|
||||
|
@ -4021,7 +4121,7 @@
|
|||
{"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"carcrazy.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"app.json"}]
|
||||
"data": [{"name":"CarCrazy.csv"}]
|
||||
},
|
||||
{
|
||||
"id": "shortcuts",
|
||||
|
@ -4043,14 +4143,17 @@
|
|||
{
|
||||
"id": "vectorclock",
|
||||
"name": "Vector Clock",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A digital clock that uses the built-in vector font.",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-vector-clock-screenshot.png"}],
|
||||
"screenshots": [
|
||||
{"url":"bangle2-vector-clock-screenshot.png"},
|
||||
{"url":"bangle1-vector-clock-screenshot.png"}
|
||||
],
|
||||
"storage": [
|
||||
{"name":"vectorclock.app.js","url":"app.js"},
|
||||
{"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
|
||||
|
@ -4106,9 +4209,10 @@
|
|||
"id": "pastel",
|
||||
"name": "Pastel Clock",
|
||||
"shortName": "Pastel",
|
||||
"version": "0.07",
|
||||
"description": "A Configurable clock with custom fonts and background",
|
||||
"version": "0.08",
|
||||
"description": "A Configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||
"icon": "pastel.png",
|
||||
"dependencies": {"mylocation":"app", "widpedom":"app"},
|
||||
"screenshots": [{"url":"screenshot_pastel.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
@ -4149,7 +4253,7 @@
|
|||
"id": "waveclk",
|
||||
"name": "Wave Clock",
|
||||
"version": "0.02",
|
||||
"description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**",
|
||||
"description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: Works on any Bangle.js 2, but requires firmware 2v11 or later on Bangle.js 1**",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
|
@ -4165,7 +4269,7 @@
|
|||
"id": "floralclk",
|
||||
"name": "Floral Clock",
|
||||
"version": "0.01",
|
||||
"description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**",
|
||||
"description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: Works on any Bangle.js 2 but requires firmware 2v11 or later on Bangle.js 1**",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_floral.png"}],
|
||||
"type": "clock",
|
||||
|
@ -4280,7 +4384,7 @@
|
|||
"version": "0.01",
|
||||
"description": "A touch based GPS watch, shows OS map reference",
|
||||
"icon": "gpstouch.png",
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
|
||||
"screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
|
||||
"tags": "tools,app",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
@ -4309,7 +4413,7 @@
|
|||
"name": "Q Alarm and Timer",
|
||||
"shortName": "Q Alarm",
|
||||
"icon": "app.png",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Alarm and timer app with days of week and 'hard' option.",
|
||||
"tags": "tool,alarm,widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
|
@ -4327,7 +4431,7 @@
|
|||
"id": "emojuino",
|
||||
"name": "Emojuino",
|
||||
"shortName": "Emojuino",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.",
|
||||
"icon": "emojuino.png",
|
||||
"screenshots": [
|
||||
|
@ -4349,7 +4453,7 @@
|
|||
"id": "cliclockJS2Enhanced",
|
||||
"name": "Commandline-Clock JS2 Enhanced",
|
||||
"shortName": "CLI-Clock JS2",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screengrab.png"}],
|
||||
|
@ -4367,9 +4471,9 @@
|
|||
"name": "A Battery Widget (with percentage)",
|
||||
"shortName":"A Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"1.01",
|
||||
"version":"1.02",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Simple and slim battery widget with charge status and percentage",
|
||||
"tags": "widget,battery",
|
||||
|
@ -4433,7 +4537,7 @@
|
|||
"shortName": "AuthWatch",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version": "0.01",
|
||||
"version": "0.04",
|
||||
"description": "Google Authenticator compatible tool.",
|
||||
"tags": "tool",
|
||||
"interface": "interface.html",
|
||||
|
@ -4462,7 +4566,7 @@
|
|||
{"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"app.json"}
|
||||
{"name":"calendarItems.csv"}
|
||||
]
|
||||
},
|
||||
{ "id": "timecal",
|
||||
|
@ -4515,7 +4619,7 @@
|
|||
"shortName":"93 Dub",
|
||||
"icon": "93dub.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.03",
|
||||
"version":"0.05",
|
||||
"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",
|
||||
|
@ -4533,9 +4637,10 @@
|
|||
"version":"0.01",
|
||||
"description": "Simple app to power off your Bangle.js",
|
||||
"icon": "app.png",
|
||||
"tags": "poweroff, shutdown",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"tags": "tool, poweroff, shutdown",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"poweroff.app.js","url":"app.js"},
|
||||
{"name":"poweroff.img","url":"app-icon.js","evaluate":true}
|
||||
|
@ -4545,9 +4650,17 @@
|
|||
"id": "sensible",
|
||||
"name": "SensiBLE",
|
||||
"shortName": "SensiBLE",
|
||||
"version": "0.02",
|
||||
"version": "0.04",
|
||||
"description": "Collect, display and advertise real-time sensor data.",
|
||||
"icon": "sensible.png",
|
||||
"screenshots": [
|
||||
{ "url": "screenshot-top.png" },
|
||||
{ "url": "screenshot-acc.png" },
|
||||
{ "url": "screenshot-bar.png" },
|
||||
{ "url": "screenshot-gps.png" },
|
||||
{ "url": "screenshot-hrm.png" },
|
||||
{ "url": "screenshot-mag.png" }
|
||||
],
|
||||
"type": "app",
|
||||
"tags": "tool,sensors",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
|
@ -4557,5 +4670,338 @@
|
|||
{ "name": "sensible.app.js", "url": "sensible.js" },
|
||||
{ "name": "sensible.img", "url": "sensible-icon.js", "evaluate": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "widbars",
|
||||
"name": "Bars Widget",
|
||||
"version": "0.01",
|
||||
"description": "Display several measurements as vertical bars.",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widbars.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"a_speech_timer",
|
||||
"name":"Speech Timer",
|
||||
"icon": "app.png",
|
||||
"version":"1.01",
|
||||
"description": "A timer designed to help keeping your speeches and presentations to time.",
|
||||
"tags": "tool,timer",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"a_speech_timer.app.js","url":"app.js"},
|
||||
{"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "mylocation",
|
||||
"name": "My Location",
|
||||
"shortName":"My Location",
|
||||
"icon": "mylocation.png",
|
||||
"type": "app",
|
||||
"screenshots": [{"url":"screenshot_1.png"}],
|
||||
"version":"0.01",
|
||||
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
|
||||
"readme": "README.md",
|
||||
"tags": "tool,utility",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"mylocation.app.js","url":"mylocation.app.js"},
|
||||
{"name":"mylocation.img","url":"mylocation.icon.js","evaluate": true }
|
||||
],
|
||||
"data": [
|
||||
{"name":"mylocation.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pebble",
|
||||
"name": "Pebble Clock",
|
||||
"shortName": "Pebble",
|
||||
"version": "0.04",
|
||||
"description": "A pebble style clock to keep the rebellion going",
|
||||
"readme": "README.md",
|
||||
"icon": "pebble.png",
|
||||
"screenshots": [{"url":"pebble_screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"pebble.app.js","url":"pebble.app.js"},
|
||||
{"name":"pebble.settings.js","url":"pebble.settings.js"},
|
||||
{"name":"pebble.img","url":"pebble.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "pooqroman",
|
||||
"name": "pooq Roman watch face",
|
||||
"shortName":"pooq Roman",
|
||||
"version":"0.03",
|
||||
"description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"pooqroman.app.js","url":"app.js"},
|
||||
{"name":"pooqroman.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"pooqroman.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widbata",
|
||||
"name": "Battery Level Widget (Themed)",
|
||||
"shortName":"Battery Theme",
|
||||
"icon": "widbata.png",
|
||||
"screenshots": [{"url":"screenshot_widbata_1.png"}],
|
||||
"version":"0.01",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Shows the current battery level status in the top right using the clocks colour theme",
|
||||
"tags": "widget,battery",
|
||||
"storage": [
|
||||
{"name":"widbata.wid.js","url":"widbata.wid.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weatherClock",
|
||||
"name": "Weather Clock",
|
||||
"version": "0.04",
|
||||
"description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screens/screen1.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock, weather",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"weatherClock.app.js","url":"app.js"},
|
||||
{"name":"weatherClock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menuwheel",
|
||||
"name": "Wheel Menus",
|
||||
"version": "0.01",
|
||||
"description": "Replace Bangle.js 2's menus with a version that contains variable-size text and a back button",
|
||||
"readme": "README.md",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [
|
||||
{"url":"screenshot_b1_dark.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_light.png"},
|
||||
{"url":"screenshot_b2_dark.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_light.png"}
|
||||
],
|
||||
"type": "boot",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"menuwheel.boot.js","url":"boot.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "widChargingStatus",
|
||||
"name": "Charging Status",
|
||||
"shortName":"ChargingStatus",
|
||||
"icon": "widget.png",
|
||||
"version":"0.1",
|
||||
"type": "widget",
|
||||
"description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.",
|
||||
"tags": "widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widChargingStatus.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow",
|
||||
"name": "FLOW",
|
||||
"shortName": "FLOW",
|
||||
"version": "0.01",
|
||||
"description": "A game where you have to help a flow avoid white obstacles thing by tapping! This is a demake of an app which I forgot the name of. Press BTN(1) to restart. See if you can get to 2500 score!",
|
||||
"icon": "app.png",
|
||||
"tags": "game",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name": "flow.app.js", "url": "app.js" },
|
||||
{"name": "flow.img", "url": "app-icon.js","evaluate": true }
|
||||
]
|
||||
},
|
||||
{ "id": "scribble",
|
||||
"name": "Scribble",
|
||||
"shortName":"Scribble",
|
||||
"version":"0.01",
|
||||
"type": "app",
|
||||
"description": "A keyboard on your wrist! Swipe right for space, left for delete.",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
"tags": "tools, keyboard, text, scribble",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"scribble.app.js","url":"app.js"},
|
||||
{"name":"scribble.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"screenshots":[
|
||||
{ "url":"screenshot.png" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ptlaunch",
|
||||
"name": "Pattern Launcher",
|
||||
"shortName": "Pattern Launcher",
|
||||
"version": "0.10",
|
||||
"description": "Directly launch apps from the clock screen with custom patterns.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"main_menu_add.png"}, {"url":"add_pattern.png"}, {"url":"select_app.png"}, {"url":"main_menu_manage.png"}, {"url":"manage_patterns.png"}],
|
||||
"tags": "tools",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "ptlaunch.app.js", "url": "app.js" },
|
||||
{ "name": "ptlaunch.boot.js", "url": "boot.js" },
|
||||
{ "name": "ptlaunch.img", "url": "app-icon.js", "evaluate": true }
|
||||
],
|
||||
"data": [{"name":"ptlaunch.patterns.json"}]
|
||||
},
|
||||
{
|
||||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
"shortName": "Rebble",
|
||||
"version": "0.02",
|
||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||
"readme": "README.md",
|
||||
"icon": "rebble.png",
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"screenshots": [{"url":"screenshot_rebble.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"rebble.app.js","url":"rebble.app.js"},
|
||||
{"name":"rebble.settings.js","url":"rebble.settings.js"},
|
||||
{"name":"rebble.img","url":"rebble.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "snaky",
|
||||
"name": "Snaky",
|
||||
"shortName":"Snaky",
|
||||
"version":"0.01",
|
||||
"description": "The classic snake game. Eat apples and don't bite your tail. Control the snake with the touch screen.",
|
||||
"tags": "game,fun",
|
||||
"icon": "snaky.png",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"snaky.app.js","url":"snaky.js"},
|
||||
{"name":"snaky.img","url":"snaky-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "clicompleteclk",
|
||||
"name": "CLI complete clock",
|
||||
"shortName":"CLI cmplt clock",
|
||||
"version":"0.03",
|
||||
"description": "Command line styled clock with lots of information",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
"type": "clock",
|
||||
"tags": "clock,cli,command,bash,shell,weather,hrt",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"clicompleteclk.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"clicompleteclk.json"}]
|
||||
},
|
||||
{
|
||||
"id":"awairmonitor",
|
||||
"name":"Awair Monitor",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
"version":"0.01",
|
||||
"description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
|
||||
"tags": "tool,health",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"awairmonitor.app.js","url":"app.js"},
|
||||
{"name":"awairmonitor.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "pooqround",
|
||||
"name": "pooq Round watch face",
|
||||
"shortName":"pooq Round",
|
||||
"version":"0.00",
|
||||
"description": "A 24 hour analogue watchface with high legibility and a novel style.",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"pooqround.app.js","url":"app.js"},
|
||||
{"name":"pooqround.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"pooqround.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "coretemp",
|
||||
"name": "Core Temp Display",
|
||||
"version": "0.01",
|
||||
"description": "Display CoreTemp device sensor data",
|
||||
"icon": "coretemp.png",
|
||||
"type": "app",
|
||||
"tags": "health",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"coretemp.boot.js","url":"boot.js"},
|
||||
{"name":"coretemp.app.js","url":"coretemp.js"},
|
||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "showimg",
|
||||
"name": "simple image viewer",
|
||||
"shortName":"showImage",
|
||||
"version":"0.1",
|
||||
"description": "Displays the image file in showimage.user.img. Returns to watch face after 60s or button push. I use it to display my vaccination certificate.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"showimg.app.js","url":"app.js"},
|
||||
{"name":"showimg.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "lapcounter",
|
||||
"name": "Lap Counter",
|
||||
"version": "0.01",
|
||||
"description": "Click button to count laps. Shows count and total time snapshot (like a stopwatch, but laid back).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"lapcounter.app.js","url":"app.js"},
|
||||
{"name":"lapcounter.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
0.01: Initial version for upload
|
||||
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
|
||||
0.03: Code style cleanup
|
||||
0.04: Set 00:00 to 12:00 for 12 hour time
|
||||
0.05: Display time, even on Thursday
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
Uses many portions from Espruino documentation, example watchfaces, and the waveclk app. It also sourced from Jon Barlow's 91 Dub v2.0 source code and resources and adapted for Bangle.js 2's screen. Time, date and the battery display works. It is not pixel perfect to the original.
|
||||
|
||||
Contributors:
|
||||
Leer10
|
||||
Orviwan (original watchface and assets)
|
||||
Gordon Williams (Bangle.js, watchapps for reference code and documentation)
|
||||
DiscoMinotaur (adjustments)
|
||||
* Leer10
|
||||
* Orviwan (original watchface and assets)
|
||||
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)
|
||||
* DiscoMinotaur (adjustments)
|
||||
* Ray Holder (minor 12 hour time rendering adjustment, fix Thursdays)
|
||||
|
|
|
@ -78,6 +78,9 @@ function draw(){
|
|||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
} else if (h === 0) {
|
||||
// display 12:00 instead of 00:00 for 12 hr mode
|
||||
h = "12";
|
||||
}
|
||||
|
||||
//draw separator
|
||||
|
@ -90,7 +93,7 @@ function draw(){
|
|||
if (w == 1) {imgW = imgMon;}
|
||||
if (w == 2) {imgW = imgTue;}
|
||||
if (w == 3) {imgW = imgWed;}
|
||||
if (w == 4) {imgW = imgThr;}
|
||||
if (w == 4) {imgW = imgThu;}
|
||||
if (w == 5) {imgW = imgFri;}
|
||||
if (w == 6) {imgW = imgSat;}
|
||||
g.drawImage(imgW, 85, 63);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
1.00: Release (2021/12/01)
|
||||
1.01: Grey font when timer is frozen (2021/12/04)
|
|
@ -0,0 +1,16 @@
|
|||
# A Speech Timer
|
||||
|
||||
* A timer designed to help keeping your speeches and presentations to time
|
||||
* Vibrates 1-2-3 times and changes screen color within the target time range.
|
||||
* Example for a 5 to 7 minutes speech: vibrates once at 5:00 (green), twice at 6:00 (yellow), thrice at 7:00 (red).
|
||||
* Use the buttons to start a timer
|
||||
* Swipe left or right to choose different target times
|
||||
* Touching the timer on the upper part of the screen locks (or unlocks) the buttons to prevent accidental changes
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP//kAj//AAP5/+PApH7//PAonvAoXzAonj//nApHggEHAoWAgA5BAAJCCAoU/IYIFCv///w0CAonrv/HAoXLv+DAogLFgPeAoV+nlOAoV4/8+AoV79+eFIVzAof7u/v5xBCs4FL84FE//O74FBu4FB64FD73TAoNz/+eAoV5IIIFCvl8vwFCv8A/wFDO4IFFFIQFCGoSVFUIqtDh65D/1vYof+Y4LLDw7dD/0ndIYRCeoQFC/P/z/+i///oFBGoX8gEfAgI="))
|
|
@ -0,0 +1,173 @@
|
|||
Graphics.prototype.setFontMichroma36 = function() {
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAAAAAAAAAAAAAAAAAAAGAAAAA+AAAAD+AAAAP+AAAA/8AAAD/wAAAf/AAAB/4AAAH/gAAAf+AAAB/4AAAH/gAAAf+AAAAfwAAAAfAAAAAcAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AP///8APwAD+APAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAeAPAAAeAPwAD+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAEAAAAAOAAAAAfAAAAA+AAAAB8AAAAD8AAAAH4AAAAPwAAAAPgAAAAfAAAAAf///+Af///+Af///+Af///+AAAAAAAAAAAAAAAAAAAAAAAAAA/Af+AD/A/+AH/B/+AP/D/+APwD4eAPADweAfADweAeADweAeADweAeADweAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAPgeAeAPAeAeAPAeAeAPAeAeAPAeAfAPAeAPw/AeAP/+AeAH/+AeAD/8AeAB/wAOAAAAAAAAAAAAAAAAAAAAAAAAAB8APgAD8AP4AH8AP8AP8AP8APgAB+AfAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAeAfAeAeAPx/h+AP///+AH///8AD///4AB/h/gAAAAAAAAAAAAAAAAAAAAAAeAAAAA/AAAAA/AAAAB/AAAAD/AAAAH/AAAAPvAAAAPPAAAAfPAAAA+PAAAB8PAAAD4PAAADwPAAAHwPAAAPgPAAAfAPAAA+APAAA8APAAB8APAAD4APAAHwAPAAPgAPAAPAAPAAfAAPAAf///+Af///+Af///+Af///+AAAAPAAAAAPAAAAAPAAAAAPAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAf/8PgAf/8P4Af/8P8Af/8P8AeB4A+AeB4AeAeDwAeAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAfAeDwAeAeD4A+AeD+D+AeB//8AeB//4AeA//4AAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AH///8AP4fB+APAeAeAfA8AeAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAfA8APAPA+AeAPgeAeAP8fh+AH8f/8AD8P/8AA8H/4AAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAACAeAAAGAeAAAOAeAAAeAeAAA+AeAAD+AeAAH8AeAAP4AeAAfwAeAA/gAeAB/AAeAD+AAeAP4AAeAfwAAeA/gAAeB/AAAeD+AAAeH8AAAefwAAAe/gAAAf/AAAAf+AAAAf8AAAAf4AAAAfgAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAMAAB+B/wAD/j/4AH/3/8AP///+AP//A+AfB+AeAeA+AeAeA+APAeA+APAeA+APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA+APAeA+APAeA+APAeA+AOAeA+AeAPh/A+AP///+AP/3/8AH/3/8AB/D/wAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAD/4HAAH/8HwAP/+H4AP5/H8AfAfA8AeAPAeAeAPAeAeAPAeAeAHgfAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHAPAeAPAOAeAPAeAPAPAeAPwfB+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAAAAAAAB8DwAAB8HwAAB8HwAAB8DwAAAAAAAAAAAAA"), 46, atob("CBIkESMjJCMjIyMjCA=="), 36+(1<<8)+(1<<16));
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontMichroma16 = function(scale) {
|
||||
g.setFontCustom(atob("AAAAGAAYAAAAGAB4A/APwD4AeADgAAAAAAA/8H/4YBjAGMAcwBzAHMAcwBzAHMAYYBh/+D/wAAAAABgAOABwAGAA//h/+AAAAAA4+Hn4YZjhmMOYw5jDmMMYwxjDGOMYYxh/GD4YAAAAADBwcHhgGOAYwBzHHMccxxzHHMcc5xhnGH/4PfAAAAAAAOAB4APgB2AGYAxgHGA4YDBgYGD/+P/4AOAAYAAAAAD+cP547BjsGOwc7BzsHOwc7BzsHOwY7zjv+APgAAAAAD/wf/hmGOYYxhzGHMYcxhzGHOYYZhh3uDP4AeAAAEAA4ADgAOAI4DjgeODw4eDjgOcA7gD8APgA8AAAAAAAAAA58H/4bxjmGMYcxhzGHMYcxhzGHOYYbxh/+DnwAAAAADxgfnBnOOMYwxjDHMMcwxzDHMMY4xhjOH/4P/AAAAAABnAGcAAA"), 46, atob("BAgQCBAQEBAQEBAQBA=="), 16+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
function timeToString(duration) {
|
||||
var hrs = ~~(duration / 3600);
|
||||
var mins = ~~((duration % 3600) / 60);
|
||||
var secs = ~~duration % 60;
|
||||
var ret = "";
|
||||
if (hrs > 0) {
|
||||
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
|
||||
}
|
||||
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
|
||||
ret += "" + secs;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var newtimer_left_from = 60;
|
||||
var newtimer_left_to = 2*60;
|
||||
|
||||
var newtimer_right_from = 5*60;
|
||||
var newtimer_right_to = 7*60;
|
||||
|
||||
var current_from = 5*60;
|
||||
var current_mid = 6*60;
|
||||
var current_to = 7*60;
|
||||
var current_value = 0;
|
||||
|
||||
var timerinterval;
|
||||
var istimeron = false;
|
||||
|
||||
var islocked = false;
|
||||
|
||||
function countDown() {
|
||||
current_value++;
|
||||
draw();
|
||||
|
||||
if (current_value == current_from) {
|
||||
Bangle.buzz(500);
|
||||
} else if (current_value == current_mid) {
|
||||
Bangle.buzz(400).then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 800));
|
||||
}).then(()=>{
|
||||
return Bangle.buzz(500);
|
||||
});
|
||||
} else if (current_value == current_to) {
|
||||
Bangle.buzz(300).then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 600));
|
||||
}).then(()=>{
|
||||
Bangle.buzz(300).then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 600));
|
||||
}).then(()=>{
|
||||
return Bangle.buzz(500);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Bangle.on('touch',(touchside, touchdata)=>{
|
||||
if (!islocked && istimeron && touchdata.y > (100+10)) {
|
||||
Bangle.buzz(40);
|
||||
istimeron = false;
|
||||
clearInterval(timerinterval);
|
||||
} else if (touchdata.y > 24 && touchdata.y < (100-10)) {
|
||||
Bangle.buzz(40);
|
||||
islocked = !islocked;
|
||||
} else if (!islocked && touchdata.y > (100+10) && touchdata.x > 88 + 10) {
|
||||
Bangle.buzz(40);
|
||||
current_from = newtimer_right_from;
|
||||
current_to = newtimer_right_to;
|
||||
current_mid = (current_from + current_to) / 2;
|
||||
current_value = 0;
|
||||
if (timerinterval) clearInterval(timerinterval);
|
||||
timerinterval = setInterval(countDown, 1000);
|
||||
istimeron = true;
|
||||
} else if (!islocked && touchdata.y > (100+10) && touchdata.x < 88 - 10) {
|
||||
Bangle.buzz(40);
|
||||
current_from = newtimer_left_from;
|
||||
current_to = newtimer_left_to;
|
||||
current_mid = (current_from + current_to) / 2;
|
||||
current_value = 0;
|
||||
if (timerinterval) clearInterval(timerinterval);
|
||||
timerinterval = setInterval(countDown, 1000);
|
||||
istimeron = true;
|
||||
}
|
||||
showInstructions = false;
|
||||
draw();
|
||||
});
|
||||
|
||||
Bangle.on('swipe',(swiperight, swipedown)=>{
|
||||
console.log(swiperight);
|
||||
console.log(swipedown);
|
||||
|
||||
if (swiperight == -1) {
|
||||
if (newtimer_left_from >= 60) {
|
||||
newtimer_left_from += 60;
|
||||
newtimer_left_to += 60;
|
||||
} else { // special case for 0:30 to 1:00
|
||||
newtimer_left_from = 60;
|
||||
newtimer_left_to = 120;
|
||||
}
|
||||
newtimer_right_from += 60;
|
||||
newtimer_right_to += 60;
|
||||
draw();
|
||||
} else if (swiperight == 1) {
|
||||
if (newtimer_left_from > 60) {
|
||||
newtimer_left_from -= 60;
|
||||
newtimer_left_to -= 60;
|
||||
} else { // special case for 0:30 to 1:00
|
||||
newtimer_left_from = 30;
|
||||
newtimer_left_to = 60;
|
||||
}
|
||||
|
||||
if (newtimer_right_from > 120) {
|
||||
newtimer_right_from -= 60;
|
||||
newtimer_right_to -= 60;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
var drawTimeout;
|
||||
var showInstructions = true;
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
if (current_value >= current_to) { g.setBgColor("#F00"); }
|
||||
else if (current_value >= current_mid) { g.setBgColor("#FF0"); }
|
||||
else if (current_value >= current_from) { g.setBgColor("#8F8"); }
|
||||
g.clearRect(0,24,176,176);
|
||||
|
||||
g.reset().setFontAlign(0, 0).setColor(istimeron ? "#000" : "#444");
|
||||
g.setFont("Michroma36").drawString(timeToString(current_value), 88, 62);
|
||||
|
||||
g.reset().setFontAlign(0, 0);
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(timeToString(current_from), 44, 62+26);
|
||||
g.drawString(timeToString(current_mid), 88, 62+26);
|
||||
g.drawString(timeToString(current_to), 132, 62+26);
|
||||
|
||||
if (current_value >= current_from) { g.drawRect(44-1,62+26+9,44+1,62+26+9+1); }
|
||||
if (current_value >= current_mid) { g.drawRect(88-1,62+26+9,88+1,62+26+9+1); }
|
||||
if (current_value >= current_to) { g.drawRect(132-1,62+26+9,132+1,62+26+9+1); }
|
||||
|
||||
if (showInstructions) {
|
||||
g.setFont("6x8").drawString("Tapping timer locks buttons", 88, 100+5);
|
||||
g.setFont("6x8").drawString("<= Swipe to change time =>", 88, 168);
|
||||
}
|
||||
|
||||
g.setColor(islocked ? "#444" : "#000");
|
||||
g.setFont("Michroma16");
|
||||
g.drawString(timeToString(newtimer_left_from), 44, 138-9);
|
||||
g.drawString(timeToString(newtimer_left_to), 44, 138+9);
|
||||
g.drawString(timeToString(newtimer_right_from), 132, 138-9);
|
||||
g.drawString(timeToString(newtimer_right_to), 132, 138+9);
|
||||
|
||||
g.drawRect(0+8,138-24, 88-9+1, 138+22+1);
|
||||
g.drawRect(0+8,138-24, 88-9, 138+22);
|
||||
g.drawRect(88+8,138-24, 176-10+1, 138+22+1);
|
||||
g.drawRect(88+8,138-24, 176-10, 138+22);
|
||||
}
|
||||
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
|
@ -9,3 +9,4 @@
|
|||
0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021
|
||||
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
|
||||
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||
0.12: Actual pixels as of 29th Nov 2021
|
||||
|
|
|
@ -21,8 +21,8 @@ function showAlarm(alarm) {
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showPrompt(msg,{
|
||||
title:alarm.timer ? "TIMER!" : "ALARM!",
|
||||
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
|
||||
buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
}).then(function(sleep) {
|
||||
buzzCount = 0;
|
||||
if (sleep) {
|
||||
|
|
|
@ -33,16 +33,16 @@ function getCurrentHr() {
|
|||
function showMainMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarm/Timer' },
|
||||
'< Back' : ()=>{load();},
|
||||
'New Alarm': ()=>editAlarm(-1),
|
||||
'New Timer': ()=>editTimer(-1)
|
||||
/*LANG*/'< Back' : ()=>{load();},
|
||||
/*LANG*/'New Alarm': ()=>editAlarm(-1),
|
||||
/*LANG*/'New Timer': ()=>editTimer(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
if (alarm.timer) {
|
||||
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
|
||||
txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
|
||||
} else {
|
||||
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += /*LANG*/" (repeat)";
|
||||
}
|
||||
menu[txt] = function() {
|
||||
if (alarm.timer) editTimer(idx);
|
||||
|
@ -70,27 +70,27 @@ function editAlarm(alarmIndex) {
|
|||
as = a.as;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarm' },
|
||||
'< Back' : showMainMenu,
|
||||
'Hours': {
|
||||
'': { 'title': /*LANG*/'Alarm' },
|
||||
/*LANG*/'< Back' : showMainMenu,
|
||||
/*LANG*/'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
/*LANG*/'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
/*LANG*/'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>en=v
|
||||
},
|
||||
'Repeat': {
|
||||
/*LANG*/'Repeat': {
|
||||
value: en,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>repeat=v
|
||||
},
|
||||
'Auto snooze': {
|
||||
/*LANG*/'Auto snooze': {
|
||||
value: as,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>as=v
|
||||
|
@ -108,14 +108,14 @@ function editAlarm(alarmIndex) {
|
|||
last : day, rp : repeat, as: as
|
||||
};
|
||||
}
|
||||
menu["> Save"] = function() {
|
||||
menu[/*LANG*/"> Save"] = function() {
|
||||
if (newAlarm) alarms.push(getAlarm());
|
||||
else alarms[alarmIndex] = getAlarm();
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["> Delete"] = function() {
|
||||
menu[/*LANG*/"> Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
|
@ -136,18 +136,18 @@ function editTimer(alarmIndex) {
|
|||
en = a.on;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Timer' },
|
||||
'Hours': {
|
||||
'': { 'title': /*LANG*/'Timer' },
|
||||
/*LANG*/'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
/*LANG*/'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
/*LANG*/'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
|
||||
onchange: v=>en=v
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24);
|
||||
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
if (!require('Storage').read("alarm.js")) {
|
||||
console.log("No alarm app!");
|
||||
console.log(/*LANG*/"No alarm app!");
|
||||
require('Storage').write('alarm.json',"[]");
|
||||
} else {
|
||||
var t = 3600000*(active[0].hr-hr);
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
Fix music control
|
||||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Android icon now goes to settings page with 'find phone'
|
||||
0.05: Fix handling of message actions
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
// 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" });
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
0.04: Fix tapping at very bottom of list, exit on inactivity
|
||||
0.03: Add "Calculating" placeholder, update JSON save format
|
||||
0.02: Fix JSON save format
|
||||
0.01: First release
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Authentiwatch - 2FA Authenticator
|
||||
|
||||
* GitHub: https://github.com/andrewgoz/Authentiwatch <-- Report bugs here
|
||||
* Bleeding edge AppLoader: https://andrewgoz.github.io/Authentiwatch/
|
||||
|
||||
## Supports
|
||||
|
||||
* Google Authenticator compatible 2-factor authentication
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN"))
|
||||
require("heatshrink").decompress(atob("mEwxH+AH4AD64ADFlgAFF04INFz4LUF0QwjEBwv/FzwwgF/4v/F6nMAAWi1AFD5nOeEHPEweoFooAB5/X5wvdFwotG5nN6/WAoQuaEoguHSYPQLwIIDF8uo5ouB6AJEFzuiFwup5/WFwI6GL0esXYKMBHYy9j1WqfBSOhBIYKJF8gAKF/4v6cZAvhGDAuWSDAvXMCwuYF+AwUFzX+0XGGAgxKFrYuBAAQxEeg4tcF4oABBQnGAAgv/F6b5KXsIvIGAqNnF/69fX8ZeSF7btNR8IuOF75ePL8ouOd74NKF8IANF94wEF1QAXA"))
|
||||
|
|
|
@ -6,8 +6,15 @@ const algos = {
|
|||
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
||||
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
||||
};
|
||||
const calculating = "Calculating";
|
||||
const notokens = "No tokens";
|
||||
const notsupported = "Not supported";
|
||||
|
||||
var tokens = require("Storage").readJSON("authentiwatch.json", true) || [];
|
||||
// sample settings:
|
||||
// {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}}
|
||||
var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}};
|
||||
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
|
||||
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
|
||||
|
||||
// QR Code Text
|
||||
//
|
||||
|
@ -66,9 +73,8 @@ function do_hmac(key, message, algo) {
|
|||
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
||||
return v.getUint32(0) & 0x7FFFFFFF;
|
||||
}
|
||||
function hotp(token) {
|
||||
function hotp(d, token, dohmac) {
|
||||
var tick;
|
||||
var d = new Date();
|
||||
if (token.period > 0) {
|
||||
// RFC6238 - timed
|
||||
var seconds = Math.floor(d.getTime() / 1000);
|
||||
|
@ -81,15 +87,17 @@ function hotp(token) {
|
|||
var v = new DataView(msg.buffer);
|
||||
v.setUint32(0, tick >> 16 >> 16);
|
||||
v.setUint32(4, tick & 0xFFFFFFFF);
|
||||
var ret = "";
|
||||
try {
|
||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||
ret = "" + hash % Math.pow(10, token.digits);
|
||||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
var ret = calculating;
|
||||
if (dohmac) {
|
||||
try {
|
||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||
ret = "" + hash % Math.pow(10, token.digits);
|
||||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
}
|
||||
} catch(err) {
|
||||
ret = notsupported;
|
||||
}
|
||||
} catch(err) {
|
||||
ret = "Not supported";
|
||||
}
|
||||
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
||||
}
|
||||
|
@ -109,7 +117,7 @@ function drawToken(id, r) {
|
|||
var y1 = r.y;
|
||||
var x2 = r.x + r.w - 1;
|
||||
var y2 = r.y + r.h - 1;
|
||||
var adj;
|
||||
var adj, sz;
|
||||
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
||||
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
||||
if (id == state.curtoken) {
|
||||
|
@ -129,7 +137,7 @@ function drawToken(id, r) {
|
|||
adj = (y1 + y2) / 2;
|
||||
}
|
||||
g.clearRect(x1, y1, x2, y2);
|
||||
g.drawString(tokens[id].label, (x1 + x2) / 2, adj, false);
|
||||
g.drawString(tokens[id].label.substr(0, 10), (x1 + x2) / 2, adj, false);
|
||||
if (id == state.curtoken) {
|
||||
if (tokens[id].period > 0) {
|
||||
// timed - draw progress bar
|
||||
|
@ -140,11 +148,14 @@ function drawToken(id, r) {
|
|||
// counter - draw triangle as swipe hint
|
||||
let yc = (y1 + y2) / 2;
|
||||
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
|
||||
adj = 5;
|
||||
adj = 10;
|
||||
}
|
||||
// digits just below label
|
||||
g.setFont("Vector", (state.otp.length > 8) ? 26 : 30);
|
||||
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
|
||||
sz = 30;
|
||||
do {
|
||||
g.setFont("Vector", sz--);
|
||||
} while (g.stringWidth(state.otp) > (r.w - adj));
|
||||
g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + 16, false);
|
||||
}
|
||||
// shaded lines top and bottom
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
|
@ -154,9 +165,14 @@ function drawToken(id, r) {
|
|||
}
|
||||
|
||||
function draw() {
|
||||
var timerfn = exitApp;
|
||||
var timerdly = 10000;
|
||||
var d = new Date();
|
||||
if (state.curtoken != -1) {
|
||||
var t = tokens[state.curtoken];
|
||||
if (state.otp == calculating) {
|
||||
state.otp = hotp(d, t, true).hotp;
|
||||
}
|
||||
if (d.getTime() > state.nextTime) {
|
||||
if (state.hide == 0) {
|
||||
// auto-hide the current token
|
||||
|
@ -167,7 +183,7 @@ function draw() {
|
|||
state.nextTime = 0;
|
||||
} else {
|
||||
// time to generate a new token
|
||||
var r = hotp(t);
|
||||
var r = hotp(d, t, state.otp != "");
|
||||
state.nextTime = r.next;
|
||||
state.otp = r.hotp;
|
||||
if (t.period <= 0) {
|
||||
|
@ -191,11 +207,13 @@ function draw() {
|
|||
y += tokenentryheight;
|
||||
}
|
||||
if (drewcur) {
|
||||
// the current token has been drawn - draw it again in 1sec
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
// the current token has been drawn - schedule a redraw
|
||||
if (tokens[state.curtoken].period > 0) {
|
||||
timerdly = (state.otp == calculating) ? 1 : 1000; // timed
|
||||
} else {
|
||||
timerdly = state.nexttime - d.getTime(); // counter
|
||||
}
|
||||
state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime());
|
||||
timerfn = draw;
|
||||
if (tokens[state.curtoken].period <= 0) {
|
||||
state.hide = 0;
|
||||
}
|
||||
|
@ -210,14 +228,18 @@ function draw() {
|
|||
} else {
|
||||
g.setFont("Vector", 30);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
}
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
}
|
||||
state.drawtimer = setTimeout(timerfn, timerdly);
|
||||
}
|
||||
|
||||
function onTouch(zone, e) {
|
||||
if (e) {
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
|
||||
if (id == state.curtoken || tokens.length == 0) {
|
||||
if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) {
|
||||
id = -1;
|
||||
}
|
||||
if (state.curtoken != id) {
|
||||
|
@ -231,37 +253,34 @@ function onTouch(zone, e) {
|
|||
if (y > Bangle.appRect.h) {
|
||||
state.listy += (y - Bangle.appRect.h);
|
||||
}
|
||||
state.otp = "";
|
||||
}
|
||||
state.nextTime = 0;
|
||||
state.curtoken = id;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
|
||||
if (e.dx == 0 && e.dy == 0) return;
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
|
||||
newy = Math.max(0, newy);
|
||||
if (newy != state.listy) {
|
||||
state.listy = newy;
|
||||
draw();
|
||||
}
|
||||
state.listy = Math.max(0, newy);
|
||||
draw();
|
||||
}
|
||||
|
||||
function onSwipe(e) {
|
||||
if (e == 1) {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
||||
tokens[state.curtoken].period--;
|
||||
require("Storage").writeJSON("authentiwatch.json", tokens);
|
||||
let newsettings={tokens:tokens,misc:settings.misc};
|
||||
require("Storage").writeJSON("authentiwatch.json", newsettings);
|
||||
state.nextTime = 0;
|
||||
state.otp = "";
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function bangle1Btn(e) {
|
||||
|
@ -281,16 +300,22 @@ function bangle1Btn(e) {
|
|||
state.curtoken = -1;
|
||||
state.nextTime = 0;
|
||||
onTouch(0, fakee);
|
||||
} else {
|
||||
draw(); // resets idle timer
|
||||
}
|
||||
}
|
||||
|
||||
function exitApp() {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
|
||||
Bangle.on('touch', onTouch);
|
||||
Bangle.on('drag' , onDrag );
|
||||
Bangle.on('swipe', onSwipe);
|
||||
if (typeof BTN2 == 'number') {
|
||||
setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){exitApp(); }, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
||||
|
|
Before Width: | Height: | Size: 964 B After Width: | Height: | Size: 1.6 KiB |
|
@ -35,8 +35,9 @@ const otpAuthUrl = 'otpauth://';
|
|||
|
||||
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
||||
|
||||
/* Array of TOTP tokens */
|
||||
var tokens=[];
|
||||
/* Settings */
|
||||
var settings = {tokens:[], misc:{}};
|
||||
var tokens = settings.tokens;
|
||||
|
||||
/* Remove any non-base-32 characters from the given string and collapses
|
||||
* whitespace to a single space. Optionally removes all whitespace from
|
||||
|
@ -261,6 +262,7 @@ qrcode.callback = res => {
|
|||
scanning = false;
|
||||
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
||||
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
||||
t['label'] = t['label'].substr(0, 10);
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
||||
t['period'] = '30';
|
||||
|
@ -319,21 +321,21 @@ function doScan() {
|
|||
*/
|
||||
function loadTokens() {
|
||||
Util.showModal('Loading...');
|
||||
Puck.eval(`require('Storage').read(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||
Puck.eval(`require('Storage').readJSON(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||
Util.hideModal();
|
||||
try {
|
||||
tokens = JSON.parse(data);
|
||||
updateTokens();
|
||||
} catch {
|
||||
tokens = [];
|
||||
}
|
||||
if (data.data ) settings.tokens = data.data ; /* v0.02 settings */
|
||||
if (data.tokens) settings.tokens = data.tokens; /* v0.03+ settings */
|
||||
if (data.misc ) settings.misc = data.misc ; /* v0.03+ settings */
|
||||
tokens = settings.tokens;
|
||||
updateTokens();
|
||||
});
|
||||
}
|
||||
/* Save settings as a JSON file on the watch.
|
||||
*/
|
||||
function saveTokens() {
|
||||
Util.showModal('Saving...');
|
||||
Puck.write(`\x10require('Storage').write(${JSON.stringify('authentiwatch.json')},${JSON.stringify(tokens)})\n`,()=>{
|
||||
let newsettings={tokens:tokens,misc:settings.misc};
|
||||
Puck.write(`\x10require('Storage').writeJSON(${JSON.stringify('authentiwatch.json')},${JSON.stringify(newsettings)})\n`,()=>{
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11)
|
|
@ -0,0 +1,22 @@
|
|||
# Awair Monitor
|
||||
|
||||
Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.
|
||||
|
||||
* What you need:
|
||||
* A BangleJS 2
|
||||
* An Awair device [with local API enabled](https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature)
|
||||
* The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrive the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
|
||||
* How to get started
|
||||
* Open awair_to_bangle.html with a text/code editor and input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
* Launch the Awair Monitor app on your BangleJS
|
||||
* Open awair_to_bangle.html on Chrome and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
|
||||
* Once connected to the watch with the app running, the watch app is updated once per second
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
||||
|
||||
Contributions are welcome, send me your Pull Requests!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP/AD38g4FD8EAAoeAgE/AoUD/EfAgP+AYMPDgQPBw4FB/F///DAoPwAQPjAQPBAQPxDgJVCAoP4gYaCCwIcBAoM/8P8h0HjEP8f4h0Gp0H4/44lj5+H4/54lzj/jx/5/lyDgIFDh/xAoQRBAoXsuY8Bx4jCAoeEkYFB447CAoRxBOAPxM4RmC8IFD4ZZD/8H/DHDh/+AoaSBUAIABCoYATVwS2Ct4FE84REXQQLCk4RJAo0XGxY="))
|
|
@ -0,0 +1,98 @@
|
|||
Graphics.prototype.setFontMichroma36 = function() {
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAAAAAAAAAAAAAAAAAAAGAAAAA+AAAAD+AAAAP+AAAA/8AAAD/wAAAf/AAAB/4AAAH/gAAAf+AAAB/4AAAH/gAAAf+AAAAfwAAAAfAAAAAcAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AP///8APwAD+APAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAeAPAAAeAPwAD+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAEAAAAAOAAAAAfAAAAA+AAAAB8AAAAD8AAAAH4AAAAPwAAAAPgAAAAfAAAAAf///+Af///+Af///+Af///+AAAAAAAAAAAAAAAAAAAAAAAAAA/Af+AD/A/+AH/B/+AP/D/+APwD4eAPADweAfADweAeADweAeADweAeADweAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAPgeAeAPAeAeAPAeAeAPAeAeAPAeAfAPAeAPw/AeAP/+AeAH/+AeAD/8AeAB/wAOAAAAAAAAAAAAAAAAAAAAAAAAAB8APgAD8AP4AH8AP8AP8AP8APgAB+AfAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAeAfAeAeAPx/h+AP///+AH///8AD///4AB/h/gAAAAAAAAAAAAAAAAAAAAAAeAAAAA/AAAAA/AAAAB/AAAAD/AAAAH/AAAAPvAAAAPPAAAAfPAAAA+PAAAB8PAAAD4PAAADwPAAAHwPAAAPgPAAAfAPAAA+APAAA8APAAB8APAAD4APAAHwAPAAPgAPAAPAAPAAfAAPAAf///+Af///+Af///+Af///+AAAAPAAAAAPAAAAAPAAAAAPAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAf/8PgAf/8P4Af/8P8Af/8P8AeB4A+AeB4AeAeDwAeAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAfAeDwAeAeD4A+AeD+D+AeB//8AeB//4AeA//4AAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AH///8AP4fB+APAeAeAfA8AeAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAfA8APAPA+AeAPgeAeAP8fh+AH8f/8AD8P/8AA8H/4AAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAACAeAAAGAeAAAOAeAAAeAeAAA+AeAAD+AeAAH8AeAAP4AeAAfwAeAA/gAeAB/AAeAD+AAeAP4AAeAfwAAeA/gAAeB/AAAeD+AAAeH8AAAefwAAAe/gAAAf/AAAAf+AAAAf8AAAAf4AAAAfgAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAMAAB+B/wAD/j/4AH/3/8AP///+AP//A+AfB+AeAeA+AeAeA+APAeA+APAeA+APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA+APAeA+APAeA+APAeA+AOAeA+AeAPh/A+AP///+AP/3/8AH/3/8AB/D/wAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAD/4HAAH/8HwAP/+H4AP5/H8AfAfA8AeAPAeAeAPAeAeAPAeAeAHgfAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHAPAeAPAOAeAPAeAPAPAeAPwfB+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAAAAAAAB8DwAAB8HwAAB8HwAAB8DwAAAAAAAAAAAAA"), 46, atob("CBIkESMjJCMjIyMjCA=="), 36+(1<<8)+(1<<16));
|
||||
};
|
||||
|
||||
var drawTimeout;
|
||||
|
||||
function queueNextDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 1000 - (Date.now() % 1000));
|
||||
}
|
||||
|
||||
var locale = require("locale");
|
||||
|
||||
var bt_current_co2 = 0;
|
||||
var bt_current_voc = 0;
|
||||
var bt_current_pm25 = 0;
|
||||
var bt_current_humi = 0;
|
||||
var bt_current_temp = 0;
|
||||
var bt_last_update = 0;
|
||||
|
||||
var last_update = 0;
|
||||
var bt_co2_history = new Array(10).fill(0);
|
||||
var bt_voc_history = new Array(10).fill(0);
|
||||
var bt_pm25_history = new Array(10).fill(0);
|
||||
var bt_humi_history = new Array(10).fill(0);
|
||||
var bt_temp_history = new Array(10).fill(0);
|
||||
|
||||
var internal_last_update = -1;
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
|
||||
var date = new Date();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 56);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.drawString(locale.date(new Date(),1), g.getWidth()/2, 80);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.drawString("CO2", 20, 100);
|
||||
g.drawString("VOC", 55, 100);
|
||||
g.drawString("PM25", 90, 100);
|
||||
g.drawString("Humi", 125, 100);
|
||||
g.drawString("Temp", 160, 100);
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(""+bt_current_co2, 18, 110);
|
||||
g.drawString(""+bt_current_voc, 53, 110);
|
||||
g.drawString(""+bt_current_pm25, 88, 110);
|
||||
g.drawString(""+bt_current_humi, 123, 110);
|
||||
g.drawString(""+bt_current_temp, 158, 110);
|
||||
|
||||
if (last_update != bt_last_update) {
|
||||
last_update = bt_last_update;
|
||||
internal_last_update = last_update;
|
||||
if (last_update % 10 == 0) {
|
||||
bt_co2_history.shift(); bt_co2_history.push(bt_current_co2);
|
||||
bt_voc_history.shift(); bt_voc_history.push(bt_current_voc);
|
||||
bt_pm25_history.shift(); bt_pm25_history.push(bt_current_pm25);
|
||||
bt_humi_history.shift(); bt_humi_history.push(bt_current_humi);
|
||||
bt_temp_history.shift(); bt_temp_history.push(bt_current_temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (internal_last_update == -1) {
|
||||
g.drawString("Waiting for connection", 88, 164);
|
||||
} else if (internal_last_update > last_update + 5) {
|
||||
g.drawString("Trying to reconnect since " + (internal_last_update - last_update), 88, 164);
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
// max height = 32
|
||||
g.drawLine(10+i*2, 150-(Math.min(Math.max(bt_co2_history[i],400), 1200)-400)/25, 10+i*2, 150);
|
||||
g.drawLine(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
|
||||
g.drawLine(80+i*2, 150-(Math.min(Math.max(bt_pm25_history[i],0), 32)-0)/1, 80+i*2, 150);
|
||||
g.drawLine(115+i*2, 150-(Math.min(Math.max(bt_humi_history[i],20), 60)-20)/1.25, 115+i*2, 150);
|
||||
g.drawLine(150+i*2, 150-(Math.min(Math.max(bt_temp_history[i],19), 27)-19)*4, 150+i*2, 150);
|
||||
|
||||
// target humidity level
|
||||
g.setColor("#00F").drawLine(115, 150-(40-20)/1.25, 115+18, 150-(40-20)/1.25);
|
||||
g.reset();
|
||||
}
|
||||
|
||||
if (internal_last_update != -1) { internal_last_update++; }
|
||||
queueNextDraw();
|
||||
}
|
||||
|
||||
// init
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 146 KiB |
|
@ -0,0 +1,195 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://puck-js.com/puck.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// Don't forget to enable the Local API on your Awair before using this
|
||||
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
|
||||
const awair_ip_1 = "192.168.2.2"; // <- INPUT YOUR AWAIR IP ADDRESS HERE
|
||||
const awair_name_1 = "Awair";
|
||||
|
||||
var bt_connection;
|
||||
var is_connected = false;
|
||||
var reconnect_counter = 5;
|
||||
var reconnect_attempt_counter = 1;
|
||||
|
||||
window.onload = function() {
|
||||
var chart_co2;
|
||||
var chart_voc;
|
||||
var chart_pm;
|
||||
var chart_temperature;
|
||||
var chart_humidity;
|
||||
var dataPoints_1 = [];
|
||||
var posx = 0;
|
||||
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||
});
|
||||
|
||||
posx++;
|
||||
|
||||
chart_co2 = new CanvasJS.Chart("chartContainer_co2",{
|
||||
title:{ text:"CO2", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.co2 }]
|
||||
});
|
||||
chart_voc = new CanvasJS.Chart("chartContainer_voc",{
|
||||
title:{ text:"VOC", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.voc }]
|
||||
});
|
||||
chart_pm = new CanvasJS.Chart("chartContainer_pm",{
|
||||
title:{ text:"PM", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.pm25 }]
|
||||
});
|
||||
chart_humidity = new CanvasJS.Chart("chartContainer_humidity",{
|
||||
title:{ text:"Humidity", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.humid }]
|
||||
});
|
||||
chart_temperature = new CanvasJS.Chart("chartContainer_temperature",{
|
||||
title:{ text:"Temperature", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.temp }]
|
||||
});
|
||||
|
||||
chart_co2.set("backgroundColor", "#1A202C");
|
||||
chart_voc.set("backgroundColor", "#1A202C");
|
||||
chart_pm.set("backgroundColor", "#1A202C");
|
||||
chart_humidity.set("backgroundColor", "#1A202C");
|
||||
chart_temperature.set("backgroundColor", "#1A202C");
|
||||
|
||||
updateChart();
|
||||
});
|
||||
|
||||
function updateChart() {
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||
});
|
||||
|
||||
posx++;
|
||||
chart_co2.render();
|
||||
chart_voc.render();
|
||||
chart_pm.render();
|
||||
chart_temperature.render();
|
||||
chart_humidity.render();
|
||||
|
||||
chart_co2.title.set("text", "CO2 level (ppm)");
|
||||
chart_voc.title.set("text", "VOC level (ppb)");
|
||||
chart_pm.title.set("text", "PM2.5 level (ug/m³)");
|
||||
chart_humidity.title.set("text", "Humidity level (%)");
|
||||
chart_temperature.title.set("text", "Temperature level (°C)");
|
||||
|
||||
let current_co2 = dataPoints_1['co2'][dataPoints_1['co2'].length-1].y;
|
||||
let current_voc = dataPoints_1['voc'][dataPoints_1['voc'].length-1].y;
|
||||
let current_pm25 = dataPoints_1['pm25'][dataPoints_1['pm25'].length-1].y;
|
||||
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
||||
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
||||
let last_update = dataPoints_1['temp'].length-1;
|
||||
if (is_connected && bt_connection.isOpen) {
|
||||
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_last_update='+last_update+';\n');
|
||||
|
||||
console.log("Sent data through Bluetooth");
|
||||
} else if (is_connected && !bt_connection.isOpen) {
|
||||
console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
|
||||
reconnect_counter--;
|
||||
|
||||
if (reconnect_counter <= 0) {
|
||||
reconnect_counter = 10 * reconnect_attempt_counter;
|
||||
reconnect_attempt_counter++;
|
||||
|
||||
console.log("Trying to reconnect");
|
||||
bt_connection.reconnect(function(c) {
|
||||
console.log("Reconnect callback");
|
||||
if (!c) {
|
||||
console.log("Couldn't reconnect");
|
||||
return;
|
||||
}
|
||||
bt_connection = c;
|
||||
is_connected = true;
|
||||
reconnect_attempt_counter = 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setTimeout(function(){updateChart()}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function connectBT() {
|
||||
console.log("Connect BT");
|
||||
Puck.connect(function(c) {
|
||||
console.log("Connect callback");
|
||||
if (!c) {
|
||||
console.log("Couldn't connect");
|
||||
return;
|
||||
}
|
||||
bt_connection = c;
|
||||
is_connected = true;
|
||||
reconnect_attempt_counter = 1;
|
||||
});
|
||||
}
|
||||
|
||||
function disconnectBT() {
|
||||
if (is_connected && bt_connection) {
|
||||
bt_connection.close();
|
||||
is_connected = false;
|
||||
console.log("Closed Bluetooth connection");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript" src="https://canvasjs.com/assets/script/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
||||
</head>
|
||||
<body style="background-color:#1A202C;">
|
||||
|
||||
<p style="color: #F7FAFC">
|
||||
<b>How to use</b>
|
||||
<br/><br/>
|
||||
Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
<br/><br/>
|
||||
Step 2: Modify this HTML file to input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
<br/><br/>
|
||||
Step 3: Launch the Awair Monitor app on your BangleJS
|
||||
<br/><br/>
|
||||
Step 4: Click "Connect BangleJS"
|
||||
<br/><br/>
|
||||
Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<button onclick="connectBT();">Connect BangleJS</button>
|
||||
<button onclick="disconnectBT();">Disconnect BangleJS</button>
|
||||
</center>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_voc" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_pm" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_humidity" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_temperature" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -40,3 +40,6 @@
|
|||
0.35: Add Bangle.appRect polyfill
|
||||
Don't set beep vibration up on Bangle.js 2 (built in)
|
||||
0.36: Add comments to .boot0 to make debugging a bit easier
|
||||
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
||||
0.38: Option to log to file if settings.log==2
|
||||
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||
|
|
|
@ -23,8 +23,14 @@ if (s.ble!==false) {
|
|||
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
|
||||
}
|
||||
}
|
||||
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
||||
if (s.log==2) { // logging to file
|
||||
boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
`;
|
||||
} if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
|
||||
LoopbackA.setConsole(true);\n`;
|
||||
else if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
||||
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
||||
/* If not programmable add our own handler for Bluetooth data
|
||||
to allow Gadgetbridge commands to be received*/
|
||||
|
@ -41,7 +47,10 @@ Bluetooth.on('line',function(l) {
|
|||
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
|
||||
});\n`;
|
||||
} else {
|
||||
if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
||||
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
|
||||
if (!NRF.getSecurityStatus().connected) LoopbackA.setConsole();\n`;
|
||||
else if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
||||
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
|
||||
}
|
||||
// we just reset, so BLE should be on.
|
||||
|
@ -78,14 +87,8 @@ boot += `E.on('errorFlag', function(errorFlags) {
|
|||
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`;
|
||||
// Apply any settings-specific stuff
|
||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.quiet && s.qmOptions) boot+=`Bangle.setOptions(${E.toJS(s.qmOptions)});\n`;
|
||||
if (s.quiet && s.qmBrightness) {
|
||||
if (s.qmBrightness!=1) boot+=`Bangle.setLCDBrightness(${s.qmBrightness});\n`;
|
||||
} else {
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
}
|
||||
if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
// Pre-2v10 firmwares without a theme/setUI
|
||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Basic calendar
|
||||
0.02: Make Bangle 2 compatible
|
||||
0.03: Add setting to start week on Sunday
|
||||
|
|
|
@ -6,3 +6,8 @@ Basic calendar
|
|||
|
||||
- Use `BTN4` (left screen tap) to go to the previous month
|
||||
- Use `BTN5` (right screen tap) to go to the next month
|
||||
|
||||
## Settings
|
||||
|
||||
- Starts on Sunday: whether the calendar should start on Sunday (default is Monday).
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@ const gray2 = "#888888";
|
|||
const gray3 = "#bbbbbb";
|
||||
const red = "#d41706";
|
||||
|
||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||
if (settings.startOnSun === undefined)
|
||||
settings.startOnSun = false;
|
||||
|
||||
function drawCalendar(date) {
|
||||
g.setBgColor(color4);
|
||||
g.clearRect(0, 0, maxX, maxY);
|
||||
|
@ -61,13 +65,18 @@ function drawCalendar(date) {
|
|||
);
|
||||
|
||||
g.setFont("6x8", fontSize);
|
||||
const dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||
let dowLbls;
|
||||
if (settings.startOnSun) {
|
||||
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
||||
} else {
|
||||
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||
}
|
||||
dowLbls.forEach((lbl, i) => {
|
||||
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
|
||||
});
|
||||
|
||||
date.setDate(1);
|
||||
const dow = date.getDay();
|
||||
const dow = date.getDay() + (settings.startOnSun ? 1 : 0);
|
||||
const dowNorm = dow === 0 ? 7 : dow;
|
||||
|
||||
const monthMaxDayMap = {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
(function(back) {
|
||||
var FILE = "calendar.json";
|
||||
var settings = require('Storage').readJSON(FILE, true) || {};
|
||||
if (settings.startOnSun === undefined)
|
||||
settings.startOnSun = true;
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "Calendar" },
|
||||
"< Back" : () => back(),
|
||||
'Start on Sunday': {
|
||||
value: settings.startOnSun,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => {
|
||||
settings.startOnSun = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
0.01: New widget and app!
|
||||
0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme)
|
||||
0.03: Display only minutes:seconds when less than 1 hour left
|
||||
0.03: Display only minutes:seconds when less than 1 hour left
|
||||
0.04: Change to 7 segment font, move to top widget bar
|
||||
Better auto-update behaviour, less RAM used
|
||||
|
|
|
@ -5,14 +5,13 @@ The advantage is, that you can still see your normal watchface and other widgets
|
|||
The widget is always active, but only shown when the timer is on.
|
||||
Hours, minutes, seconds and timer status can be set with an app.
|
||||
|
||||
When there is less than one seconds left on the timer it buzzes.
|
||||
When there is less than one second left on the timer it buzzes.
|
||||
|
||||
The widget has been tested on Bangle 1 and Bangle 2
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Features
|
||||
|
@ -28,15 +27,15 @@ There are no settings section in the settings app, timer can be set using an app
|
|||
* Hours: Set the hours for the timer
|
||||
* Minutes: Set the minutes for the timer
|
||||
* Seconds: Set the seconds for the timer
|
||||
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
|
||||
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
|
||||
|
||||
|
||||
## Releases
|
||||
|
||||
* Offifical app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
|
||||
* Official app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
|
||||
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/chronowid (https://purple-tentacle.github.io/BangleApps/index.html#)
|
||||
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/chronowid
|
||||
|
||||
## Requests
|
||||
|
||||
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
|
||||
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
|
||||
|
|
|
@ -3,7 +3,6 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
|
||||
const storage = require('Storage');
|
||||
const boolFormat = v => v ? "On" : "Off";
|
||||
let settingsChronowid;
|
||||
|
||||
function updateSettings() {
|
||||
|
@ -12,6 +11,7 @@ function updateSettings() {
|
|||
now.getHours() + settingsChronowid.hours, now.getMinutes() + settingsChronowid.minutes, now.getSeconds() + settingsChronowid.seconds);
|
||||
settingsChronowid.goal = goal.getTime();
|
||||
storage.writeJSON('chronowid.json', settingsChronowid);
|
||||
if (WIDGETS["chronowid"]) WIDGETS["chronowid"].reload();
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
|
@ -44,6 +44,7 @@ function showMenu() {
|
|||
timerMenu.started.value = settingsChronowid.started;
|
||||
}
|
||||
},
|
||||
'< Back' : ()=>{load();},
|
||||
'Reset values': function() {
|
||||
settingsChronowid.hours = 0;
|
||||
settingsChronowid.minutes = 0;
|
||||
|
@ -84,15 +85,15 @@ function showMenu() {
|
|||
},
|
||||
'Timer on': {
|
||||
value: settingsChronowid.started,
|
||||
format: boolFormat,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settingsChronowid.started = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
timerMenu['-Exit-'] = ()=>{load();};
|
||||
|
||||
return E.showMenu(timerMenu);
|
||||
}
|
||||
|
||||
showMenu();
|
||||
showMenu();
|
||||
|
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -1,93 +1,79 @@
|
|||
(() => {
|
||||
const storage = require('Storage');
|
||||
settingsChronowid = storage.readJSON("chronowid.json",1)||{}; //read settingsChronowid from file
|
||||
var height = 23;
|
||||
var width = 58;
|
||||
var settingsChronowid;
|
||||
var interval = 0; //used for the 1 second interval timer
|
||||
var now = new Date();
|
||||
var diff;
|
||||
|
||||
var time = 0;
|
||||
var diff = settingsChronowid.goal - now;
|
||||
|
||||
//Convert ms to time
|
||||
function getTime(t) {
|
||||
var milliseconds = parseInt((t % 1000) / 100),
|
||||
seconds = Math.floor((t / 1000) % 60),
|
||||
minutes = Math.floor((t / (1000 * 60)) % 60),
|
||||
hours = Math.floor((t / (1000 * 60 * 60)) % 24);
|
||||
|
||||
hours = (hours < 10) ? "0" + hours : hours;
|
||||
minutes = (minutes < 10) ? "0" + minutes : minutes;
|
||||
seconds = (seconds < 10) ? "0" + seconds : seconds;
|
||||
|
||||
return hours + ":" + minutes + ":" + seconds;
|
||||
return hours.toString().padStart(2,0) + ":" + minutes.toString().padStart(2,0) + ":" + seconds.toString().padStart(2,0);
|
||||
}
|
||||
|
||||
function printDebug() {
|
||||
print ("Nowtime: " + getTime(now));
|
||||
print ("Now: " + now);
|
||||
/*function printDebug() {
|
||||
print ("Goaltime: " + getTime(settingsChronowid.goal));
|
||||
print ("Goal: " + settingsChronowid.goal);
|
||||
print("Difftime: " + getTime(diff));
|
||||
print("Diff: " + diff);
|
||||
print ("Started: " + settingsChronowid.started);
|
||||
print ("----");
|
||||
}
|
||||
}*/
|
||||
|
||||
//counts down, calculates and displays
|
||||
function countDown() {
|
||||
now = new Date();
|
||||
var now = new Date();
|
||||
diff = settingsChronowid.goal - now; //calculate difference
|
||||
WIDGETS["chronowid"].draw();
|
||||
//time is up
|
||||
// time is up
|
||||
if (settingsChronowid.started && diff < 1000) {
|
||||
Bangle.buzz(1500);
|
||||
//write timer off to file
|
||||
settingsChronowid.started = false;
|
||||
storage.writeJSON('chronowid.json', settingsChronowid);
|
||||
require('Storage').writeJSON('chronowid.json', settingsChronowid);
|
||||
clearInterval(interval); //stop interval
|
||||
interval = undefined;
|
||||
}
|
||||
//printDebug();
|
||||
// calculates width and redraws accordingly
|
||||
WIDGETS["chronowid"].redraw();
|
||||
}
|
||||
|
||||
// draw your widget
|
||||
function draw() {
|
||||
if (!settingsChronowid.started) {
|
||||
width = 0;
|
||||
return; //do not draw anything if timer is not started
|
||||
}
|
||||
g.reset();
|
||||
if (diff >= 0) {
|
||||
if (diff < 3600000) { //less than 1 hour left
|
||||
width = 58;
|
||||
g.clearRect(this.x,this.y,this.x+width,this.y+height);
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(getTime(diff).substring(3), this.x+1, this.y+5); //remove hour part 00:00:00 -> 00:00
|
||||
}
|
||||
if (diff >= 3600000) { //one hour or more left
|
||||
width = 48;
|
||||
g.clearRect(this.x,this.y,this.x+width,this.y+height);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(getTime(diff), this.x+1, this.y+((height/2)-4)); //display hour 00:00:00
|
||||
}
|
||||
}
|
||||
// not needed anymoe, because we check if diff < 1000 now, so 00:00 is displayed.
|
||||
// else {
|
||||
// width = 58;
|
||||
// g.clearRect(this.x,this.y,this.x+width,this.y+height);
|
||||
// g.setFont("6x8", 2);
|
||||
// g.drawString("END", this.x+15, this.y+5);
|
||||
// }
|
||||
}
|
||||
|
||||
if (settingsChronowid.started) interval = setInterval(countDown, 1000); //start countdown each second
|
||||
|
||||
// add the widget
|
||||
WIDGETS["chronowid"]={area:"bl",width:width,draw:draw,reload:function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
WIDGETS["chronowid"]={area:"tl",width:0,draw:function() {
|
||||
if (!this.width) return;
|
||||
g.reset().setFontAlign(0,0).clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
||||
//g.drawRect(this.x,this.y,this.x+this.width-1, this.y+23);
|
||||
var scale;
|
||||
var timeStr;
|
||||
if (diff < 3600000) { //less than 1 hour left
|
||||
width = 58;
|
||||
scale = 2;
|
||||
timeStr = getTime(diff).substring(3); // remove hour part 00:00:00 -> 00:00
|
||||
} else { //one hour or more left
|
||||
width = 48;
|
||||
scale = 1;
|
||||
timeStr = getTime(diff); //display hour 00:00:00 but small
|
||||
}
|
||||
// Font5x9Numeric7Seg - just build this in as it's tiny
|
||||
g.setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 9 + (scale<<8));
|
||||
g.drawString(timeStr, this.x+this.width/2, this.y+12);
|
||||
}, redraw:function() {
|
||||
var last = this.width;
|
||||
if (!settingsChronowid.started) this.width = 0;
|
||||
else this.width = (diff < 3600000) ? 58 : 48;
|
||||
if (last != this.width) Bangle.drawWidgets();
|
||||
else this.draw();
|
||||
}, reload:function() {
|
||||
settingsChronowid = require('Storage').readJSON("chronowid.json",1)||{};
|
||||
if (interval) clearInterval(interval);
|
||||
interval = undefined;
|
||||
// start countdown each second
|
||||
if (settingsChronowid.started) interval = setInterval(countDown, 1000);
|
||||
// reset everything
|
||||
countDown();
|
||||
}};
|
||||
|
||||
//printDebug();
|
||||
countDown();
|
||||
})();
|
||||
// set width correctly, start countdown each second
|
||||
WIDGETS["chronowid"].reload();
|
||||
})();
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Submitted to App Loader
|
||||
0.02: Removed unneded code, added HID controlls thanks to t0m1o1 for his code :p
|
||||
0.03: Load widgets after Bangle.setUI to ensure widgets know if they're on a clock or not (fix #970)
|
||||
|
|
|
@ -50,7 +50,7 @@ if (next) {
|
|||
setTimeout(drawApp, 1000);
|
||||
Bangle.setLocked(true);
|
||||
}, BTN1, { edge:"falling",repeat:true,debounce:50});
|
||||
Bangle.on('drag', function(e) {
|
||||
Bangle.on('drag', function(e) {
|
||||
if(!e.b){
|
||||
console.log(lasty);
|
||||
console.log(lastx);
|
||||
|
@ -91,7 +91,7 @@ if (next) {
|
|||
lasty = lasty + e.dy;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -144,14 +144,15 @@ function writeLine(str,line){
|
|||
}
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawAll();
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on) drawAll();
|
||||
});
|
||||
var click = setInterval(updateTime, 1000);
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clockupdown", btn=>{
|
||||
drawAll();
|
||||
drawAll(); // why do we redraw here??
|
||||
});
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawAll();
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New clock!
|
||||
0.02: Load steps from Health Tracking app (if installed)
|
|
@ -0,0 +1,24 @@
|
|||
# Command line complete clock
|
||||
|
||||
Command line styled clock with lots of information:
|
||||
|
||||
It can show the following (depending on availability) information:
|
||||
* Time data:
|
||||
* Time
|
||||
* Day of week
|
||||
* Date
|
||||
* Additional information (can be toggled via settings):
|
||||
* Weather conditions and temperature (requires app [Weather](https://banglejs.com/apps/#weather))
|
||||
* Steps (requires app [Health Tracking](https://banglejs.com/apps/#health%20tracking) or a step widget)
|
||||
* Heart rate (when screen is on and unlocked)
|
||||
|
||||
## TODO
|
||||
* Make time font bigger
|
||||
* Show progress of steps (if any goal is set)
|
||||
* Show trend of HRM out of history data
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icon
|
||||
Icon taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgI/8/4ACAqYv/F/PwAqgA6A=="))
|
|
@ -0,0 +1,250 @@
|
|||
const storage = require('Storage');
|
||||
const locale = require("locale");
|
||||
|
||||
const font12 = g.getFonts().includes("12x20");
|
||||
const font = font12 ? "12x20" : "6x8";
|
||||
const fontsize = font12 ? 1: 2;
|
||||
const fontheight = 19;
|
||||
|
||||
const marginTop = 5;
|
||||
const marginLeftTopic = 3; // margin of topics
|
||||
const marginLeftData = font12 ? 64 : 75; // margin of data values
|
||||
|
||||
const topicColor = g.theme.dark ? "#fff" : "#000";
|
||||
const textColor = g.theme.dark ? "#0f0" : "#080";
|
||||
const textColorRed = g.theme.dark ? "#FF0000" : "#FF0000";
|
||||
|
||||
let hrtValue;
|
||||
let hrtValueIsOld = false;
|
||||
|
||||
let localTempValue;
|
||||
let weatherTempString;
|
||||
let lastHeartRateRowIndex;
|
||||
let lastStepsRowIndex;
|
||||
let i = 2;
|
||||
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = storage.readJSON('clicompleteclk.json', 1) || {};
|
||||
}
|
||||
|
||||
function setting(key) {
|
||||
if (!settings) { loadSettings(); }
|
||||
const DEFAULTS = {
|
||||
'battery': true,
|
||||
'batteryLvl': 30,
|
||||
'weather': true,
|
||||
'steps': true,
|
||||
'heartrate': true
|
||||
};
|
||||
return (key in settings) ? settings[key] : DEFAULTS[key];
|
||||
}
|
||||
|
||||
|
||||
let showBattery = setting('battery');
|
||||
let batteryWarnLevel = setting('batteryLvl');
|
||||
let showWeather = setting('weather');
|
||||
let showSteps = setting('steps');
|
||||
let showHeartRate = setting('heartrate');
|
||||
|
||||
|
||||
var drawTimeout;
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
drawAll(true);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function drawAll(drawInfoToo){
|
||||
let now = new Date();
|
||||
updateTime(now);
|
||||
if (drawInfoToo) {
|
||||
drawInfo(now);
|
||||
}
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function updateTime(now){
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
writeLineTopic("TIME", 1);
|
||||
writeLine(locale.time(now,1),1);
|
||||
}
|
||||
|
||||
function drawInfo(now) {
|
||||
if (now == undefined)
|
||||
now = new Date();
|
||||
|
||||
i = 2;
|
||||
|
||||
writeLineTopic("DOWK", i);
|
||||
writeLine(locale.dow(now),i);
|
||||
i++;
|
||||
|
||||
writeLineTopic("DATE", i);
|
||||
writeLine(locale.date(now,1),i);
|
||||
i++;
|
||||
|
||||
if (showBattery) {
|
||||
writeLineTopic("BATT", i);
|
||||
const b = E.getBattery();
|
||||
writeLine(b + "%", i, b < batteryWarnLevel ? textColorRed : textColor);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (showWeather) {
|
||||
drawWeather();
|
||||
}
|
||||
|
||||
if (showSteps) {
|
||||
drawSteps(i);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (showHeartRate) {
|
||||
drawHeartRate(i);
|
||||
}
|
||||
}
|
||||
|
||||
function drawWeather() {
|
||||
const weatherJson = getWeather();
|
||||
if(weatherJson && weatherJson.weather){
|
||||
const currentWeather = weatherJson.weather;
|
||||
|
||||
const weatherTempValue = locale.temp(currentWeather.temp-273.15);
|
||||
weatherTempString = weatherTempValue;
|
||||
writeLineTopic("WTHR", i);
|
||||
writeLine(currentWeather.txt,i);
|
||||
i++;
|
||||
|
||||
writeLineTopic("TEMP", i);
|
||||
writeLine(weatherTempValue,i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
function drawSteps(i) {
|
||||
if (!showSteps) return;
|
||||
if (i == undefined)
|
||||
i = lastStepsRowIndex;
|
||||
const steps = getSteps();
|
||||
if (steps != undefined) {
|
||||
writeLineTopic("STEP", i);
|
||||
writeLine(steps, i);
|
||||
}
|
||||
lastStepsRowIndex = i;
|
||||
}
|
||||
|
||||
function drawHeartRate(i) {
|
||||
if (!showHeartRate) return;
|
||||
if (i == undefined)
|
||||
i = lastHeartRateRowIndex;
|
||||
writeLineTopic("HRTM", i);
|
||||
if (hrtValue != undefined) {
|
||||
if (!hrtValueIsOld)
|
||||
writeLine(hrtValue,i);
|
||||
else
|
||||
writeLine(hrtValue,i, topicColor);
|
||||
}
|
||||
lastHeartRateRowIndex = i;
|
||||
}
|
||||
|
||||
|
||||
function writeLineTopic(str, line) {
|
||||
var y = marginTop+line*fontheight;
|
||||
g.setFont(font,fontsize);
|
||||
g.setColor(topicColor).setFontAlign(-1,-1);
|
||||
|
||||
g.clearRect(0,y,g.getWidth(),y+fontheight-1);
|
||||
g.drawString("[" + str + "]",marginLeftTopic,y);
|
||||
}
|
||||
|
||||
function writeLine(str,line,pColor){
|
||||
if (pColor == undefined)
|
||||
pColor = textColor;
|
||||
var y = marginTop+line*fontheight;
|
||||
g.setFont(font,fontsize);
|
||||
g.setColor(pColor).setFontAlign(-1,-1);
|
||||
g.drawString(str,marginLeftData,y);
|
||||
}
|
||||
|
||||
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
let health;
|
||||
try {
|
||||
health = require("health");
|
||||
} catch (e) {
|
||||
// Module health not found
|
||||
}
|
||||
if (health != undefined) {
|
||||
health.readDay(new Date(), h=>steps+=h.steps);
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom.getSteps();
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
function getWeather() {
|
||||
let jsonWeather = storage.readJSON('weather.json');
|
||||
return jsonWeather;
|
||||
}
|
||||
|
||||
// EVENTS:
|
||||
|
||||
// turn on HRM when the LCD is unlocked
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
if (showHeartRate) {
|
||||
Bangle.setHRMPower(1,"clicompleteclk");
|
||||
if (hrtValue == undefined)
|
||||
hrtValue = "...";
|
||||
else
|
||||
hrtValueIsOld = true;
|
||||
}
|
||||
} else {
|
||||
if (showHeartRate) {
|
||||
hrtValueIsOld = true;
|
||||
Bangle.setHRMPower(0,"clicompleteclk");
|
||||
}
|
||||
}
|
||||
// Update steps and heart rate
|
||||
drawSteps();
|
||||
drawHeartRate();
|
||||
});
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on) {
|
||||
drawAll(true);
|
||||
} else {
|
||||
if (showHeartRate) {
|
||||
hrtValueIsOld = true;
|
||||
}
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
if (showHeartRate) {
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
//if(hrm.confidence > 90){
|
||||
hrtValueIsOld = false;
|
||||
hrtValue = hrm.bpm;
|
||||
if (Bangle.isLCDOn())
|
||||
drawHeartRate();
|
||||
//} else {
|
||||
// hrtValue = undefined;
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
g.clear();
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
loadSettings();
|
||||
drawAll(true);
|
After Width: | Height: | Size: 258 B |
|
@ -0,0 +1,54 @@
|
|||
(function(back) {
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON('clicompleteclk.json', 1) || {};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
storage.write('clicompleteclk.json', settings);
|
||||
}
|
||||
E.showMenu({
|
||||
'': { 'title': 'CLI complete clk' },
|
||||
'Show battery': {
|
||||
value: "battery" in settings ? settings.battery : false,
|
||||
format: () => (settings.battery ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.battery = !settings.battery;
|
||||
save('battery', settings.battery);
|
||||
},
|
||||
},
|
||||
'Battery warn': {
|
||||
value: "batteryLvl" in settings ? settings.batteryLvl : 30,
|
||||
min: 0,
|
||||
max : 100,
|
||||
step: 10,
|
||||
format: x => {
|
||||
return x + "%";
|
||||
},
|
||||
onchange: x => save('batteryLvl', x),
|
||||
},
|
||||
'Show weather': {
|
||||
value: "weather" in settings ? settings.weather : false,
|
||||
format: () => (settings.weather ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.weather = !settings.weather;
|
||||
save('weather', settings.weather);
|
||||
},
|
||||
},
|
||||
'Show steps': {
|
||||
value: "steps" in settings ? settings.steps : false,
|
||||
format: () => (settings.steps ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.steps = !settings.steps;
|
||||
save('steps', settings.steps);
|
||||
},
|
||||
},
|
||||
'Show heartrate': {
|
||||
value: "heartrate" in settings ? settings.heartrate : false,
|
||||
format: () => (settings.heartrate ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.heartrate = !settings.heartrate;
|
||||
save('heartrate', settings.heartrate);
|
||||
},
|
||||
},
|
||||
'< Back': back,
|
||||
});
|
||||
});
|
|
@ -7,3 +7,4 @@
|
|||
0.13: Use setUI, work with smaller screens and themes
|
||||
0.14: Fix BTN1 (fix #853)
|
||||
Add light/dark theme support
|
||||
0.15: Load widgets after Bangle.setUI to ensure widgets know if they're on a clock or not (fix #970)
|
||||
|
|
|
@ -183,9 +183,6 @@ Bangle.on('HRM', function(hrm) {
|
|||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawAll();
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on) drawAll();
|
||||
});
|
||||
|
@ -195,4 +192,7 @@ Bangle.setUI("clockupdown", btn=>{
|
|||
if (btn<0) changeInfoMode();
|
||||
if (btn>0) changeFunctionMode();
|
||||
drawAll();
|
||||
});
|
||||
});
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawAll();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.1: New app
|
|
@ -0,0 +1,20 @@
|
|||
# CoreTemp display
|
||||
|
||||
Basic bare-bones example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current body core temperature readings.
|
||||
|
||||
## Usage
|
||||
|
||||
On startup connects to a CoreTemp device (1809/2A1C) and emits a "Core, temp" value for each reading.
|
||||
The app simply displays these readings on screen.
|
||||
|
||||
## TODO
|
||||
|
||||
* Integrate with other tracking/sports apps to log data.
|
||||
* Add device selection
|
||||
* Provide enable/disable option
|
||||
* Check status, add Retry/reconnect
|
||||
* Also provide skin temp reading
|
||||
|
||||
## Creator
|
||||
|
||||
Ivor Hewitt
|
|
@ -0,0 +1,23 @@
|
|||
(function() {
|
||||
var gatt;
|
||||
|
||||
//Would it be better to scan by uuid rather than name?
|
||||
NRF.requestDevice({ timeout: 20000, filters: [{ name: 'CORE [a]' }] }).then(function(device) {
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService("1809");
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic("2A1C");
|
||||
}).then(function(characteristic) {
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var core = (dv.buffer[2]*256+dv.buffer[1])/100;
|
||||
Bangle.emit('Core',{
|
||||
temp:core
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
});
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///k0DxUFgsDCY8KwAfJlQLHhWglWq1WgBIcCA4QCB1WoComq0+iBYWqCwl//4OBAAQxChWlv/2BYIlCBYUqv9VvQLBwA9BBYWlqtV/QLBGoRIBgQLBr9aBYQ2BBYMKroLBtQLCgALClIKC1AXG1NVuoFBF4sC09V+woCBAJHCgWXq9oPQZrDgWdq9gBZG9rqgCTwSbCgVVqysDBYkK6tWYoa/DkEJ6vaaIgWBaAILCbQhUCBYXoc4wNBBZWqBfBtB1ALKKZILCR4J3FToQLBU4KPEWoQLNZYILIa4NVcYReEcYOnqtaDAbvDgALBcg4EBlNVqtqDoOgd4YoBBYNWytWCwQdCgQLBAAVaBYkA0oLDuwLFkv1BgZGDAAMJuoKCroWEGAOnDAVftShGr////1tDdG14LB+wiEAAdqHAjTHBYgA=="))
|
|
@ -0,0 +1,19 @@
|
|||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
var btm = g.getHeight()-1;
|
||||
|
||||
function onCore(c) {
|
||||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,24,g.getWidth(),80);
|
||||
var str = c.temp + "C";
|
||||
g.setFontVector(40).drawString(str,px,45);
|
||||
}
|
||||
Bangle.on('Core', onCore);
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
After Width: | Height: | Size: 4.8 KiB |
|
@ -3,3 +3,5 @@
|
|||
0.03: cycle thru pages
|
||||
0.04: reset to clock after 2 mins of inactivity
|
||||
0.05: add Bangle 2 version
|
||||
0.06: Adds settings page (hide clocks or launchers)
|
||||
0.06: Adds setting for directly launching app on touch for Bangle 2
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
function wdog(handle,timeout){
|
||||
if(handle !== undefined){
|
||||
wdog.handle = handle;
|
||||
|
@ -17,7 +22,13 @@ function wdog(handle,timeout){
|
|||
wdog(load,120000)
|
||||
|
||||
var s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
|
||||
var apps = s.list(/\.info$/).map(app=>{
|
||||
var a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
|
|
|
@ -2,8 +2,20 @@
|
|||
*
|
||||
*/
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
var s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
|
||||
var apps = s.list(/\.info$/).map(app=>{
|
||||
var a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
|
@ -28,7 +40,7 @@ const YOFF = 30;
|
|||
function draw_icon(p,n,selected) {
|
||||
var x = (n%2)*72+XOFF;
|
||||
var y = n>1?72+YOFF:YOFF;
|
||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+10,y+2,x+60,y+52);
|
||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
||||
g.clearRect(x+12,y+4,x+59,y+51);
|
||||
g.setColor(g.theme.fg);
|
||||
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||
|
@ -52,7 +64,7 @@ function drawPage(p){
|
|||
}
|
||||
for (var i=0;i<4;i++) {
|
||||
if (!apps[p*4+i]) return i;
|
||||
draw_icon(p,i,selected==i);
|
||||
draw_icon(p,i,selected==i && !settings.direct);
|
||||
}
|
||||
g.flip();
|
||||
}
|
||||
|
@ -81,9 +93,9 @@ Bangle.on("touch",(_,p)=>{
|
|||
for (i=0;i<4;i++){
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
draw_icon(page,i,true);
|
||||
if (selected>=0) {
|
||||
if (selected!=i){
|
||||
draw_icon(page,i,true && !settings.direct);
|
||||
if (selected>=0 || settings.direct) {
|
||||
if (selected!=i && !settings.direct){
|
||||
draw_icon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
(function(back) {
|
||||
var FILE = "dtlaunch.json";
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "Desktop launcher" },
|
||||
"< Back" : () => back(),
|
||||
'Show clocks': {
|
||||
value: settings.showClocks,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showClocks = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Show launchers': {
|
||||
value: settings.showLaunchers,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showLaunchers = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
|
@ -0,0 +1,42 @@
|
|||
(function(back) {
|
||||
var FILE = "dtlaunch.json";
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "Desktop launcher" },
|
||||
"< Back" : () => back(),
|
||||
'Show clocks': {
|
||||
value: settings.showClocks,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showClocks = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Show launchers': {
|
||||
value: settings.showLaunchers,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showLaunchers = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Direct launch': {
|
||||
value: settings.direct,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.direct = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Upgraded text to images, added welcome screen and subtitles.
|
||||
0.03: Advertise app name as Espruino manufacturer data when idle.
|
||||
|
|
|
@ -32,6 +32,7 @@ const CYCLE_BUZZ_MILLISECONDS = 50;
|
|||
const WELCOME_MESSAGE = 'Emojuino:\r\n\r\n< Swipe >\r\nto select\r\n\r\nTap\r\nto transmit';
|
||||
|
||||
// Non-user-configurable constants
|
||||
const APP_ID = 'emojuino';
|
||||
const IMAGE_INDEX = 0;
|
||||
const CODE_POINT_INDEX = 1;
|
||||
const EMOJI_PX = 96;
|
||||
|
@ -40,12 +41,11 @@ const EMOJI_Y = (g.getHeight() - EMOJI_PX) / 2;
|
|||
const TX_X = 68;
|
||||
const TX_Y = 12;
|
||||
const FONT_SIZE = 24;
|
||||
const BTN_WATCH_OPTIONS = { repeat: true, debounce: 20, edge: "falling" };
|
||||
const ESPRUINO_COMPANY_CODE = 0x0590;
|
||||
const UNICODE_CODE_POINT_ELIDED_UUID = [ 0x49, 0x6f, 0x49, 0x44, 0x55,
|
||||
0x54, 0x46, 0x2d, 0x33, 0x32 ];
|
||||
|
||||
|
||||
|
||||
// Global variables
|
||||
let emojiIndex = 0;
|
||||
let isToggleOn = false;
|
||||
|
@ -100,9 +100,22 @@ function transmitEmoji(image, codePoint, duration) {
|
|||
}
|
||||
|
||||
|
||||
// Transmit the app name under the Espruino company code to facilitate discovery
|
||||
function transmitAppName() {
|
||||
let options = {
|
||||
showName: false,
|
||||
manufacturer: ESPRUINO_COMPANY_CODE,
|
||||
manufacturerData: JSON.stringify({ name: APP_ID }),
|
||||
interval: 2000
|
||||
}
|
||||
|
||||
NRF.setAdvertising({}, options);
|
||||
}
|
||||
|
||||
|
||||
// Terminate the emoji transmission
|
||||
function terminateEmoji(displayIntervalId) {
|
||||
NRF.setAdvertising({ });
|
||||
transmitAppName();
|
||||
isTransmitting = false;
|
||||
clearInterval(displayIntervalId);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], false);
|
||||
|
@ -169,3 +182,4 @@ g.setFontAlign(0, 0);
|
|||
g.drawString(WELCOME_MESSAGE, g.getWidth() / 2, g.getHeight() / 2);
|
||||
Bangle.on('touch', handleTouch);
|
||||
Bangle.on('drag', handleDrag);
|
||||
transmitAppName();
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# FLOW
|
||||
|
||||
This is a game where you have to help a flow avoid white obstacles thing by tapping!
|
||||
This is a demake of an app which I forgot the name of.
|
||||
Press BTN(1) to restart.
|
||||
See if you can get to 2500 score!
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4X/AwX48EHgEC1WgCQkVqoDBBfuqBQcBqoLagEqGAguBqALaGAOoAoQuEBbEAKgIMBBQNUBbgMCyoKHBbBVBBYIKGBbEBtNVrQLfOgNaT4gLagp0CPQOABbcBFwNAgEKBgILbitVqAFClWq0ALZFwTDFGAQLZFwYwDBfg"))
|
|
@ -0,0 +1,220 @@
|
|||
const isB2 = process.env.HWVERSION === 2;
|
||||
|
||||
// Bangle.js 1 runs just too fast in direct mode??? (also no getPixel)
|
||||
if (!isB2) Bangle.setLCDMode("120x120");
|
||||
|
||||
const options = Bangle.getOptions();
|
||||
|
||||
options.lockTimeout = 0;
|
||||
options.lcdPowerTimeout = 0;
|
||||
|
||||
Bangle.setOptions(options);
|
||||
|
||||
g.reset();
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.setColor(255, 255, 255);
|
||||
g.clear();
|
||||
const h = g.getHeight();
|
||||
|
||||
function trigToCoord(ret) {
|
||||
return ((ret + 1) * h) / 2;
|
||||
}
|
||||
|
||||
function trigToLen(ret) {
|
||||
return (ret * h) / 2;
|
||||
}
|
||||
|
||||
let i = 0.2;
|
||||
let speedCoef = 0.014;
|
||||
|
||||
let flowFile = require("Storage").readJSON("flow.json");
|
||||
|
||||
let highestI = (flowFile && flowFile.hiscore) || 0.1;
|
||||
|
||||
let colorA = [255, 255, 0];
|
||||
let colorB = [0, 255, 255];
|
||||
|
||||
let x = 0;
|
||||
let xt = 0;
|
||||
let safeMode = false;
|
||||
let lost = false;
|
||||
|
||||
function offsetRect(g, x, y, w) {
|
||||
g.fillRect(x, y, x + w, y + w);
|
||||
}
|
||||
|
||||
function getColor(num) {
|
||||
return [
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[1, 1, 0],
|
||||
[0, 1, 1],
|
||||
[1, 0, 1],
|
||||
[0.5, 0.5, 1],
|
||||
[1, 0.5, 0],
|
||||
[0, 1, 0.5],
|
||||
[0.5, 0.5, 0.5],
|
||||
][num];
|
||||
}
|
||||
|
||||
function calculateColor(num) {
|
||||
colorA = getColor(Math.floor((num % 1) * 10));
|
||||
colorB = getColor(Math.floor((num % 10) - (num % 1)));
|
||||
}
|
||||
|
||||
calculateColor(highestI);
|
||||
|
||||
Bangle.on("touch", () => (safeMode = !safeMode));
|
||||
|
||||
function resetGame() {
|
||||
x = xt = 0;
|
||||
safeMode = lost = false;
|
||||
i = 0.2;
|
||||
speedCoef = 0.014;
|
||||
obstaclePeriod = 150;
|
||||
obstacleMode = 1;
|
||||
g.clear();
|
||||
shownScore = false;
|
||||
intervalId = setInterval(draw);
|
||||
}
|
||||
|
||||
function checkCollision() {
|
||||
lost = g.getPixel(trigToCoord(+x), (h * 2) / 3 - 4) !== 0;
|
||||
if (lost) {
|
||||
scoringI = i;
|
||||
speedCoef = Math.min(speedCoef, 0.02);
|
||||
g.setFont(isB2 ? "6x15" : "4x6", 3);
|
||||
g.setColor(colorA[0], colorA[1], colorA[2])
|
||||
.drawString(
|
||||
"Game over",
|
||||
trigToCoord(0) - g.stringWidth("Game over") / 2,
|
||||
trigToCoord(0)
|
||||
)
|
||||
.setColor(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPlayer() {
|
||||
if (!safeMode) xt = Math.cos(i * Math.PI * 4) / 7.5;
|
||||
else xt = -Math.cos(i * Math.PI * 2) / 20 + 0.35;
|
||||
x = x * 0.8 + xt * 0.2;
|
||||
if (highestI > 250) calculateColor(i);
|
||||
g.setColor(colorA[0], colorA[1], colorA[2]);
|
||||
offsetRect(g, trigToCoord(+x), (h * 2) / 3, 3);
|
||||
g.setColor(colorB[0], colorB[1], colorB[2]);
|
||||
offsetRect(g, trigToCoord(-x), (h * 2) / 3, 3);
|
||||
}
|
||||
|
||||
let obstaclePeriod = 150;
|
||||
let obstacleMode = 1;
|
||||
|
||||
function drawObstracle() {
|
||||
g.setColor(1, 1, 1);
|
||||
switch (obstacleMode) {
|
||||
case 0:
|
||||
offsetRect(g, trigToCoord(-0.15), 0, trigToLen(0.3));
|
||||
break;
|
||||
case 1:
|
||||
offsetRect(g, trigToCoord(0.2), 0, trigToLen(0.2));
|
||||
offsetRect(g, trigToCoord(-0.4), 0, trigToLen(0.2));
|
||||
break;
|
||||
case 2:
|
||||
break;
|
||||
}
|
||||
obstaclePeriod--;
|
||||
if (obstaclePeriod <= 0) {
|
||||
// If we are off cooldown mode, pick a random actual mode
|
||||
if (obstacleMode === 2) {
|
||||
obstaclePeriod = Math.random() * 50 + 50;
|
||||
obstacleMode = Math.round(Math.random());
|
||||
} else if (Math.random() > 0.5) {
|
||||
// Give it a chance to repeat with no cooldown
|
||||
obstaclePeriod = 25 + 2.5 * speedCoef;
|
||||
obstacleMode = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shownScore = false;
|
||||
let scoringI = 0;
|
||||
|
||||
function draw() {
|
||||
if (!lost) {
|
||||
drawPlayer();
|
||||
checkCollision();
|
||||
speedCoef *= 1.0005;
|
||||
drawObstracle();
|
||||
} else {
|
||||
speedCoef /= 1.05;
|
||||
if (speedCoef <= 0.005) {
|
||||
clearInterval(intervalId);
|
||||
i -= speedCoef;
|
||||
g.setFont(isB2 ? "6x15" : "4x6", 1);
|
||||
const str = "Hiscore: " + Math.round(highestI * 10);
|
||||
g.setColor(
|
||||
scoringI > highestI ? 0 : 255,
|
||||
0,
|
||||
scoringI > highestI ? 255 : 0
|
||||
)
|
||||
.drawString(
|
||||
str,
|
||||
trigToCoord(0) - g.stringWidth(str) / 2,
|
||||
trigToCoord(0)
|
||||
)
|
||||
.setColor(255, 255, 255);
|
||||
if (scoringI > highestI) {
|
||||
highestI = scoringI;
|
||||
require("Storage").writeJSON("flow.json", {
|
||||
hiscore: highestI,
|
||||
});
|
||||
calculateColor(highestI);
|
||||
}
|
||||
setTimeout(resetGame, 3000);
|
||||
} else if (speedCoef <= 0.01 && !shownScore) {
|
||||
shownScore = true;
|
||||
g.setFont(isB2 ? "6x15" : "4x6", 2);
|
||||
const str = "Score: " + Math.round(scoringI * 10);
|
||||
g.setColor(colorB[0], colorB[1], colorB[2])
|
||||
.drawString(
|
||||
str,
|
||||
trigToCoord(0) - g.stringWidth(str) / 2,
|
||||
trigToCoord(0)
|
||||
)
|
||||
.setColor(1, 1, 1);
|
||||
}
|
||||
}
|
||||
i += speedCoef;
|
||||
g.scroll(0, speedCoef * h);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
let intervalId;
|
||||
|
||||
if (BTN.read()) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
color = getColor(i);
|
||||
g.setColor(color[0], color[1], color[2]);
|
||||
g.fillRect((i / 10) * h, 0, ((i + 1) / 10) * h, h);
|
||||
}
|
||||
g.setColor(0);
|
||||
g.setFont("Vector", 9);
|
||||
let str = "Welcome to the debug screen!";
|
||||
g.drawString(
|
||||
str,
|
||||
trigToCoord(0) - g.stringWidth(str) / 2,
|
||||
trigToCoord(0) - 9
|
||||
);
|
||||
str = "Don't hold BTN while opening to play!";
|
||||
g.drawString(str, trigToCoord(0) - g.stringWidth(str) / 2, trigToCoord(0));
|
||||
g.flip();
|
||||
setInterval(() => {
|
||||
g.scroll(0, 0.014 * h);
|
||||
i += 0.014;
|
||||
calculateColor(i);
|
||||
g.setColor(colorA[0], colorA[1], colorA[2]);
|
||||
g.fillRect(0, 0, trigToCoord(0), 0.014 * h);
|
||||
g.setColor(colorB[0], colorB[1], colorB[2]);
|
||||
g.fillRect(trigToCoord(0), 0, trigToCoord(1), 0.014 * h);
|
||||
}, 1000 / 30);
|
||||
} else intervalId = setInterval(draw, 1000 / 30);
|
After Width: | Height: | Size: 323 B |
After Width: | Height: | Size: 992 B |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
|
@ -1 +1,4 @@
|
|||
0.01: Initial version
|
||||
0.02: Add support for ZIPs
|
||||
Find and download ZIPs direct from the Espruino website
|
||||
Take 'beta' tag off
|
||||
|
|
|
@ -4,28 +4,45 @@
|
|||
</head>
|
||||
<body>
|
||||
<p><b>THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE
|
||||
INSTRUCTIONS FOR <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">BANGLE.JS</a> 1 AND <a href="https://www.espruino.com/Bangle.js2#firmware-updates" target="_blank">BANGLE.JS 2</a></b></p>
|
||||
INSTRUCTIONS FOR <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">BANGLE.JS</a> 1 AND <a href="https://www.espruino.com/Bangle.js2#firmware-updates" target="_blank">BANGLE.JS 2</a></b>. For usage on Bangle.js 2 you'll likely need to have an updated bootloader.</p>
|
||||
<div id="fw-unknown">
|
||||
<p>Firmware updates using the App Loader are only possible on
|
||||
<p><b>Firmware updates using the App Loader are only possible on
|
||||
Bangle.js 2. For firmware updates on Bangle.js 1 please
|
||||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></p>
|
||||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
|
||||
</div>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span></p>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p>Please upload a hex file here. This file should be the <code>.app_hex</code>
|
||||
<div id="latest-firmware" style="display:none">
|
||||
<p>The currently available Espruino firmware releases are:</p>
|
||||
<ul id="latest-firmware-list">
|
||||
</ul>
|
||||
<p>To update, click the link and then click the 'Upload' button that appears.</p>
|
||||
</div>
|
||||
|
||||
<p>Or you can upload a hex or zip file here. This file should be an <code>.app_hex</code>
|
||||
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
|
||||
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex"/><br>
|
||||
<p><button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
|
||||
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
|
||||
</div>
|
||||
|
||||
<p>Firmware updates via this tool work differently to the NRF Connect method mentioned on
|
||||
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js page</a>. Firmware
|
||||
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
|
||||
the new firmware into internal Storage.</p>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
<script src="../../core/lib/espruinotools.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js"></script>
|
||||
|
||||
<script>
|
||||
var hex;
|
||||
var hexJS; // JS to upload hex
|
||||
var HEADER_LEN = 16; // size of app flash header
|
||||
var APP_START = 0x26000;
|
||||
var APP_MAX_LENGTH = 0xda000; // from linker file - the max size the app can be, for sanity check!
|
||||
var MAX_ADDRESS = 0x1000000; // discount anything in hex file above this
|
||||
var VERSION = 0x12345678; // VERSION! Use this to test firmware in JS land
|
||||
var DEBUG = false;
|
||||
|
@ -37,6 +54,8 @@ function log(t) {
|
|||
|
||||
function onInit(device) {
|
||||
console.log(device);
|
||||
if (device && device.version)
|
||||
document.getElementById("fw-version").innerText = device.version;
|
||||
if (device && device.id=="BANGLEJS2") {
|
||||
document.getElementById("fw-unknown").style = "display:none";
|
||||
document.getElementById("fw-ok").style = "";
|
||||
|
@ -44,7 +63,7 @@ function onInit(device) {
|
|||
}
|
||||
|
||||
function checkForFileOnServer() {
|
||||
/*function getURL(url, callback) {
|
||||
function getURL(url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = callback;
|
||||
baseURL = url;
|
||||
|
@ -55,6 +74,7 @@ function checkForFileOnServer() {
|
|||
|
||||
function getFilesFromURL(url, regex, callback) {
|
||||
getURL(url, function() {
|
||||
console.log(this.responseXML)
|
||||
var files = [];
|
||||
var elements = this.responseXML.getElementsByTagName("a");
|
||||
for (var i=0;i<elements.length;i++) {
|
||||
|
@ -67,35 +87,78 @@ function checkForFileOnServer() {
|
|||
});
|
||||
}
|
||||
|
||||
var regex = new RegExp("_bangle2");
|
||||
var regex = new RegExp("_banglejs2.*zip$");
|
||||
|
||||
var domFirmwareList = document.getElementById("latest-firmware-list");
|
||||
var domFirmware = document.getElementById("latest-firmware");
|
||||
console.log("Checking server...");
|
||||
|
||||
var domFirmware = document.getElementById("latest-firmware");
|
||||
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
|
||||
releaseFiles.sort().reverse().forEach(function(f) {
|
||||
releaseFiles.sort().reverse().forEach(function(f) {
|
||||
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
|
||||
domFirmware.innerHTML += 'Release: <a href="'+f+'">'+name+'</a><br/>';
|
||||
console.log("Found "+name);
|
||||
domFirmwareList.innerHTML += '<li>Release: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
|
||||
domFirmware.style = "";
|
||||
});
|
||||
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
|
||||
travisFiles.forEach(function(f) {
|
||||
var name = f.substr(f.lastIndexOf('/')+1);
|
||||
domFirmware.innerHTML += 'Cutting Edge build: <a href="'+f+'">'+name+'</a><br/>';
|
||||
console.log("Found "+name);
|
||||
domFirmwareList.innerHTML += '<li>Cutting Edge build: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
|
||||
domFirmware.style = "";
|
||||
});
|
||||
document.getElementById("checking-server").style = "display:none";
|
||||
document.getElementById("main-ui").style = "";
|
||||
console.log("Finished check for firmware files...");
|
||||
var fwlinks = document.querySelectorAll(".fw-link");
|
||||
for (var i=0;i<fwlinks.length;i++)
|
||||
fwlinks[i].addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
var url = e.target.href;
|
||||
downloadZipFile(url).then(info=>{
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
});
|
||||
});
|
||||
});
|
||||
});*/
|
||||
});
|
||||
}
|
||||
|
||||
function downloadFile() {
|
||||
/*response = await fetch(APP_HEX_PATH+"readlink.php?link="+APP_HEX_FILE, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (response.ok) {
|
||||
blob = await response.blob();
|
||||
data = await blob.text();
|
||||
document.getElementById("latest-firmware").innerHTML="(<b>"+data.toString()+"</b>)";
|
||||
}*/
|
||||
function downloadZipFile(url) {
|
||||
return new Promise((resolve,reject) => {
|
||||
Espruino.Core.Utils.getBinaryURL(url, (err, binary) => {
|
||||
if (err) return reject("Unable to download "+url);
|
||||
resolve(binary);
|
||||
});
|
||||
}).then(convertZipFile);
|
||||
}
|
||||
|
||||
function convertZipFile(binary) {
|
||||
var info = {};
|
||||
Promise.resolve(binary).then(binary => {
|
||||
info.binary = binary;
|
||||
return JSZip.loadAsync(binary)
|
||||
}).then(function(zipFile) {
|
||||
info.zipFile = zipFile;
|
||||
return info.zipFile.file("manifest.json").async("string");
|
||||
}).then(function(content) {
|
||||
info.manifest = JSON.parse(content).manifest;
|
||||
}).then(function(content) {
|
||||
console.log(info.manifest);
|
||||
return info.zipFile.file(info.manifest.application.dat_file).async("arraybuffer");
|
||||
}).then(function(content) {
|
||||
info.dat_file = content;
|
||||
}).then(function(content) {
|
||||
console.log(info.manifest);
|
||||
return info.zipFile.file(info.manifest.application.bin_file).async("arraybuffer");
|
||||
}).then(function(content) {
|
||||
info.bin_file = content;
|
||||
if (info.bin_file.byteLength > APP_MAX_LENGTH) throw new Error("Firmware file is too big!");
|
||||
info.storageContents = new Uint8Array(info.bin_file.byteLength + HEADER_LEN)
|
||||
info.storageContents.set(new Uint8Array(info.bin_file), HEADER_LEN);
|
||||
createJS_app(info.storageContents, APP_START, APP_START+info.bin_file.byteLength);
|
||||
log("Download complete");
|
||||
console.log("Download complete",info);
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
return info;
|
||||
}).catch(err => log("ERROR:" + err));
|
||||
}
|
||||
|
||||
function handleFileSelect(event) {
|
||||
|
@ -103,13 +166,24 @@ function handleFileSelect(event) {
|
|||
log("More than one file selected!");
|
||||
return;
|
||||
}
|
||||
var file = event.target.files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
hex = event.target.result.split("\n");
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
fileLoaded();
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
if (file.name.endsWith(".hex") || file.name.endsWith(".app_hex")) {
|
||||
reader.onload = function(event) {
|
||||
hex = event.target.result.split("\n");
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
fileLoaded();
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
} else if (file.name.endsWith(".zip")) {
|
||||
reader.onload = function(event) {
|
||||
convertZipFile(event.target.result);
|
||||
};
|
||||
reader.readAsArrayBuffer(event.target.files[0]);
|
||||
} else {
|
||||
log("Unknown file extension for "+file.name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -174,14 +248,16 @@ function btoa(input) {
|
|||
return out;
|
||||
}
|
||||
|
||||
// To upload the app, we write to external flash
|
||||
function createJS_app(binary, bin32, startAddress, endAddress, HEADER_LEN) {
|
||||
/* To upload the app, we write to external flash,
|
||||
binary = Uint8Array of data to flash. Should include HEADER_LEN header, then bytes to flash */
|
||||
function createJS_app(binary, startAddress, endAddress) {
|
||||
/* typedef struct {
|
||||
uint32_t address;
|
||||
uint32_t size;
|
||||
uint32_t CRC;
|
||||
uint32_t version;
|
||||
} FlashHeader; */
|
||||
var bin32 = new Uint32Array(binary.buffer);
|
||||
bin32[0] = startAddress;
|
||||
bin32[1] = endAddress - startAddress;
|
||||
bin32[2] = CRC32(new Uint8Array(binary.buffer, HEADER_LEN));
|
||||
|
@ -189,7 +265,8 @@ function createJS_app(binary, bin32, startAddress, endAddress, HEADER_LEN) {
|
|||
console.log("CRC 0x"+bin32[2].toString(16));
|
||||
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
|
||||
hexJS += '\x10var s = require("Storage");\n';
|
||||
var CHUNKSIZE = 1024;
|
||||
hexJS += '\x10s.erase(".firmware");\n';
|
||||
var CHUNKSIZE = 2048;
|
||||
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
|
||||
var l = binary.length-i;
|
||||
if (l>CHUNKSIZE) l=CHUNKSIZE;
|
||||
|
@ -243,10 +320,8 @@ function fileLoaded() {
|
|||
});
|
||||
console.log(`// Data from 0x${startAddress.toString(16)} to 0x${endAddress.toString(16)} (${endAddress-startAddress} bytes)`);
|
||||
// Work out data
|
||||
var HEADER_LEN = 16;
|
||||
var binary = new Uint8Array(HEADER_LEN + endAddress-startAddress);
|
||||
binary.fill(0); // actually seems to assume a block is filled with 0 if not complete
|
||||
var bin32 = new Uint32Array(binary.buffer);
|
||||
parseLines(function(addr, data) {
|
||||
if (addr>MAX_ADDRESS) return; // ignore data out of range
|
||||
var binAddr = HEADER_LEN + addr - startAddress;
|
||||
|
@ -260,7 +335,7 @@ function fileLoaded() {
|
|||
createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress);
|
||||
} else {
|
||||
console.log("App - Writing to external flash");
|
||||
createJS_app(binary, bin32, startAddress, endAddress);
|
||||
createJS_app(binary, startAddress, endAddress);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -279,7 +354,7 @@ function handleUpload() {
|
|||
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
document.getElementById("upload").addEventListener("click", handleUpload);
|
||||
checkForFileOnServer();
|
||||
setTimeout(checkForFileOnServer, 10);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,26 @@
|
|||
# Gadgetbridge Debug
|
||||
|
||||
This is useful if your Bangle isn't responding to the Gadgetbridge
|
||||
Android app properly.
|
||||
|
||||
This app disables all existing Gadgetbridge handlers and then displays the
|
||||
messages that come from Gadgetbridge on the screen
|
||||
of the watch. It also saves the last 10 messages in a variable
|
||||
called `history`.
|
||||
|
||||
More info on Gadgetbridge at http://www.espruino.com/Gadgetbridge
|
||||
|
||||
## Usage
|
||||
|
||||
* Run the `GB Debug` app on your Bangle
|
||||
* Connect your Bangle to Gadgetbridge
|
||||
* Do whatever was causing you problems (eg receiving a call)
|
||||
* The Gadgetbridge message should now be displayed on-screen
|
||||
|
||||
If you want to get the *actual* data rather than copying it from the screen.
|
||||
|
||||
* Ensure the `GB Debug` app is kept running after the above steps
|
||||
* Disconnect Gadgetbridge from the Bangle
|
||||
* Connect the Web IDE on your PC
|
||||
* Type `show()` on the left-hand side of the IDE and the
|
||||
last 10 messages from Gadgetbridge will be shown.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4cBzsE/4AClMywH680rlOW9N9kmSpICnyBBBgQRMkBUDgIRKoBoGGRYAFHBGARpARHT5MJKxQAFLgzELCIlIBQkSCIsEPRKBHCIYbGoIRFiQRJhJgFCISeEBwMQOQykCCIqlBpMEBIgRHOQYRIYQbPDhAbBNwgRJVwOCTIgRFMAJKDgQRGOQprBCIMSGogHBJwwbBkC2FCJNbUgMNwHYBYPJCIhODju0yFNCIUGCJGCoE2NwO24EAmw1FHgWCpMGgQOBBIMwCJGSpMmyAjDCI6eBCIWAhu2I4IRCUIYREk+Ah3brEB2CzFAAIRCl3b23btsNCJckjoRC1h2CyAREtoNC9oDC2isCCIgHBjdt5MtCJj2CowjD2uyCIOSCI83lu123tAQIRI4EB28/++39/0mwRCoARCgbfByU51/3rev+mWCIQwCPok0EYIRB/gRDpJ+EcYQRJkARQdgq/Bl5HE7IRDZAltwAREyXbCIbIFgEfCIXsBwQCDQAYRNLgvfCIXtCI44Dm3JCIUlYoYCGkrjBk9bxMkyy9CChICFA="))
|
|
@ -0,0 +1,21 @@
|
|||
E.showMessage("Waiting for message");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var history = [];
|
||||
|
||||
GB = function(e) {
|
||||
if (history.length > 10)
|
||||
history = history.slice(history.length-10);
|
||||
history.push(e);
|
||||
|
||||
var s = JSON.stringify(e,null,2);
|
||||
|
||||
g.reset().clear(Bangle.appRect);
|
||||
g.setFont("6x8").setFontAlign(-1,0);
|
||||
g.drawString(s, 10, g.getHeight()/2);
|
||||
};
|
||||
|
||||
function show() {
|
||||
print(JSON.stringify(history,null,2));
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -5,3 +5,4 @@
|
|||
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
|
||||
0.06: Bangle.js 2 support
|
||||
0.07: Fix "previous" button image
|
||||
0.08: Fix scrolling title background color
|
||||
|
|
|
@ -91,7 +91,7 @@ function rScroller(l) {
|
|||
y = l.y+l.h/2;
|
||||
l.offset = l.offset%w;
|
||||
g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
||||
.setColor(l.col)
|
||||
.setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout
|
||||
.setFontAlign(-1, 0) // left center
|
||||
.clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
||||
.drawString(l.label, l.x-l.offset+40, y)
|
||||
|
|
|
@ -24,3 +24,5 @@
|
|||
0.22: Respect Quiet Mode
|
||||
0.23: Allow notification dismiss to remove from phone too
|
||||
0.24: tag HRM power requests to allow this to work alongside other widgets/apps (fix #799)
|
||||
0.25: workaround call notification
|
||||
Fix inflated step number
|
||||
|
|
|
@ -184,7 +184,7 @@
|
|||
case "call":
|
||||
var note = { size: 55, title: event.name, id: "call",
|
||||
body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))}
|
||||
if (event.cmd === "incoming") {
|
||||
if (event.cmd === "incoming" || event.cmd === "") {
|
||||
require("notify").show(note);
|
||||
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
|
||||
Bangle.buzz();
|
||||
|
@ -262,7 +262,7 @@
|
|||
// Send a summary of activity to Gadgetbridge
|
||||
function sendActivity(hrm) {
|
||||
var steps = currentSteps - lastSentSteps;
|
||||
lastSentSteps = 0;
|
||||
lastSentSteps = currentSteps;
|
||||
gbSend({ t: "act", stp: steps, hrm:hrm });
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: multiple player score support
|
|
@ -0,0 +1,37 @@
|
|||
# Golf Score
|
||||
|
||||
Lets you keep track of strokes during a game of Golf.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
1. Open the app,
|
||||
1. scroll to setup
|
||||
2. set the number of holes (18 by default, but can be configured)
|
||||
3. set the number of players (4 by default, but can be 1-20)
|
||||
4. click back
|
||||
5. scroll to a hole (hole 1)
|
||||
6. scroll to a player and set the number of strokes they took (repeat as needed)
|
||||
7. click next hole and repeat #6 and #7 as needed; or click back
|
||||
8. at any time, check the score card for a sum total of all the strokes for each player
|
||||
|
||||
## Features
|
||||
|
||||
Track strokes for multiple players (1-20)
|
||||
Set number of holes on course
|
||||
|
||||
## Controls
|
||||
|
||||
N/A
|
||||
|
||||
## Requests
|
||||
|
||||
Michael Salaverry (github.com/barakplasma)
|
||||
|
||||
## Creator
|
||||
|
||||
Michael Salaverry
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIEBgOABQcD4AFDg1wAokYDokOAokDDwkBDwkADwn4nAFD/geDgP8gYFEDwn8gFgDocA+AFCkE/A4IABg//Aoc//4RDn/+Goc/8AFJj4FLEQYFGh4FLIAYFGg4FKh5sBApEfnhTEAok+Aol8vihEAon4AocB+F4ZQYFF8AFDg/AAocPAouAKYcfXQQFHjzEEhjvDA"))
|
|
@ -0,0 +1,113 @@
|
|||
// @ts-check
|
||||
// @ts-ignore
|
||||
const menu = require("graphical_menu");
|
||||
/**
|
||||
* @type {{showMenu: (config) => void}}
|
||||
*/
|
||||
let E;
|
||||
/**
|
||||
* @type {{clear: () => void}}
|
||||
*/
|
||||
let g;
|
||||
|
||||
let holes_count = 18;
|
||||
let player_count = 4;
|
||||
/**
|
||||
* @type {number[][]}
|
||||
*/
|
||||
let course = new Array(holes_count).map(() => new Array(player_count).fill(0));
|
||||
|
||||
const main_menu = {
|
||||
"": {
|
||||
"title": "-- Golf --"
|
||||
},
|
||||
"Setup": function () { E.showMenu(setup_menu); },
|
||||
"Score Card": function () {
|
||||
calculate_score();
|
||||
E.showMenu(score_card);
|
||||
},
|
||||
};
|
||||
|
||||
function calculate_score() {
|
||||
let scores = course.reduce((acc, hole) => {
|
||||
hole.forEach((stroke_count, player) => {
|
||||
acc[player] = acc[player]+stroke_count;
|
||||
});
|
||||
return acc;
|
||||
}, new Array(player_count).fill(0));
|
||||
|
||||
score_card = {
|
||||
"": {
|
||||
"title": "score card"
|
||||
},
|
||||
"< Back": function () { E.showMenu(main_menu); },
|
||||
};
|
||||
|
||||
for (let player = 0; player < player_count; player++) {
|
||||
score_card["Player - " + (player + 1)] = {
|
||||
value: scores[player]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let score_card = {};
|
||||
|
||||
const setup_menu = {
|
||||
"": {
|
||||
"title": "-- Golf Setup --"
|
||||
},
|
||||
"Holes": {
|
||||
value: holes_count,
|
||||
min: 1, max: 20, step: 1, wrap: true,
|
||||
onchange: v => { holes_count = v; add_holes(); }
|
||||
},
|
||||
"Players": {
|
||||
value: player_count,
|
||||
min: 1, max: 10, step: 1, wrap: true,
|
||||
onchange: v => { player_count = v; }
|
||||
},
|
||||
"< Back": function () { E.showMenu(main_menu); },
|
||||
};
|
||||
|
||||
function inc_hole(i, player) { return function (v) { course[i][player] = v; }; }
|
||||
|
||||
function add_holes() {
|
||||
for (let j = 0; j < 20; j++) {
|
||||
delete main_menu["Hole - " + (j + 1)];
|
||||
}
|
||||
for (let i = 0; i < holes_count; i++) {
|
||||
course[i] = new Array(player_count).fill(0);
|
||||
main_menu["Hole - " + (i + 1)] = goto_hole_menu(i);
|
||||
}
|
||||
E.showMenu(main_menu);
|
||||
}
|
||||
|
||||
function goto_hole_menu(i) {
|
||||
return function () {
|
||||
E.showMenu(hole_menu(i));
|
||||
};
|
||||
}
|
||||
|
||||
function hole_menu(i) {
|
||||
let menu = {
|
||||
"": {
|
||||
"title": `-- Hole ${i + 1}--`
|
||||
},
|
||||
"Next hole": goto_hole_menu(i + 1),
|
||||
"< Back": function () { E.showMenu(main_menu); },
|
||||
};
|
||||
|
||||
for (let player = 0; player < player_count; player++) {
|
||||
menu[`player - ${player + 1}`] = {
|
||||
value: course[i][player],
|
||||
min: 1, max: 20, step: 1, wrap: true,
|
||||
onchange: inc_hole(i, player)
|
||||
};
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
g.clear();
|
||||
add_holes();
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -28,3 +28,4 @@
|
|||
0.24: Better support for Bangle.js 2, avoid widget area for Graphs, smooth graphs more
|
||||
0.25: Fix issue where if Bangle.js 2 got a GPS fix but no reported time, errors could be caused by the widget (fix #935)
|
||||
0.26: Multiple bugfixes
|
||||
0.27: Map drawing with light theme (fix #1023)
|
||||
|
|
|
@ -8,3 +8,6 @@ This app allows you to record a GPS track. It can run in background. The data ca
|
|||
|
||||
When you turn on recording, a widget badge that looks like a satellite will appear immediately at the top of the screen. However, the recording does not begin immediately. It usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). You will notice a blinking question mark at the lower left of the badge indicating currently getting a fix. The badge will change when a GPS fix is achieved and that is when the app actually starts writing data to the log file. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix.
|
||||
|
||||
## Viewing a track
|
||||
|
||||

|
||||
|
|