Merge branch 'espruino:master' into master
269
apps.json
|
@ -16,7 +16,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.40",
|
||||
"version": "0.41",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
@ -29,6 +29,24 @@
|
|||
],
|
||||
"sortorder": -10
|
||||
},
|
||||
{ "id": "ac_ac",
|
||||
"name": "A Configurable Analog Clock",
|
||||
"shortName":"Configurable Clock",
|
||||
"version":"0.03",
|
||||
"description": "AC-AC, a highly customizable analog clock with several clock faces, hands and complications to choose from",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": false,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"custom": "Customizer.html",
|
||||
"storage": [
|
||||
{"name":"ac_ac.app.js","url":"app.js"},
|
||||
{"name":"ac_ac.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hebrew_calendar",
|
||||
"name": "Hebrew Calendar",
|
||||
|
@ -77,7 +95,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.17",
|
||||
"version": "0.18",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -99,18 +117,20 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"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",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
"dependencies": {"messages":"app"},
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"android.app.js","url":"app.js"},
|
||||
{"name":"android.settings.js","url":"settings.js"},
|
||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"android.settings.json"}],
|
||||
"sortorder": -8
|
||||
},
|
||||
{
|
||||
|
@ -167,7 +187,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.40",
|
||||
"version": "0.41",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
@ -218,7 +238,7 @@
|
|||
{
|
||||
"id": "locale",
|
||||
"name": "Languages",
|
||||
"version": "0.14",
|
||||
"version": "0.15",
|
||||
"description": "Translations for different countries",
|
||||
"icon": "locale.png",
|
||||
"type": "locale",
|
||||
|
@ -307,7 +327,7 @@
|
|||
"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",
|
||||
"tags": "tool,system,android,widget,gadgetbridge",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"dependencies": {"notify":"type"},
|
||||
"readme": "README.md",
|
||||
|
@ -324,7 +344,7 @@
|
|||
"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": "",
|
||||
"tags": "tool,debug,gadgetbridge",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
|
@ -768,7 +788,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder (BETA)",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -1040,7 +1060,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -1351,6 +1371,22 @@
|
|||
{"name":"pparrot.img","url":"party-parrot-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hralarm",
|
||||
"name": "Heart rate alarm",
|
||||
"shortName":"HR Alarm",
|
||||
"version":"0.01",
|
||||
"description": "This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"hralarm.wid.js","url":"widget.js"},
|
||||
{"name":"hralarm.settings.js","url":"settings.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hrings",
|
||||
"name": "Hypno Rings",
|
||||
|
@ -1504,7 +1540,7 @@
|
|||
{
|
||||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
|
@ -1518,13 +1554,14 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Update (AGPS)",
|
||||
"version": "0.01",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"version": "0.03",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tool,outdoors,agps",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
|
@ -1717,17 +1754,18 @@
|
|||
{
|
||||
"id": "wohrm",
|
||||
"name": "Workout HRM",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "hrm,workout",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-workout-HRM-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"wohrm.app.js","url":"app.js"},
|
||||
{"name":"wohrm.settings.js","url":"settings.js"},
|
||||
{"name":"wohrm.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
@ -1893,13 +1931,15 @@
|
|||
{
|
||||
"id": "widhwt",
|
||||
"name": "Hand Wash Timer",
|
||||
"version": "0.01",
|
||||
"description": "Swipe your wrist over the watch face to start your personal Bangle.js hand wash timer for 35 sec. Start washing after the short buzz and stop after the long buzz.",
|
||||
"version": "0.02",
|
||||
"description": "On Bangle.js 1 swipe your wrist over the watch face to start your personal Bangle.js 1 hand wash timer. On Bangle.js2 the Pattern Launcher is recommended to start the timer. Start washing after the short buzz and stop after the long buzz 35sec. later.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"allow_emulator": true,
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widhwt.app.js","url":"app.js"},
|
||||
{"name":"widhwt.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
|
@ -2076,12 +2116,13 @@
|
|||
"id": "devstopwatch",
|
||||
"name": "Dev Stopwatch",
|
||||
"shortName": "Dev Stopwatch",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Stopwatch with 5 laps supported (cyclically replaced)",
|
||||
"icon": "app.png",
|
||||
"tags": "stopwatch,chrono,timer,chronometer",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"bangle1-dev-stopwatch-screenshot.png"}],
|
||||
"screenshots": [{"url":"bangle1-dev-stopwatch-screenshot.png"},{"url":"bangle2-dev-stopwatch-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"devstopwatch.app.js","url":"app.js"},
|
||||
|
@ -2254,6 +2295,20 @@
|
|||
{"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "run",
|
||||
"name": "Run",
|
||||
"version":"0.01",
|
||||
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors,gps",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"run.app.js","url":"app.js"},
|
||||
{"name":"run.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "banglerun",
|
||||
"name": "BangleRun",
|
||||
|
@ -2971,6 +3026,20 @@
|
|||
],
|
||||
"data": [{"wildcard":"accellog.?.csv"}]
|
||||
},
|
||||
{ "id": "accelgraph",
|
||||
"name": "Accelerometer Graph",
|
||||
"shortName":"Accel Graph",
|
||||
"version":"0.01",
|
||||
"description": "A simple app to draw a graph of data from the accelerometer on the screen",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,debug",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"accelgraph.app.js","url":"app.js"},
|
||||
{"name":"accelgraph.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cprassist",
|
||||
"name": "CPR Assist",
|
||||
|
@ -3794,7 +3863,7 @@
|
|||
{
|
||||
"id": "simplest",
|
||||
"name": "Simplest Clock",
|
||||
"version": "0.03",
|
||||
"version": "0.05",
|
||||
"description": "The simplest working clock, acts as a tutorial piece",
|
||||
"icon": "simplest.png",
|
||||
"screenshots": [{"url":"screenshot_simplest.png"}],
|
||||
|
@ -3900,8 +3969,8 @@
|
|||
"id": "qmsched",
|
||||
"name": "Quiet Mode Schedule and Widget",
|
||||
"shortName": "Quiet Mode",
|
||||
"version": "0.06",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.",
|
||||
"version": "0.07",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times, change theme and LCD options while Quiet Mode is active.",
|
||||
"icon": "app.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"}],
|
||||
|
@ -4215,7 +4284,7 @@
|
|||
"id": "pastel",
|
||||
"name": "Pastel Clock",
|
||||
"shortName": "Pastel",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||
"icon": "pastel.png",
|
||||
"dependencies": {"mylocation":"app", "widpedom":"app","weather":"app"},
|
||||
|
@ -4242,7 +4311,7 @@
|
|||
{
|
||||
"id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"version": "0.04",
|
||||
"version": "0.06",
|
||||
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
@ -4335,8 +4404,10 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"ffcniftya.app.js","url":"app.js"},
|
||||
{"name":"ffcniftya.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
{"name":"ffcniftya.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"ffcniftya.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"ffcniftya.json"}]
|
||||
},
|
||||
{
|
||||
"id": "ffcniftyb",
|
||||
|
@ -4422,7 +4493,7 @@
|
|||
"name": "Q Alarm and Timer",
|
||||
"shortName": "Q Alarm",
|
||||
"icon": "app.png",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Alarm and timer app with days of week and 'hard' option.",
|
||||
"tags": "tool,alarm,widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
|
@ -4495,7 +4566,7 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.09",
|
||||
"version":"0.12",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
|
@ -4805,7 +4876,7 @@
|
|||
{
|
||||
"id": "menuwheel",
|
||||
"name": "Wheel Menus",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"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",
|
||||
|
@ -5009,7 +5080,7 @@
|
|||
{
|
||||
"id": "coretemp",
|
||||
"name": "CoreTemp",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Display CoreTemp device sensor data",
|
||||
"icon": "coretemp.png",
|
||||
"type": "app",
|
||||
|
@ -5019,6 +5090,7 @@
|
|||
"storage": [
|
||||
{"name":"coretemp.wid.js","url":"widget.js"},
|
||||
{"name":"coretemp.app.js","url":"coretemp.js"},
|
||||
{"name":"coretemp.recorder.js","url":"recorder.js"},
|
||||
{"name":"coretemp.settings.js","url":"settings.js"},
|
||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
|
||||
{"name":"coretemp.boot.js","url":"boot.js"}
|
||||
|
@ -5043,7 +5115,7 @@
|
|||
{
|
||||
"id": "lapcounter",
|
||||
"name": "Lap Counter",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"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"}],
|
||||
|
@ -5078,10 +5150,10 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.03",
|
||||
"version":"0.05",
|
||||
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}],
|
||||
"dependencies": {"widpedom":"app"},
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
@ -5130,6 +5202,42 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "ftclock",
|
||||
"name": "Four Twenty Clock",
|
||||
"version": "0.02",
|
||||
"description": "A clock that tells when and where it's going to be 4:20 next",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot1.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"ftclock.app.js","url":"app.js"},
|
||||
{"name":"fourTwenty","url":"fourTwenty.js"},
|
||||
{"name":"fourTwentyTz","url":"fourTwentyTz.js"},
|
||||
{"name":"ftclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mmind",
|
||||
"name": "Classic Mind Game",
|
||||
"shortName":"Master Mind",
|
||||
"icon": "mmind.png",
|
||||
"version":"0.01",
|
||||
"description": "This is the classic game for masterminds",
|
||||
"screenshots": [{"url":"screenshot_mmind.png"}],
|
||||
"type": "app",
|
||||
"tags": "game",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"mmind.app.js","url":"mmind.app.js"},
|
||||
{"name":"mmind.img","url":"mmind.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "presentor",
|
||||
"name": "Presentor",
|
||||
"version": "3.0",
|
||||
|
@ -5289,7 +5397,7 @@
|
|||
{ "id": "colorful_clock",
|
||||
"name": "Colorful Analog Clock",
|
||||
"shortName":"Colorful Clock",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "a colorful analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
|
@ -5437,7 +5545,7 @@
|
|||
},
|
||||
{
|
||||
"id": "flipper",
|
||||
"name": "flipper",
|
||||
"name": "Flipper",
|
||||
"version": "0.01",
|
||||
"description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.",
|
||||
"readme":"README.md",
|
||||
|
@ -5465,5 +5573,92 @@
|
|||
{"name":"ruuviwatch.app.js","url":"ruuviwatch.app.js"},
|
||||
{"name":"ruuviwatch.img","url":"ruuviwatch.app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "limelight",
|
||||
"name": "Limelight",
|
||||
"version": "0.01",
|
||||
"description": "Simple analogue clock (with configurable fonts) based on the work of @Andreas_Rozek (Simple_Clock)",
|
||||
"icon": "limelight.png",
|
||||
"readme":"README.md",
|
||||
"screenshots": [{"url":"screenshot_limelight.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"limelight.app.js","url":"limelight.app.js"},
|
||||
{"name":"limelight.settings.js","url":"limelight.settings.js"},
|
||||
{"name":"limelight.img","url":"limelight.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "banglexercise",
|
||||
"name": "BanglExercise",
|
||||
"shortName":"BanglExercise",
|
||||
"version":"0.01",
|
||||
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "sport",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"banglexercise.app.js","url":"app.js"},
|
||||
{"name":"banglexercise.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"banglexercise.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"banglexercise.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widpa",
|
||||
"name": "Simple Pedometer",
|
||||
"shortName":"Simple Pedometer",
|
||||
"icon": "screenshot_widpa.png",
|
||||
"screenshots": [{"url":"screenshot_widpa.png"}],
|
||||
"version":"0.01",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in 12x16 font, requires firmware v2.11.21 or later",
|
||||
"tags": "widget,battery",
|
||||
"storage": [
|
||||
{"name":"widpa.wid.js","url":"widpa.wid.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widpb",
|
||||
"name": "Lato Pedometer",
|
||||
"shortName":"Lato Pedometer",
|
||||
"icon": "screenshot_widpb.png",
|
||||
"screenshots": [{"url":"screenshot_widpb.png"}],
|
||||
"version":"0.01",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in the Lato font, requires firmware v2.11.21 or later",
|
||||
"tags": "widget,battery",
|
||||
"storage": [
|
||||
{"name":"widpb.wid.js","url":"widpb.wid.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "timeandlife",
|
||||
"name": "Time and Life",
|
||||
"shortName":"Time and Lfie",
|
||||
"icon": "app.png",
|
||||
"version":"0.1",
|
||||
"description": "A simple watchface which displays the time when the screen is tapped and decays according to the rules of Conway's game of life.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"timeandlife.app.js","url":"app.js"},
|
||||
{"name":"timeandlife.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,890 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="width:100%; height:100%">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
<meta name="viewport" content="minimal-ui, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<style type="text/css">
|
||||
html {
|
||||
text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-o-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src 'self' 'unsafe-inline' 'unsafe-eval' file: https:;
|
||||
">
|
||||
<meta http-equiv="X-Content-Security-Policy" content="
|
||||
default-src 'self' 'unsafe-inline' 'unsafe-eval' file: https:;
|
||||
">
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<style>
|
||||
body { background:white; color:black }
|
||||
|
||||
table.centered td {
|
||||
text-align:center; vertical-align:top;
|
||||
}
|
||||
|
||||
label.Preview {
|
||||
display:inline-block; position:relative;
|
||||
}
|
||||
label.Preview > input[type="radio"] {
|
||||
display:none; position:relative;
|
||||
-webkit-appearance:none; -moz-appearance:none; appearance:none;
|
||||
width:0px; height:0px;
|
||||
}
|
||||
label.Preview > input[type="radio"] + img {
|
||||
display:inline-block; position:relative;
|
||||
width:88px; height:88px;
|
||||
margin:0px; padding:0px;
|
||||
border:solid 3px white;
|
||||
box-shadow:0px 0px 1px 1px lightgray;
|
||||
}
|
||||
label.Preview > input[type="radio"]:checked + img {
|
||||
border:solid 3px red;
|
||||
}
|
||||
|
||||
|
||||
.ColorPatch {
|
||||
display:inline-block; position:relative;
|
||||
-webkit-appearance:none; -moz-appearance:none; appearance:none;
|
||||
width:30px; height:30px; box-sizing:border-box;
|
||||
border:solid 3px white;
|
||||
margin:0px; padding:0px;
|
||||
box-shadow:inset 0px 0px 1px 1px black;
|
||||
vertical-align:top;
|
||||
}
|
||||
.ColorPatch:checked {
|
||||
border:solid 3px red;
|
||||
}
|
||||
|
||||
|
||||
label.ColorPatch {
|
||||
display:inline-block; position:relative;
|
||||
width:70px;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
}
|
||||
label.ColorPatch > input[type="radio"] {
|
||||
display:none; position:relative;
|
||||
-webkit-appearance:none; -moz-appearance:none; appearance:none;
|
||||
width:0px; height:0px;
|
||||
}
|
||||
label.ColorPatch > input[type="radio"] + span {
|
||||
display:inline-block; position:absolute;
|
||||
left:0px; top:0px; right:0px; bottom:0px;
|
||||
margin:0px; padding:0px;
|
||||
border:solid 3px white;
|
||||
box-shadow:inset 0px 0px 1px 1px black;
|
||||
font-size:16px; line-height:24px;
|
||||
text-align:center;
|
||||
}
|
||||
label.ColorPatch > input[type="radio"]:checked + span {
|
||||
border:solid 3px red;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
let ClockSize, ClockSizeURL
|
||||
let ClockFace, ClockFaceNumerals, ClockFaceDots, ClockFaceURL
|
||||
let ClockHands, SecondHand, ClockHandsURL, FillColor
|
||||
let ComplicationTL, ComplicationTLURL
|
||||
let ComplicationT, ComplicationTURL
|
||||
let ComplicationTR, ComplicationTRURL
|
||||
let ComplicationL, ComplicationLURL
|
||||
let ComplicationR, ComplicationRURL
|
||||
let ComplicationBL, ComplicationBLURL
|
||||
let ComplicationB, ComplicationBURL
|
||||
let ComplicationBR, ComplicationBRURL
|
||||
let Foreground, Background
|
||||
|
||||
/**** backupConfiguration ****/
|
||||
|
||||
function backupConfiguration () {
|
||||
let Configuration = {
|
||||
ClockSize, ClockSizeURL,
|
||||
ClockFace, ClockFaceNumerals, ClockFaceDots, ClockFaceURL,
|
||||
ClockHands, SecondHand, ClockHandsURL, FillColor,
|
||||
ComplicationTL, ComplicationTLURL,
|
||||
ComplicationT, ComplicationTURL,
|
||||
ComplicationTR, ComplicationTRURL,
|
||||
ComplicationL, ComplicationLURL,
|
||||
ComplicationR, ComplicationRURL,
|
||||
ComplicationBL, ComplicationBLURL,
|
||||
ComplicationB, ComplicationBURL,
|
||||
ComplicationBR, ComplicationBRURL,
|
||||
Foreground, Background
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem('ac_ac',JSON.stringify(Configuration))
|
||||
} catch (Signal) {
|
||||
console.error('could not backup clock configuration, reason',Signal)
|
||||
}
|
||||
}
|
||||
|
||||
/**** restoreConfiguration - warning: no input validations yet! ****/
|
||||
|
||||
function restoreConfiguration () {
|
||||
let Configuration = {}
|
||||
try {
|
||||
Configuration = JSON.parse(localStorage.getItem('ac_ac') || '')
|
||||
} catch (Signal) { /* nop */ }
|
||||
for (let Key in Configuration) {
|
||||
if (Configuration.hasOwnProperty(Key)) {
|
||||
if ((Key == null) || (typeof Configuration[Key] !== 'string')) {
|
||||
Configuration[Key] = ''
|
||||
} else {
|
||||
Configuration[Key] = Configuration[Key].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('input[name="clock-size"][value="' + Configuration.ClockSize + '"]').attr('checked','checked')
|
||||
$('#clock-size-custom-url').val(Configuration.ClockSizeURL)
|
||||
|
||||
$('input[name="clock-face"][value="' + Configuration.ClockFace + '"]').attr('checked','checked')
|
||||
$('input[name="clock-face-numerals"][value="' + Configuration.ClockFaceNumerals + '"]').attr('checked','checked')
|
||||
$('input[name="clock-face-dots"][value="' + Configuration.ClockFaceDots + '"]').attr('checked','checked')
|
||||
$('#clock-face-custom-url').val(Configuration.ClockFaceURL)
|
||||
|
||||
$('input[name="clock-hands"][value="' + Configuration.ClockHands + '"]').attr('checked','checked')
|
||||
$('input[name="fill-color"][value="' + Configuration.FillColor + '"]').attr('checked','checked')
|
||||
$('input[name="second-hand"][value="' + Configuration.SecondHand + '"]').attr('checked','checked')
|
||||
$('#clock-hands-custom-url').val(Configuration.ClockHandsURL)
|
||||
|
||||
$('#complication-tl').val(Configuration.ComplicationTL)
|
||||
$('#complication-tl-custom-url').val(Configuration.ComplicationTLURL)
|
||||
|
||||
$('#complication-t').val(Configuration.ComplicationT)
|
||||
$('#complication-t-custom-url').val(Configuration.ComplicationTURL)
|
||||
|
||||
$('#complication-tr').val(Configuration.ComplicationTR)
|
||||
$('#complication-tr-custom-url').val(Configuration.ComplicationTRURL)
|
||||
|
||||
$('#complication-l').val(Configuration.ComplicationL)
|
||||
$('#complication-l-custom-url').val(Configuration.ComplicationLURL)
|
||||
|
||||
$('#complication-r').val(Configuration.ComplicationR)
|
||||
$('#complication-r-custom-url').val(Configuration.ComplicationRURL)
|
||||
|
||||
$('#complication-bl').val(Configuration.ComplicationBL)
|
||||
$('#complication-bl-custom-url').val(Configuration.ComplicationBLURL)
|
||||
|
||||
$('#complication-b').val(Configuration.ComplicationB)
|
||||
$('#complication-b-custom-url').val(Configuration.ComplicationBURL)
|
||||
|
||||
$('#complication-br').val(Configuration.ComplicationBR)
|
||||
$('#complication-br-custom-url').val(Configuration.ComplicationBRURL)
|
||||
|
||||
$('input[name="foreground"][value="' + Configuration.Foreground + '"]').attr('checked','checked')
|
||||
$('input[name="background"][value="' + Configuration.Background + '"]').attr('checked','checked')
|
||||
}
|
||||
restoreConfiguration();
|
||||
|
||||
/**** retrieveInputs ****/
|
||||
|
||||
function retrieveInputs () {
|
||||
ClockSize = $('input[name="clock-size"]:checked').val()
|
||||
ClockSizeURL = ($('#clock-size-custom-url').val() || '').trim()
|
||||
|
||||
ClockFace = $('input[name="clock-face"]:checked').val()
|
||||
ClockFaceNumerals = $('input[name="clock-face-numerals"]:checked').val()
|
||||
ClockFaceDots = $('input[name="clock-face-dots"]:checked').val()
|
||||
ClockFaceURL = ($('#clock-face-custom-url').val() || '').trim()
|
||||
|
||||
ClockHands = $('input[name="clock-hands"]:checked').val()
|
||||
FillColor = $('input[name="fill-color"]:checked').val()
|
||||
SecondHand = $('input[name="second-hand"]:checked').val()
|
||||
ClockHandsURL = ($('#clock-hands-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationTL = $('#complication-tl').val()
|
||||
ComplicationTLURL = ($('#complication-tl-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationT = $('#complication-t').val()
|
||||
ComplicationTURL = ($('#complication-t-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationTR = $('#complication-tr').val()
|
||||
ComplicationTRURL = ($('#complication-tr-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationL = $('#complication-l').val()
|
||||
ComplicationLURL = ($('#complication-l-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationR = $('#complication-r').val()
|
||||
ComplicationRURL = ($('#complication-r-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationBL = $('#complication-bl').val()
|
||||
ComplicationBLURL = ($('#complication-bl-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationB = $('#complication-b').val()
|
||||
ComplicationBURL = ($('#complication-b-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationBR = $('#complication-br').val()
|
||||
ComplicationBRURL = ($('#complication-br-custom-url').val() || '').trim()
|
||||
|
||||
Foreground = $('input[name="foreground"]:checked').val()
|
||||
Background = $('input[name="background"]:checked').val()
|
||||
}
|
||||
retrieveInputs()
|
||||
|
||||
/**** validateInputs ****/
|
||||
|
||||
function validateInputs () {
|
||||
function withError (Message) {
|
||||
showMessage(Message)
|
||||
$('#UploadButton').attr('disabled',true)
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case (ClockSize === 'custom') && (ClockSizeURL === ''):
|
||||
return withError('please enter the URL of your custom "Clock Size Calculator"')
|
||||
|
||||
case (ClockFace === 'custom') && (ClockFaceURL === ''):
|
||||
return withError('please enter the URL of your custom clock face')
|
||||
|
||||
case (ClockHands === 'custom') && (ClockHandsURL === ''):
|
||||
return withError('please enter the URL of your custom clock hands')
|
||||
|
||||
case (ComplicationTL === 'custom') && (ComplicationTLURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the top-left corner')
|
||||
case (ComplicationT === 'custom') && (ComplicationTURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the top edge')
|
||||
case (ComplicationTR === 'custom') && (ComplicationTRURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the top-right corner')
|
||||
case (ComplicationL === 'custom') && (ComplicationLURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the left edge')
|
||||
case (ComplicationR === 'custom') && (ComplicationRURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the right edge')
|
||||
case (ComplicationBL === 'custom') && (ComplicationBLURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the bottom-left corner')
|
||||
case (ComplicationB === 'custom') && (ComplicationBURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the bottom edge')
|
||||
case (ComplicationBR === 'custom') && (ComplicationBRURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the bottom-right corner')
|
||||
}
|
||||
|
||||
hideMessage()
|
||||
$('#UploadButton').removeAttr('disabled')
|
||||
}
|
||||
|
||||
/**** hide/showMesssage ****/
|
||||
|
||||
function hideMessage () { $('#MessageView').hide() }
|
||||
|
||||
function showMessage (Message) {
|
||||
$('#MessageView').text(Message).show()
|
||||
}
|
||||
|
||||
/**** createAndUploadApp ****/
|
||||
|
||||
function createAndUploadApp () {
|
||||
function WidgetsOnBackground () {
|
||||
return (
|
||||
ClockSize === 'smart'
|
||||
? "require('https://raw.githubusercontent.com/rozek/banglejs-2-widgets-on-background/main/drawWidgets.js');"
|
||||
: ''
|
||||
)
|
||||
}
|
||||
|
||||
function chosenClockSize () {
|
||||
switch (ClockSize) {
|
||||
case 'simple': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clock-size/main/ClockSize.js')"
|
||||
case 'smart': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-smart-clock-size/main/ClockSize.js')"
|
||||
case 'custom': return "require('" + ClockSizeURL + "')"
|
||||
}
|
||||
}
|
||||
|
||||
function chosenClockFace () {
|
||||
switch (ClockFace) {
|
||||
case 'none': return "undefined"
|
||||
case 'four-fold': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-four-fold-clock-face/main/ClockFace.js')"
|
||||
case 'twelve-fold': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-twelve-fold-clock-face/main/ClockFace.js')"
|
||||
case 'rainbow': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-rainbow-clock-face/main/ClockFace.js')"
|
||||
case 'custom': return "require('" + ClockFaceURL + "')"
|
||||
}
|
||||
}
|
||||
|
||||
function chosenClockHands () {
|
||||
switch (ClockHands) {
|
||||
case 'simple': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-simpled-clock-hands/main/ClockHands.js')"
|
||||
case 'rounded': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-rounded-clock-hands/main/ClockHands.js')"
|
||||
case 'hollow': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-hollow-clock-hands/main/ClockHands.js')"
|
||||
case 'custom': return "require('" + ClockHandsURL + "')"
|
||||
}
|
||||
}
|
||||
|
||||
function chosenComplication (Complication, customURL) {
|
||||
switch (Complication) {
|
||||
case 'none': return "undefined"
|
||||
case 'date': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-date-complication/main/Complication.js')"
|
||||
case 'weekday': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-weekday-complication/main/Complication.js')"
|
||||
case 'calendar-week': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-calendar-week-complication/main/Complication.js')"
|
||||
case 'moon-phase': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-moon-phase-complication/main/Complication.js')"
|
||||
case 'custom': return "require('" + customURL + "')"
|
||||
}
|
||||
}
|
||||
function chosenComplicationAt (Position) {
|
||||
switch (Position) {
|
||||
case 'tl': return chosenComplication(ComplicationTL, ComplicationTLURL)
|
||||
case 't': return chosenComplication(ComplicationT, ComplicationTURL)
|
||||
case 'tr': return chosenComplication(ComplicationTR, ComplicationTRURL)
|
||||
case 'l': return chosenComplication(ComplicationL, ComplicationLURL)
|
||||
case 'r': return chosenComplication(ComplicationR, ComplicationRURL)
|
||||
case 'bl': return chosenComplication(ComplicationBL, ComplicationBLURL)
|
||||
case 'b': return chosenComplication(ComplicationB, ComplicationBURL)
|
||||
case 'br': return chosenComplication(ComplicationBR, ComplicationBRURL)
|
||||
}
|
||||
}
|
||||
|
||||
function chosenColor (ColorChoice) {
|
||||
return (ColorChoice === 'none' ? 'undefined' : "'" + ColorChoice + "'")
|
||||
}
|
||||
|
||||
function chosenForeground () { return chosenColor(Foreground) }
|
||||
function chosenBackground () { return chosenColor(Background) }
|
||||
function chosenSecondHand () { return chosenColor(SecondHand) }
|
||||
function chosenFillColor () { return chosenColor(FillColor) }
|
||||
|
||||
function chosenNumerals () { return (ClockFaceNumerals === 'roman' ? 'true' : 'false') }
|
||||
function chosenDots () { return (ClockFaceDots === 'with-dots' ? 'true' : 'false') }
|
||||
|
||||
let AppSource = `
|
||||
${WidgetsOnBackground()}
|
||||
|
||||
let Clockwork = require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clockwork/main/Clockwork.js');
|
||||
|
||||
Clockwork.windUp({
|
||||
size: ${chosenClockSize()},
|
||||
background:null,
|
||||
face: ${chosenClockFace()},
|
||||
hands: ${chosenClockHands()},
|
||||
complications:{
|
||||
tl:${chosenComplicationAt('tl')},
|
||||
t: ${chosenComplicationAt('t')},
|
||||
tr:${chosenComplicationAt('tr')},
|
||||
l: ${chosenComplicationAt('l')},
|
||||
r: ${chosenComplicationAt('r')},
|
||||
bl:${chosenComplicationAt('bl')},
|
||||
b: ${chosenComplicationAt('b')},
|
||||
br:${chosenComplicationAt('br')},
|
||||
}
|
||||
},{
|
||||
Foreground: ${chosenForeground()},
|
||||
Background: ${chosenBackground()},
|
||||
Seconds: ${chosenSecondHand()},
|
||||
withDots: ${chosenDots()},
|
||||
romanNumerals:${chosenNumerals()},
|
||||
FillColor: ${chosenFillColor()}
|
||||
});
|
||||
`
|
||||
console.log('the configured AC-AC app looks as follows:')
|
||||
console.log(AppSource)
|
||||
|
||||
backupConfiguration()
|
||||
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:'ac_ac.app.js', url:'app.js', content:AppSource},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
/**** register event handlers ****/
|
||||
|
||||
function retrieveAndValidateInputs () {
|
||||
retrieveInputs ()
|
||||
validateInputs ()
|
||||
}
|
||||
|
||||
$('input[type="radio"]').on('change',retrieveAndValidateInputs)
|
||||
$('input[type="url"]'). on('change',retrieveAndValidateInputs)
|
||||
$('select'). on('change',retrieveAndValidateInputs)
|
||||
$('#UploadButton').on('click',createAndUploadApp)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>
|
||||
Please customize your analog clock for the Bangle.js 2 according to your needs.
|
||||
When finished, click on "Upload" at the bottom of this form.
|
||||
</p><p>
|
||||
(Pressing "Upload" will also backup your current configuration so that you
|
||||
won't have to enter the same settings over and over again when you come back
|
||||
to this page later)
|
||||
</p>
|
||||
|
||||
<h3>Clock Size Calculation</h3>
|
||||
|
||||
<p>
|
||||
Click on the desired clock size calculator (if you installed some widgets
|
||||
on your Bangle.js 2, the smart one may produce larger clock faces than the
|
||||
simple one):
|
||||
</p><p>
|
||||
<table class="centered"><tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-size" value="simple">
|
||||
<img src="simpleClockSize.png"/>
|
||||
</label><br>
|
||||
simple
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-size" value="smart" checked>
|
||||
<img src="smartClockSize.png"/>
|
||||
</label><br>
|
||||
smart
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-size" value="custom">
|
||||
<img src="custom.png"/>
|
||||
</label><br>
|
||||
(custom)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p><p>
|
||||
If you prefer a "custom" clock size calculator, please enter the URL
|
||||
of its JavaScript module below:
|
||||
</p><p>
|
||||
custom URL: <input type="url" id="clock-size-custom-url" size="50">
|
||||
</p>
|
||||
|
||||
<h3>Clock Face</h3>
|
||||
|
||||
<p>
|
||||
Click on the desired clock face:
|
||||
</p><p>
|
||||
<table class="centered"><tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="none" checked>
|
||||
<img src="none.png"/>
|
||||
</label><br>
|
||||
none
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="four-fold">
|
||||
<img src="fourfoldClockFace.png"/>
|
||||
</label><br>
|
||||
four-fold
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="twelve-fold">
|
||||
<img src="twelvefoldClockFace.png"/>
|
||||
</label><br>
|
||||
twelve-fold
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="rainbow">
|
||||
<img src="RainbowClockFace.png"/>
|
||||
</label><br>
|
||||
"rainbow"<br>colored
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="custom">
|
||||
<img src="custom.png"/>
|
||||
</label><br>
|
||||
(custom)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p><p>
|
||||
Clock faces are drawn in the configured foreground and background colors
|
||||
(you may select them at the end of this form)
|
||||
</p><p>
|
||||
"Four-fold" clock faces may draw indian-arabic or roman numerals. Which do you prefer?
|
||||
</p><p>
|
||||
<input type="radio" name="clock-face-numerals" value="indian" checked> indian-arabic (3, 6, 9, 12)<br>
|
||||
<input type="radio" name="clock-face-numerals" value="roman"> roman (III, VI, IX, XII)
|
||||
</p><p>
|
||||
The "twelve-fold" and "rainbow"-colored faces may be drawn with or without
|
||||
dots marking the position of every minute. Which variant do you prefer?
|
||||
</p><p>
|
||||
<input type="radio" name="clock-face-dots" value="without-dots" checked> without dots <br>
|
||||
<input type="radio" name="clock-face-dots" value="with-dots"> with dots
|
||||
</p><p>
|
||||
If you prefer a "custom" clock face, please enter the URL
|
||||
of its JavaScript module below:
|
||||
</p><p>
|
||||
custom URL: <input type="url" id="clock-face-custom-url" size="50">
|
||||
</p>
|
||||
|
||||
<h3>Clock Hands</h3>
|
||||
|
||||
<p>
|
||||
Click on the desired clock hands:
|
||||
</p><p>
|
||||
<table class="centered"><tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="simple">
|
||||
<img src="simpleClockHands.png"/>
|
||||
</label><br>
|
||||
simple
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="rounded" checked>
|
||||
<img src="roundedClockHands.png"/>
|
||||
</label><br>
|
||||
rounded
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="hollow">
|
||||
<img src="hollowClockHands.png"/>
|
||||
</label><br>
|
||||
hollow
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="custom">
|
||||
<img src="custom.png"/>
|
||||
</label><br>
|
||||
(custom)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p><p>
|
||||
Clock hands are drawn in the configured foreground and background colors
|
||||
(you may select them at the end of this form)
|
||||
</p><p>
|
||||
Hollow clock hands may optionally be filled with a given color. If you have
|
||||
chosen hollow hands, please specify the desired fill mode and color below:
|
||||
</p><p>
|
||||
<b>Hollow Hand Fill Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="fill-color" value="none" checked/>
|
||||
<span>none</span>
|
||||
</label>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="fill-color" value="Theme"/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="fill-color" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="fill-color" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="fill-color" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="fill-color" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="fill-color" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="fill-color" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="fill-color" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="fill-color" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
Additionally, all clock hands may be drawn with or without second hands.
|
||||
If you want them to be drawn, please click on their desired color below
|
||||
(or choose "themed" to use your Bangle's configured theme) - if not, just
|
||||
select "none":
|
||||
</p><p>
|
||||
<b>Second Hand Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="second-hand" value="none" checked/>
|
||||
<span>none</span>
|
||||
</label>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="second-hand" value="Theme"/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="second-hand" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="second-hand" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="second-hand" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="second-hand" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="second-hand" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="second-hand" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="second-hand" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="second-hand" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
If you prefer "custom" clock hands, please enter the URL
|
||||
of their JavaScript module below:
|
||||
</p><p>
|
||||
custom URL: <input type="url" id="clock-hands-custom-url" size="50">
|
||||
</p>
|
||||
|
||||
<h3>Complications</h3>
|
||||
|
||||
<p>
|
||||
Complications are small displays for additional information. If you want
|
||||
one or multiple complications to be added to your clock, you'll have to
|
||||
specify which one to be loaded and where it should be placed.
|
||||
</p><p>
|
||||
Up to 6 possible positions exist (top-left, top-right, left, right,
|
||||
bottom-left and bottom-right). Alternatively, the positions "top-left" and
|
||||
"top-right" may be traded for a slightly larger complication at position
|
||||
"top" or "bottom-left" and "bottom-right" for one at the "bottom":
|
||||
</p>
|
||||
<img src="smallPlaceholders.png" width="88" height="88"/>
|
||||
<img src="largePlaceholders.png" width="88" height="88"/>
|
||||
<p>
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td colspan="3"><b>top-left:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-tl">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-tl-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>top:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-t">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-t-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>top-right:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-tr">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-tr-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>left:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-l">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-l-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>right:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-r">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-r-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>bottom-left:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-bl">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-bl-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>bottom:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-b">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-b-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>bottom-right:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-br">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-br-custom-url" size="50"></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p>
|
||||
|
||||
<h3>Settings</h3>
|
||||
|
||||
<p>
|
||||
Color faces, hands and complications are often drawn using configurable
|
||||
foreground and background colors.
|
||||
</p><p>
|
||||
Here you may specify these colors. Click on a color to select it - or on
|
||||
"themed" if you want the clock to use the currently configured theme on
|
||||
your Bangle.js 2:
|
||||
</p><p>
|
||||
<b>Background Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="background" value="Theme" checked/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="background" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="background" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="background" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="background" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="background" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="background" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="background" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="background" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
<b>Foreground Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="foreground" value="Theme" checked/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="foreground" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="foreground" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="foreground" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="foreground" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="foreground" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="foreground" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="foreground" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="foreground" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
When you are satisfied with your configuration, just click on "Upload" in
|
||||
order to generate the specified clock and upload it to your Bangle.js 2:
|
||||
</p>
|
||||
<p id="MessageView" style="display:none; color:red"></p>
|
||||
|
||||
<button id="UploadButton">Upload</button>
|
||||
</p><p>
|
||||
This application is based on the author's
|
||||
<a href="https://github.com/rozek/banglejs-2-analog-clock-construction-kit">Analog Clock Construction Kit (ACCK)</a>.
|
||||
If you need a different "clockwork", clock size calculation or clock face,
|
||||
or specific clock hands or complications, just follow the link to learn how to
|
||||
implement your own clock parts.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# AC-AC - A Configurable Analog Clock #
|
||||
|
||||
This app implements an analog clock with various faces, hands and complications
|
||||
to choose from before uploading to a Bangle.js 2.
|
||||
|
||||
It is based on the [Analog Clock Construction Kit (ACCK)](https://github.com/rozek/banglejs-2-analog-clock-construction-kit)
|
||||
and makes most of the currently implemented parts available with a few mouse
|
||||
clicks - just click on "Upload" and you will be directed to a web form where
|
||||
you compose your very own, personal analog clock.
|
||||
|
||||
You currently have the choice between
|
||||
|
||||
* 2 different clock sizes,
|
||||
* 4 different clock faces,
|
||||
* 3 different clock hands and
|
||||
* 4 different complications
|
||||
|
||||
Alternatively, you may specify the GitHub URL of ACCK compatible modules for
|
||||
external clock sizes, faces, hands or complications.
|
||||
|
||||
Additionally, you may use the currently configured global theme or configure
|
||||
your own colors for clock fore- and background and second hands.
|
||||
|
||||
Consequently, even without external modules you already have the choice between
|
||||
102144 combinations!
|
||||
|
||||
<!--
|
||||
1 + (8 Fg colors * 7 Bg colors) * 2 sizes * 4(7) faces * 3(4) hands *
|
||||
8 positions * 4 complications (w/o placeholder)
|
||||
-->
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgn/ABH+AQPvBpIAI/n8/3f5/PCp/v9oHF7w1CABffGxAYMH4f9z/514YDCxW/O4gFBxwHD/ZEL7/9GgX8GwQLCBQQXH/uP/Hf/2N44IBAgIXJ7oaD/3v/3uAYIIB9wQGAA2+/iRG5oSIM4f+1nrPYgAB3aHIAC77QYYRoCAAP676ICABXYFIntDoPf3+PC5f+BoPOX4vPNBn7IogEB/eu3QXC9wNEAAeKBIP+dgbSCDYMwgEApQVEygPCeRH8iAWBAAMHPwXDgoRGAonACwYABgN5uMAC4q8GC4U0DQsAggRF9gXFgggB/2hC4kdVAQCBVAX7xwXCVAnGCwUadAeeDYfr7IhEAAf93e+A4gpB9yRB/mqcgndRgQAHzqRE1gEC/KoCjLZEsgCB9evO4gOC/RyEgqdC2KnFO4S/KgFYsC/Ga5EBs1AX5bXHgx1C2YXEnp7GCARgB4AfE64WCnawFCgf9VAK/G/3M7zWDz4PF/maXJIAD7D8EVAP85QXN3OP/42DfoQXN/wvE/ySGABa8FAC37AgepVwQ9E1SfBAAJIEAAnrBQ39xgwJ7pRHFQX+3QECCAbyG9bPDzwXC9QMBdgQXIAAf41wEC5pLCJJBcF9fZQ5IAGYYn81q7RJQwWC/wXM9/tA4veCxooDIAPv55PEABwpB97rDAAw"))
|
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,2 @@
|
|||
let Clockwork = require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clockwork/main/Clockwork.js');
|
||||
Clockwork.windUp();
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA/4AB304ief85L/ABNVAAwKCgILHoALBgoLHqALOrVVr4BEBZIFBBYiaCAAPq2oLQEYlqF5VrBZWnBZWvBZNWz4LGBoQLHJ4O///6v/1BZHa/4LFLYOlr9pR49r1ILJ09qr4ZBBY2vrWdBY5PBq2uyoLIquqBY5bBKoZTFLYILJJ4STDBY77IJ4QLUJ4QLU1QAE0oLPqoAGBZ0BBY9ABYMABY4KCAH4AGA="))
|
|
@ -0,0 +1,24 @@
|
|||
Bangle.loadWidgets();
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
var R = Bangle.appRect;
|
||||
|
||||
var x = 0;
|
||||
var last;
|
||||
|
||||
function getY(v) {
|
||||
return (R.y+R.y2 + v*R.h/2)/2;
|
||||
}
|
||||
Bangle.on('accel', a => {
|
||||
g.reset();
|
||||
if (last) {
|
||||
g.setColor("#f00").drawLine(x-1,getY(last.x),x,getY(a.x));
|
||||
g.setColor("#0f0").drawLine(x-1,getY(last.y),x,getY(a.y));
|
||||
g.setColor("#00f").drawLine(x-1,getY(last.z),x,getY(a.z));
|
||||
}
|
||||
last = a;x++;
|
||||
if (x>=g.getWidth()) {
|
||||
x = 1;
|
||||
g.clearRect(R);
|
||||
}
|
||||
});
|
After Width: | Height: | Size: 944 B |
After Width: | Height: | Size: 3.6 KiB |
|
@ -4,3 +4,4 @@
|
|||
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
|
||||
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# Android Integration
|
||||
|
||||
This app allows your Bangle.js to receive notifications [from the Gadgetbridge app on Android](http://www.espruino.com/Gadgetbridge)
|
||||
|
||||
See [this link](http://www.espruino.com/Gadgetbridge) for notes on how to install
|
||||
the Android app (and how it works).
|
||||
|
||||
It requires the `Messages` app on Bangle.js (which should be automatically installed) to
|
||||
display any notifications that are received.
|
||||
|
||||
## Settings
|
||||
|
||||
You can access the settings menu either from the `Android` icon in the launcher,
|
||||
or from `App Settings` in the `Settings` menu.
|
||||
|
||||
It contains:
|
||||
|
||||
* `Connected` - shows whether there is an active Bluetooth connection or not
|
||||
* `Find Phone` - opens a submenu where you can activate the `Find Phone` functionality
|
||||
of Gadgetbridge - making your phone make noise so you can find it.
|
||||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||
keep any messages it has received, or should it delete them?
|
||||
* `Messages` - launches the messages app, showing a list of messages
|
||||
|
||||
## How it works
|
||||
|
||||
Gadgetbridge on Android connects to Bangle.js, and sends commands over the
|
||||
BLE UART connection. These take the form of `GB({ ... JSON ... })\n` - so they
|
||||
call a global function called `GB` which then interprets the JSON.
|
||||
|
||||
Responses are sent back to Gadgetbridge simply as one line of JSON.
|
||||
|
||||
More info on message formats on http://www.espruino.com/Gadgetbridge
|
||||
|
||||
## Testing
|
||||
|
||||
Bangle.js can only hold one connection open at a time, so it's hard to see
|
||||
if there are any errors when handling Gadgetbridge messages.
|
||||
|
||||
However you can:
|
||||
|
||||
* Use the `Gadgetbridge Debug` app on Bangle.js to display/log the messages received from Gadgetbridge
|
||||
* Connect with the Web IDE and manually enter the Gadgetbridge messages on the left-hand side to
|
||||
execute them as if they came from Gadgetbridge, for instance:
|
||||
|
||||
```
|
||||
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
|
||||
```
|
|
@ -4,6 +4,7 @@
|
|||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
|
@ -51,7 +52,8 @@
|
|||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
if (!settings.keep)
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
|
@ -68,4 +70,6 @@
|
|||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
// remove settings object so it's not taking up RAM
|
||||
delete settings;
|
||||
})();
|
||||
|
|
|
@ -2,17 +2,29 @@
|
|||
function gb(j) {
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
}
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
function updateSettings() {
|
||||
require("Storage").writeJSON("android.settings.json", settings);
|
||||
}
|
||||
var mainmenu = {
|
||||
"" : { "title" : "Android" },
|
||||
"< Back" : back,
|
||||
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : "Find Phone" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
"On" : _=>gb({t:"findPhone",n:true}),
|
||||
"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
|
||||
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
}),
|
||||
"Messages" : ()=>load("messages.app.js")
|
||||
/*LANG*/"Keep Msgs" : {
|
||||
value : !!settings.keep,
|
||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
settings.keep = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>load("messages.app.js")
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
@ -2,3 +2,9 @@
|
|||
0.02: Load widgets after setUI so widclk knows when to hide
|
||||
0.03: Clock now shows day of week under date.
|
||||
0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too.
|
||||
0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off)
|
||||
when weekday name "Off": week #:<num>
|
||||
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
|
||||
0.06: fixes #1271 - wrong settings name
|
||||
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
|
||||
week is buffered until date or timezone changes
|
|
@ -40,8 +40,10 @@ The main menu contains several settings covering Anton clock in general.
|
|||
* **Show Weekday** - Weekday is shown in the time presentation without seconds.
|
||||
Weekday name depends on the current locale.
|
||||
If seconds are shown, the weekday is never shown as there is not enough space on the watch face.
|
||||
* **Uppercase** - Weekday name and month name in the long format are converted to upper case letters.
|
||||
This can improve readability.
|
||||
* **Show CalWeek** - Week-number (ISO-8601) is shown. (default: Off)
|
||||
If "Show Weekday" is "Off" displays the week-number as "week #<num>".
|
||||
If "Show Weekday" is "On" displays "weekday name short" with " #<num>" .
|
||||
If seconds are shown, the week number is never shown as there is not enough space on the watch face.
|
||||
* **Vector font** - Use the built-in vector font for dates and weekday.
|
||||
This can improve readability.
|
||||
Otherwise, a scaled version of the built-in 6x8 pixels font is used.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clock with large digits using the "Anton" bold font
|
||||
|
||||
var SETTINGSFILE = "antonclk.json";
|
||||
const SETTINGSFILE = "antonclk.json";
|
||||
|
||||
Graphics.prototype.setFontAnton = function(scale) {
|
||||
// Actual height 69 (68 - 0)
|
||||
|
@ -19,6 +19,7 @@ var secondsWithColon;
|
|||
var dateOnMain;
|
||||
var dateOnSecs;
|
||||
var weekDay;
|
||||
var calWeek;
|
||||
var upperCase;
|
||||
var vectorFont;
|
||||
|
||||
|
@ -27,32 +28,33 @@ var drawTimeout;
|
|||
var queueMillis = 1000;
|
||||
var secondsScreen = true;
|
||||
|
||||
var isBangle1 = (g.getWidth() == 240);
|
||||
var isBangle1 = (process.env.HWVERSION == 1);
|
||||
|
||||
/* For development purposes
|
||||
//For development purposes
|
||||
/*
|
||||
require('Storage').writeJSON(SETTINGSFILE, {
|
||||
secondsMode: "Always", // "Never", "Unlocked", "Always"
|
||||
secondsMode: "Unlocked", // "Never", "Unlocked", "Always"
|
||||
secondsColoured: true,
|
||||
secondsWithColon: true,
|
||||
dateOnMain: "Long", // "Short", "Long", "ISO8601"
|
||||
dateOnSecs: "Year", // "No", "Year", "Weekday", LEGACY: true/false
|
||||
weekDay: true,
|
||||
calWeek: true,
|
||||
upperCase: true,
|
||||
vectorFont: true,
|
||||
});
|
||||
/* */
|
||||
*/
|
||||
|
||||
/* OR (also for development purposes)
|
||||
// OR (also for development purposes)
|
||||
/*
|
||||
require('Storage').erase(SETTINGSFILE);
|
||||
/* */
|
||||
|
||||
// Helper method for loading the settings
|
||||
function def(value, def) {
|
||||
return (value !== undefined ? value : def);
|
||||
}
|
||||
*/
|
||||
|
||||
// Load settings
|
||||
function loadSettings() {
|
||||
// Helper function default setting
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
secondsMode = def(settings.secondsMode, "Never");
|
||||
secondsColoured = def(settings.secondsColoured, true);
|
||||
|
@ -60,6 +62,7 @@ function loadSettings() {
|
|||
dateOnMain = def(settings.dateOnMain, "Long");
|
||||
dateOnSecs = def(settings.dateOnSecs, "Year");
|
||||
weekDay = def(settings.weekDay, true);
|
||||
calWeek = def(settings.calWeek, false);
|
||||
upperCase = def(settings.upperCase, true);
|
||||
vectorFont = def(settings.vectorFont, false);
|
||||
|
||||
|
@ -99,6 +102,24 @@ function isoStr(date) {
|
|||
return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).substr(-2) + "-" + ("0" + date.getDate()).substr(-2);
|
||||
}
|
||||
|
||||
var calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested)
|
||||
function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
|
||||
dateNoTime = date; dateNoTime.setHours(0,0,0,0);
|
||||
if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2];
|
||||
calWeekBuffer[0] = date.getTimezoneOffset();
|
||||
calWeekBuffer[1] = dateNoTime;
|
||||
var tdt = new Date(date.valueOf());
|
||||
var dayn = (date.getDay() + 6) % 7;
|
||||
tdt.setDate(tdt.getDate() - dayn + 3);
|
||||
var firstThursday = tdt.valueOf();
|
||||
tdt.setMonth(0, 1);
|
||||
if (tdt.getDay() !== 4) {
|
||||
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
|
||||
}
|
||||
calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000);
|
||||
return calWeekBuffer[2];
|
||||
}
|
||||
|
||||
function doColor() {
|
||||
return !isBangle1 && !Bangle.isLocked() && secondsColoured;
|
||||
}
|
||||
|
@ -169,11 +190,17 @@ function draw() {
|
|||
else
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(dateStr, x, y);
|
||||
if (weekDay) {
|
||||
var dowStr = require("locale").dow(date);
|
||||
if (calWeek || weekDay) {
|
||||
var dowcwStr = "";
|
||||
if (calWeek)
|
||||
dowcwStr = " #" + ("0" + ISO8601calWeek(date)).substring(-2);
|
||||
if (weekDay)
|
||||
dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort #<calWeek> e.g. Mon #01
|
||||
else //week #01
|
||||
dowcwStr = /*LANG*/"week" + dowcwStr;
|
||||
if (upperCase)
|
||||
dowStr = dowStr.toUpperCase();
|
||||
g.drawString(dowStr, x, y + (vectorFont ? 26 : 16));
|
||||
dowcwStr = dowcwStr.toUpperCase();
|
||||
g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,14 @@
|
|||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Show CalWeek": {
|
||||
value: (settings.calWeek !== undefined ? settings.calWeek : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.calWeek = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Uppercase": {
|
||||
value: (settings.upperCase !== undefined ? settings.upperCase : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Update to work with Bangle.js 2
|
||||
0.03: Select GNSS systems to use for Bangle.js 2
|
||||
|
|
|
@ -8,34 +8,72 @@
|
|||
<p>GPS can take a long time (~5 minutes) to get an accurate position the first time it is used.
|
||||
AGPS uploads a few hints to the GPS receiver about satellite positions that allow it
|
||||
to get a faster, more accurate fix - however they are only valid for a short period of time.</p>
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1d"><i class="form-icon"></i> 1 day (8kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="2d" checked><i class="form-icon"></i> 2 days (14kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
<div id="banglejs1-info" style="display:none">
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1d"><i class="form-icon"></i> 1 day (8kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="2d" checked><i class="form-icon"></i> 2 days (14kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<div id="banglejs2-info" style="display:none">
|
||||
<p>Using fewer GNSS systems may decrease the time to fix. (If unsure, select only GPS)</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Select which GNSS system you want.</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="3"><i class="form-icon"></i> GPS+BDS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="4"><i class="form-icon"></i> GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="5"><i class="form-icon"></i> GPS+GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> BDS+GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> GPS+BDS+GLONASS
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p id="upload-wrap" style="display:none">Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
var isB1; // is Bangle.js 1?
|
||||
var isB2; // is Bangle.js 2?
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
var url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
var url;
|
||||
if (isB1) {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
}
|
||||
if (isB2) {
|
||||
url = "https://www.espruino.com/agps/casic.base64";
|
||||
}
|
||||
console.log("Sending...");
|
||||
//var text = document.getElementById("agpsperiod").value;
|
||||
get(url, function(b64) {
|
||||
|
@ -48,6 +86,8 @@
|
|||
});
|
||||
});
|
||||
|
||||
// =================================================== Bangle.js 1 UBLOX
|
||||
|
||||
function UBX_CMD(cmd) {
|
||||
var d = [0xB5,0x62]; // sync chars
|
||||
d = d.concat(cmd);
|
||||
|
@ -79,13 +119,40 @@
|
|||
return UBX_CMD([].slice.call(a));
|
||||
}
|
||||
|
||||
// =================================================== Bangle.js 2 CASIC
|
||||
|
||||
function CASIC_CHECKSUM(cmd) {
|
||||
var cs = 0;
|
||||
for (var i=1;i<cmd.length;i++)
|
||||
cs = cs ^ cmd.charCodeAt(i);
|
||||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
function jsFromBase64(b64) {
|
||||
var bin = atob(b64);
|
||||
var chunkSize = 128;
|
||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
if (isB1) { // UBLOX
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
}
|
||||
if (isB2) { // CASIC
|
||||
|
||||
// Select what GNSS System to use for decreased fix time.
|
||||
var radios = document.getElementsByName('gnss_select');
|
||||
var gnss_select="1";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
gnss_select=radios[i].value;
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||
}
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
var chunk = bin.substr(i,chunkSize);
|
||||
|
@ -106,6 +173,15 @@
|
|||
oReq.send();
|
||||
}
|
||||
|
||||
// Called when we know what device we're using
|
||||
function onInit(device) {
|
||||
isB2 = (device && device.id=="BANGLEJS2");
|
||||
isB1 = !isB2;
|
||||
document.getElementById("banglejs1-info").style = isB1?"":"display:none";
|
||||
document.getElementById("banglejs2-info").style = isB2?"":"display:none";
|
||||
document.getElementById("upload-wrap").style = "";
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,40 @@
|
|||
# BanglExercise
|
||||
|
||||
Can automatically track exercises while wearing the Bangle.js watch.
|
||||
|
||||
Currently only push ups and curls are supported.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This app is experimental but it seems to work quiet reliable for me.
|
||||
It could be and is likely that the threshold values for detecting exercises do not work for everyone.
|
||||
Therefore it would be great if we could improve this app together :-)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Select the exercise type you want to practice and go for it!
|
||||
Press stop to end your exercise.
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
## TODO
|
||||
* Add other exercise types:
|
||||
* Rope jumps
|
||||
* Sit ups
|
||||
* ...
|
||||
* Save exercise summaries to file system
|
||||
* Configure daily goal for exercises
|
||||
* Find a nicer icon
|
||||
|
||||
|
||||
## Contribute
|
||||
Feel free to send in improvements and remarks.
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icons
|
||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIbYh/8AYM/+EP/wFBv4FB/4FB/4FHAwIEBAv4FPAgIGCAosHAofggYFD4EABgXgOgIFLDAQWBAo0BAoOAVIV/UYQABj/4AocDCwQFTg46CEY4vFAopBBApIAVA=="))
|
|
@ -0,0 +1,362 @@
|
|||
const Layout = require("Layout");
|
||||
const heatshrink = require('heatshrink');
|
||||
const storage = require('Storage');
|
||||
|
||||
let tStart;
|
||||
let historyY = [];
|
||||
let historyZ = [];
|
||||
let historyAvgY = [];
|
||||
let historyAvgZ = [];
|
||||
let historySlopeY = [];
|
||||
let historySlopeZ = [];
|
||||
|
||||
let lastZeroPassCameFromPositive;
|
||||
let lastZeroPassTime = 0;
|
||||
|
||||
let lastExerciseCompletionTime = 0;
|
||||
let lastExerciseHalfCompletionTime = 0;
|
||||
|
||||
let exerciseType = {
|
||||
"id": "",
|
||||
"name": ""
|
||||
};
|
||||
|
||||
// add new exercises here:
|
||||
const exerciseTypes = [{
|
||||
"id": "pushup",
|
||||
"name": "push ups",
|
||||
"useYaxe": true,
|
||||
"useZaxe": false,
|
||||
"thresholdY": 2500,
|
||||
"thresholdMinTime": 1400, // mininmal time between two push ups in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two push ups in ms
|
||||
"thresholdMinDurationTime": 700, // mininmal duration of half a push ups in ms
|
||||
},
|
||||
{
|
||||
"id": "curl",
|
||||
"name": "curls",
|
||||
"useYaxe": true,
|
||||
"useZaxe": false,
|
||||
"thresholdY": 2500,
|
||||
"thresholdMinTime": 1000, // mininmal time between two curls in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two curls in ms
|
||||
"thresholdMinDurationTime": 500, // mininmal duration of half a push ups in ms
|
||||
}
|
||||
];
|
||||
let exerciseCounter = 0;
|
||||
|
||||
let layout;
|
||||
let recordActive = false;
|
||||
|
||||
// Size of average window for data analysis
|
||||
const avgSize = 6;
|
||||
|
||||
let hrtValue;
|
||||
|
||||
let settings = storage.readJSON("banglexercise.json", 1) || {
|
||||
'buzz': true
|
||||
};
|
||||
|
||||
function showMainMenu() {
|
||||
let menu;
|
||||
menu = {
|
||||
"": {
|
||||
title: "BanglExercise"
|
||||
}
|
||||
};
|
||||
|
||||
exerciseTypes.forEach(function(et) {
|
||||
menu["Do " + et.name] = function() {
|
||||
exerciseType = et;
|
||||
E.showMenu();
|
||||
startTraining();
|
||||
};
|
||||
});
|
||||
|
||||
if (exerciseCounter > 0) {
|
||||
menu["--------"] = {
|
||||
value: ""
|
||||
};
|
||||
menu["Last:"] = {
|
||||
value: exerciseCounter + " " + exerciseType.name
|
||||
};
|
||||
}
|
||||
menu.Exit = function() {
|
||||
load();
|
||||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function accelHandler(accel) {
|
||||
if (!exerciseType) return;
|
||||
const t = Math.round(new Date().getTime()); // time in ms
|
||||
const y = exerciseType.useYaxe ? accel.y * 8192 : 0;
|
||||
const z = exerciseType.useZaxe ? accel.z * 8192 : 0;
|
||||
//console.log(t, y, z);
|
||||
|
||||
if (exerciseType.useYaxe) {
|
||||
while (historyY.length > avgSize)
|
||||
historyY.shift();
|
||||
|
||||
historyY.push(y);
|
||||
|
||||
if (historyY.length > avgSize / 2) {
|
||||
const avgY = E.sum(historyY) / historyY.length;
|
||||
historyAvgY.push([t, avgY]);
|
||||
while (historyAvgY.length > avgSize)
|
||||
historyAvgY.shift();
|
||||
}
|
||||
}
|
||||
|
||||
if (exerciseType.useYaxe) {
|
||||
while (historyZ.length > avgSize)
|
||||
historyZ.shift();
|
||||
|
||||
historyZ.push(z);
|
||||
|
||||
if (historyZ.length > avgSize / 2) {
|
||||
const avgZ = E.sum(historyZ) / historyZ.length;
|
||||
historyAvgZ.push([t, avgZ]);
|
||||
while (historyAvgZ.length > avgSize)
|
||||
historyAvgZ.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Y
|
||||
if (exerciseType.useYaxe) {
|
||||
let l = historyAvgY.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgY[l - 2];
|
||||
const p2 = historyAvgY[l - 1];
|
||||
const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
|
||||
// we use this data for exercises which can be detected by using Y axis data
|
||||
switch (exerciseType.id) {
|
||||
case "pushup":
|
||||
isValidYAxisExercise(slopeY, t);
|
||||
break;
|
||||
case "curl":
|
||||
isValidYAxisExercise(slopeY, t);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Z
|
||||
if (exerciseType.useZaxe) {
|
||||
l = historyAvgZ.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgZ[l - 2];
|
||||
const p2 = historyAvgZ[l - 1];
|
||||
const slopeZ = (p2[1] - p1[1]) / (p2[0] - p1[0]);
|
||||
historyAvgZ.shift();
|
||||
historySlopeZ.push([p2[0] - p1[0], slopeZ]);
|
||||
|
||||
// TODO: we can use this data for some exercises which can be detected by using Z axis data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if slope value of Y-axis data looks like an exercise
|
||||
*
|
||||
* In detail we look for slop values which are bigger than the configured Y threshold for the current exercise
|
||||
* Then we look for two consecutive slope values of which one is above 0 and the other is below zero.
|
||||
* If we find one pair of these values this could be part of one exercise.
|
||||
* Then we look for a pair of values which cross the zero from the otherwise direction
|
||||
*/
|
||||
function isValidYAxisExercise(slopeY, t) {
|
||||
if (!exerciseType) return;
|
||||
|
||||
const thresholdY = exerciseType.thresholdY;
|
||||
const thresholdMinTime = exerciseType.thresholdMinTime;
|
||||
const thresholdMaxTime = exerciseType.thresholdMaxTime;
|
||||
const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime;
|
||||
const exerciseName = exerciseType.name;
|
||||
|
||||
if (Math.abs(slopeY) >= thresholdY) {
|
||||
historyAvgY.shift();
|
||||
historySlopeY.push([t, slopeY]);
|
||||
//console.log(t, Math.abs(slopeY));
|
||||
|
||||
const lSlopeY = historySlopeY.length;
|
||||
if (lSlopeY > 1) {
|
||||
const p1 = historySlopeY[lSlopeY - 1][1];
|
||||
const p2 = historySlopeY[lSlopeY - 2][1];
|
||||
if (p1 > 0 && p2 < 0) {
|
||||
if (lastZeroPassCameFromPositive == false) {
|
||||
lastExerciseHalfCompletionTime = t;
|
||||
//console.log(t, exerciseName + " half complete...");
|
||||
|
||||
layout.progress.label = "½";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
|
||||
lastZeroPassCameFromPositive = true;
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
if (p2 > 0 && p1 < 0) {
|
||||
if (lastZeroPassCameFromPositive == true) {
|
||||
const tDiffLastExercise = t - lastExerciseCompletionTime;
|
||||
const tDiffStart = t - tStart;
|
||||
//console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
|
||||
// check minimal time between exercises:
|
||||
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
|
||||
|
||||
// check maximal time between exercises:
|
||||
if (lastExerciseCompletionTime <= 0 || tDiffLastExercise <= thresholdMaxTime) {
|
||||
|
||||
// check minimal duration of exercise:
|
||||
const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime;
|
||||
if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) {
|
||||
//console.log(t, exerciseName + " complete!!!");
|
||||
|
||||
lastExerciseCompletionTime = t;
|
||||
exerciseCounter++;
|
||||
|
||||
layout.count.label = exerciseCounter;
|
||||
layout.progress.label = "";
|
||||
g.clear();
|
||||
layout.render();
|
||||
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(100, 0.4);
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to quick for duration time threshold!");
|
||||
lastExerciseCompletionTime = t;
|
||||
}
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to slow for time threshold!");
|
||||
lastExerciseCompletionTime = t;
|
||||
}
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to quick for time threshold!");
|
||||
lastExerciseCompletionTime = t;
|
||||
}
|
||||
}
|
||||
|
||||
lastZeroPassCameFromPositive = false;
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
historyY = [];
|
||||
historyZ = [];
|
||||
historyAvgY = [];
|
||||
historyAvgZ = [];
|
||||
historySlopeY = [];
|
||||
historySlopeZ = [];
|
||||
|
||||
lastZeroPassCameFromPositive = undefined;
|
||||
lastZeroPassTime = 0;
|
||||
lastExerciseHalfCompletionTime = 0;
|
||||
lastExerciseCompletionTime = 0;
|
||||
exerciseCounter = 0;
|
||||
tStart = 0;
|
||||
}
|
||||
|
||||
|
||||
function startTraining() {
|
||||
if (recordActive) return;
|
||||
g.clear(1);
|
||||
reset();
|
||||
Bangle.setHRMPower(1, "banglexercise");
|
||||
if (!hrtValue) hrtValue = "...";
|
||||
|
||||
layout = new Layout({
|
||||
type: "v",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "type",
|
||||
font: "6x8:2",
|
||||
label: exerciseType.name,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "h",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "count",
|
||||
font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
|
||||
label: 10,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "progress",
|
||||
font: "6x8:2",
|
||||
label: "",
|
||||
pad: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "h",
|
||||
c: [{
|
||||
type: "img",
|
||||
pad: 4,
|
||||
src: function() {
|
||||
return heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM"));
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "hrtRate",
|
||||
font: "6x8:2",
|
||||
label: hrtValue,
|
||||
pad: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "recording",
|
||||
font: "6x8:2",
|
||||
label: "TRAINING",
|
||||
bgCol: "#f00",
|
||||
pad: 5,
|
||||
fillx: 1
|
||||
},
|
||||
]
|
||||
}, {
|
||||
btns: [{
|
||||
label: "STOP",
|
||||
cb: () => {
|
||||
stopTraining();
|
||||
}
|
||||
}],
|
||||
lazy: false
|
||||
});
|
||||
layout.render();
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz
|
||||
Bangle.on('accel', accelHandler);
|
||||
tStart = new Date().getTime();
|
||||
recordActive = true;
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(200, 1);
|
||||
}
|
||||
|
||||
function stopTraining() {
|
||||
if (!recordActive) return;
|
||||
|
||||
g.clear(1);
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
Bangle.setHRMPower(0, "banglexercise");
|
||||
showMainMenu();
|
||||
recordActive = false;
|
||||
}
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
hrtValue = hrm.bpm;
|
||||
});
|
||||
|
||||
g.clear(1);
|
||||
showMainMenu();
|
After Width: | Height: | Size: 690 B |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,21 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "banglexercise.json";
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
E.showMenu({
|
||||
'': { 'title': 'BanglExercise' },
|
||||
'< Back': back,
|
||||
'Buzz': {
|
||||
value: "buzz" in settings ? settings.buzz : false,
|
||||
format: () => (settings.buzz ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.buzz = !settings.buzz;
|
||||
save('buzz', settings.buzz);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
|
@ -44,3 +44,4 @@
|
|||
0.38: Option to log to file if settings.log==2
|
||||
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||
0.40: Bootloader now rebuilds for new firmware versions
|
||||
0.41: Add Keyboard and Mouse Bluetooth HID option
|
||||
|
|
|
@ -18,6 +18,7 @@ boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
|||
if (s.ble!==false) {
|
||||
if (s.HID) { // Human interface device
|
||||
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
||||
else if (s.HID=="com") boot += `Bangle.HID = E.toUint8Array(atob("BQEJAqEBhQEJAaEABQkZASkFFQAlAZUFdQGBApUBdQOBAwUBCTAJMQk4FYElf3UIlQOBBgUMCjgCFYElf3UIlQGBBsDABQEJBqEBhQIFBxngKecVACUBdQGVCIECdQiVAYEBGQApcxUAJXOVBXUIgQDA"));`
|
||||
else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));`
|
||||
else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`;
|
||||
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
|
||||
|
|
|
@ -2,3 +2,6 @@
|
|||
0.02: Make overriding the HRM event optional
|
||||
Emit BTHRM event for external sensor
|
||||
Add recorder app plugin
|
||||
0.03: Prevent readings from internal sensor mixing into BT values
|
||||
Mark events with src property
|
||||
Show actual source of event in app
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
Bangle.isHRMOn = function() {
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
||||
print(settings);
|
||||
if (settings.enabled && !settings.replace){
|
||||
return origIsHRMOn();
|
||||
} else if (settings.enabled && settings.replace){
|
||||
|
@ -69,13 +68,11 @@
|
|||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
|
||||
|
||||
var eventName = settings.replace ? "HRM" : "BTHRM";
|
||||
|
||||
Bangle.emit(eventName, {
|
||||
Bangle.emit(settings.replace?"HRM":"BTHRM", {
|
||||
bpm:bpm,
|
||||
confidence:100
|
||||
});
|
||||
confidence:100,
|
||||
src:settings.replace?"bthrm":undefined
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
|
@ -107,8 +104,20 @@
|
|||
if (settings.enabled || !isOn){
|
||||
Bangle.setBTHRMPower(isOn, app);
|
||||
}
|
||||
if (settings.enabled && !settings.replace || !isOn){
|
||||
if ((settings.enabled && !settings.replace) || !settings.enabled || !isOn){
|
||||
origSetHRMPower(isOn, app);
|
||||
}
|
||||
}
|
||||
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
if (settings.enabled && settings.replace){
|
||||
if (!(Bangle._PWR===undefined) && !(Bangle._PWR.HRM===undefined)){
|
||||
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||
var app = Bangle._PWR.HRM[i];
|
||||
origSetHRMPower(0, app);
|
||||
Bangle.setBTHRMPower(1, app);
|
||||
if (Bangle._PWR.HRM===undefined) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -9,13 +9,14 @@ function draw(y, event, type, counter) {
|
|||
var px = g.getWidth()/2;
|
||||
g.reset();
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,y,g.getWidth(),y+80);
|
||||
g.clearRect(0,y,g.getWidth(),y+75);
|
||||
if (type == null || event == null || counter == 0) return;
|
||||
var str = event.bpm + "";
|
||||
g.setFontVector(40).drawString(str,px,y+20);
|
||||
str = "Confidence: " + event.confidence;
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
str = "Event: " + type;
|
||||
if (type == "HRM") str += " Source: " + (event.src ? event.src : "internal");
|
||||
g.setFontVector(12).drawString(str,px,y+60);
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,6 @@ Bangle.on('BTHRM', onBtHrm);
|
|||
Bangle.on('HRM', onHrm);
|
||||
|
||||
Bangle.setHRMPower(1,'bthrm')
|
||||
Bangle.setBTHRMPower(1,'bthrm')
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
(function(recorders) {
|
||||
recorders.bthrm = function() {
|
||||
var bpm = 0;
|
||||
var bpm = "";
|
||||
function onHRM(h) {
|
||||
bpm = h.bpm;
|
||||
bpm = h.bpm;
|
||||
}
|
||||
return {
|
||||
name : "BTHR",
|
||||
fields : ["BT Heartrate"],
|
||||
getValues : () => {
|
||||
result = [bpm];
|
||||
bpm = 0;
|
||||
bpm = "";
|
||||
return result;
|
||||
},
|
||||
start : () => {
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
0.01: New clock
|
||||
0.02: Fix icon & add battery warn functionality
|
||||
0.03: Theming support & minor fixes
|
||||
0.04: Make configurable what to show in each circle
|
||||
Add step distance and weather
|
||||
Allow switching visibility of widgets
|
||||
Make circles and text slightly bigger
|
||||
0.05: Show correct percentage values in circles
|
||||
Show humidity as weather circle data
|
||||
|
|
|
@ -2,19 +2,25 @@
|
|||
|
||||
A clock with circles for different data at the bottom in a probably familiar style
|
||||
|
||||
It shows besides time, date and day of week the following information:
|
||||
By default the time, date and day of week is shown.
|
||||
|
||||
It can show the following information (this can be configured):
|
||||
* Steps (requires [pedometer widget](https://banglejs.com/apps/#pedometer))
|
||||
* Heart rate (when screen is on and unlocked)
|
||||
* Battery (including charging and battery low)
|
||||
* Steps distance (depending on steps)
|
||||
* Heart rate (automatically updates when screen is on and unlocked)
|
||||
* Battery (including charging status and battery low warning)
|
||||
* Weather (requires [weather app](https://banglejs.com/apps/#weather))
|
||||
* Humidity as circle progress
|
||||
* Temperature inside circle
|
||||
* Condition as icon below circle
|
||||
|
||||
## Screenshot
|
||||
## Screenshots
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
## TODO
|
||||
* Show weather information
|
||||
* Configure which information to show in each circle
|
||||
* Configure visibility of widgets
|
||||
# TODO
|
||||
* Add sunrise and sunset
|
||||
* Display moon instead of sun during night on weather circle
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
|
|
@ -1,19 +1,37 @@
|
|||
const locale = require("locale");
|
||||
const heatshrink = require("heatshrink");
|
||||
const storage = require("Storage");
|
||||
|
||||
const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA="));
|
||||
const shoesIconGreen = heatshrink.decompress(atob("h0OwYJGhIEDgVIAgUEyQKDkmACgcggVACIeQAYMSgIRCgmApIbDiQUDAAkBkAFDGoYAD"));
|
||||
const heartIcon = heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM"));
|
||||
const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbBIUN23AAoYOCgEDFIgODABI"));
|
||||
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
|
||||
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
|
||||
|
||||
const weatherCloudy = heatshrink.decompress(atob("iEQwYWTgP//+AAoMPAoPwAoN/AocfAgP//0AAgQAB/AFEABgdDAAMDDohMRA"));
|
||||
const weatherSunny = heatshrink.decompress(atob("iEQwYLIg3AAgVgAQMMAo8Am3YAgUB23bAoUNAoIUBjYFCsOwBYoFDDpFgHYI1JI4gFGAAYA="));
|
||||
const weatherPartlyCloudy = heatshrink.decompress(atob("iEQwYQNv0AjgGDn4EDh///gFChwREC4MfxwIBv0//+AC4X4j4FCv/AgfwgED/wIBuAaBBwgFDgP4gf/AAXABwIEBDQQAEA=="));
|
||||
const weatherRainy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AocAnAFBFIU4EAM//gRBEAIOBhw1C/AmDAosAC4JNIAAg"));
|
||||
const weatherPartlyRainy = heatshrink.decompress(atob("h0OwYJGjkAnAFCj+AAgU//4FCuEA8EAg8ch/4gEB4////AAoIIBCIMD/wgCg4bBg/8BwMD+AgBh4ZBDQf/FIIABh4IBgAA=="));
|
||||
const weatherSnowy = heatshrink.decompress(atob("iEQwYROn/8AocH8AECuAFBh0Agf+CIN/4EDx/4j/x4EAgIIBwAXBAogRFDoopFGoxBGABIA="));
|
||||
const weatherFoggy = heatshrink.decompress(atob("iEQwYROn/8AgUB/EfwAFBh/AgfwgED/wIBuEABwd/4EcDQgFDgE4Fosf///8f//A/Lj/xCQIRNA="));
|
||||
const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AoX8gE4AoQpBnAdBF4IRBDQMH/kOHgY7DAo4AOA=="));
|
||||
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = require("Storage").readJSON("circlesclock.json", 1) || {
|
||||
settings = storage.readJSON("circlesclock.json", 1) || {
|
||||
'minHR': 40,
|
||||
'maxHR': 200,
|
||||
'stepGoal': 10000,
|
||||
'batteryWarn': 30
|
||||
'stepDistanceGoal': 8000,
|
||||
'stepLength': 0.8,
|
||||
'batteryWarn': 30,
|
||||
'showWidgets': false,
|
||||
'circle1': 'hr',
|
||||
'circle2': 'steps',
|
||||
'circle3': 'battery'
|
||||
};
|
||||
// Load step goal from pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
|
@ -21,122 +39,229 @@ function loadSettings() {
|
|||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
}
|
||||
}
|
||||
loadSettings();
|
||||
const showWidgets = settings.showWidgets || false;
|
||||
|
||||
let hrtValue;
|
||||
|
||||
// layout values:
|
||||
const colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
const colorBg = g.theme.dark ? '#000' : '#fff';
|
||||
const colorGrey = '#808080';
|
||||
const colorRed = '#ff0000';
|
||||
const colorGreen = '#00ff00';
|
||||
|
||||
let hrtValue;
|
||||
|
||||
const h = g.getHeight();
|
||||
const colorGreen = '#008000';
|
||||
const colorBlue = '#0000ff';
|
||||
const colorYellow = '#ffff00';
|
||||
const widgetOffset = showWidgets ? 24 : 0;
|
||||
const h = g.getHeight() - widgetOffset;
|
||||
const w = g.getWidth();
|
||||
const hOffset = 30;
|
||||
const hOffset = 30 - widgetOffset;
|
||||
const h1 = Math.round(1 * h / 5 - hOffset);
|
||||
const h2 = Math.round(3 * h / 5 - hOffset);
|
||||
const h3 = Math.round(8 * h / 8 - hOffset);
|
||||
const w1 = Math.round(w / 6);
|
||||
const w2 = Math.round(3 * w / 6);
|
||||
const w3 = Math.round(5 * w / 6);
|
||||
const radiusOuter = 22;
|
||||
const radiusInner = 16;
|
||||
const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position
|
||||
const circlePosX = [Math.round(w / 6), Math.round(3 * w / 6), Math.round(5 * w / 6)]; // cirle x positions
|
||||
const radiusOuter = 25;
|
||||
const radiusInner = 20;
|
||||
const circleFont = "Vector:15";
|
||||
const circleFontBig = "Vector:16";
|
||||
const circleFontSmall = "Vector:13";
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
g.clear(true);
|
||||
|
||||
if (!showWidgets) {
|
||||
/*
|
||||
* we are not drawing the widgets as we are taking over the whole screen
|
||||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
if (WIDGETS && typeof WIDGETS === "object") {
|
||||
for (let wd of WIDGETS) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(0, 0, w, h);
|
||||
g.fillRect(0, widgetOffset, w, h);
|
||||
|
||||
// time
|
||||
g.setFont("Vector:50");
|
||||
g.setFontAlign(-1, -1);
|
||||
g.setFontAlign(0, -1);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(locale.time(new Date(), 1), w / 10, h1 + 8);
|
||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8);
|
||||
|
||||
// date & dow
|
||||
g.setFont("Vector:20");
|
||||
g.setFont("Vector:21");
|
||||
g.setFontAlign(-1, 0);
|
||||
g.drawString(locale.date(new Date()), w / 10, h2);
|
||||
g.drawString(locale.dow(new Date()), w / 10, h2 + 22);
|
||||
g.drawString(locale.date(new Date()), w > 180 ? 2 * w / 10 : w / 10, h2);
|
||||
g.drawString(locale.dow(new Date()), w > 180 ? 2 * w / 10 : w / 10, h2 + 22);
|
||||
|
||||
// Steps circle
|
||||
drawSteps();
|
||||
|
||||
// Heart circle
|
||||
drawHeartRate();
|
||||
|
||||
// Battery circle
|
||||
drawBattery();
|
||||
drawCircle(1);
|
||||
drawCircle(2);
|
||||
drawCircle(3);
|
||||
}
|
||||
|
||||
const defaultCircleTypes = ["steps", "hr", "battery"];
|
||||
|
||||
function drawCircle(index) {
|
||||
let type = settings['circle' + index];
|
||||
if (!type) type = defaultCircleTypes[index - 1];
|
||||
const w = getCirclePosition(type);
|
||||
switch (type) {
|
||||
case "steps":
|
||||
drawSteps(w);
|
||||
break;
|
||||
case "stepsDist":
|
||||
drawStepsDistance(w);
|
||||
break;
|
||||
case "hr":
|
||||
drawHeartRate(w);
|
||||
break;
|
||||
case "battery":
|
||||
drawBattery(w);
|
||||
break;
|
||||
case "weather":
|
||||
drawWeather(w);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function drawSteps() {
|
||||
function getCirclePosition(type) {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const setting = settings['circle' + i];
|
||||
if (setting == type) return circlePosX[i - 1];
|
||||
}
|
||||
for (let i = 0; i < defaultCircleTypes.length; i++) {
|
||||
if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) {
|
||||
return circlePosX[i];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isCircleEnabled(type) {
|
||||
return getCirclePosition(type) != undefined;
|
||||
}
|
||||
|
||||
function drawSteps(w) {
|
||||
if (!w) w = getCirclePosition("steps");
|
||||
const steps = getSteps();
|
||||
const blue = '#0000ff';
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w1, h3, radiusOuter);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
const stepGoal = settings.stepGoal || 10000;
|
||||
if (stepGoal > 0) {
|
||||
let percent = steps / stepGoal;
|
||||
if (stepGoal < steps) percent = 1;
|
||||
drawGauge(w1, h3, percent, blue);
|
||||
drawGauge(w, h3, percent, colorBlue);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w1, h3, radiusInner);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
||||
g.fillPoly([w1, h3, w1 - 15, h3 + radiusOuter + 5, w1 + 15, h3 + radiusOuter + 5]);
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont("Vector:12");
|
||||
g.setFont(circleFont);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(shortValue(steps), w1 + 2, h3);
|
||||
g.drawString(shortValue(steps), w + 2, h3);
|
||||
|
||||
g.drawImage(shoesIcon, w1 - 6, h3 + radiusOuter - 6);
|
||||
g.drawImage(shoesIcon, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
||||
function drawHeartRate() {
|
||||
function drawStepsDistance(w) {
|
||||
if (!w) w = getCirclePosition("steps");
|
||||
const steps = getSteps();
|
||||
const stepDistance = settings.stepLength || 0.8;
|
||||
const stepsDistance = Math.round(steps * stepDistance);
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w2, h3, radiusOuter);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
const stepDistanceGoal = settings.stepDistanceGoal || 8000;
|
||||
if (stepDistanceGoal > 0) {
|
||||
let percent = stepsDistance / stepDistanceGoal;
|
||||
if (stepDistanceGoal < stepsDistance) percent = 1;
|
||||
drawGauge(w, h3, percent, colorGreen);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont(circleFont);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(shortValue(stepsDistance), w + 2, h3);
|
||||
|
||||
g.drawImage(shoesIconGreen, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
||||
function drawHeartRate(w) {
|
||||
if (!w) w = getCirclePosition("hr");
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
if (hrtValue != undefined && hrtValue > 0) {
|
||||
const minHR = 40;
|
||||
const minHR = settings.minHR || 40;
|
||||
const percent = (hrtValue - minHR) / (settings.maxHR - minHR);
|
||||
drawGauge(w2, h3, percent, colorRed);
|
||||
drawGauge(w, h3, percent, colorRed);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w2, h3, radiusInner);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
||||
g.fillPoly([w2, h3, w2 - 15, h3 + radiusOuter + 5, w2 + 15, h3 + radiusOuter + 5]);
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont("Vector:12");
|
||||
g.setFont(circleFontBig);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(hrtValue != undefined ? hrtValue : "-", w2, h3);
|
||||
g.drawString(hrtValue != undefined ? hrtValue : "-", w, h3);
|
||||
|
||||
g.drawImage(heartIcon, w2 - 6, h3 + radiusOuter - 6);
|
||||
g.drawImage(heartIcon, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
||||
function drawBattery() {
|
||||
function drawBattery(w) {
|
||||
if (!w) w = getCirclePosition("battery");
|
||||
const battery = E.getBattery();
|
||||
const yellow = '#ffff00';
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w3, h3, radiusOuter);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
if (battery > 0) {
|
||||
const percent = battery / 100;
|
||||
drawGauge(w3, h3, percent, yellow);
|
||||
drawGauge(w, h3, percent, colorYellow);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w3, h3, radiusInner);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
||||
g.fillPoly([w3, h3, w3 - 15, h3 + radiusOuter + 5, w3 + 15, h3 + radiusOuter + 5]);
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont("Vector:12");
|
||||
g.setFont(circleFont);
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
let icon = powerIcon;
|
||||
|
@ -144,17 +269,100 @@ function drawBattery() {
|
|||
if (Bangle.isCharging()) {
|
||||
color = colorGreen;
|
||||
icon = powerIconGreen;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (settings.batteryWarn != undefined && battery <= settings.batteryWarn) {
|
||||
color = colorRed;
|
||||
icon = powerIconRed;
|
||||
}
|
||||
}
|
||||
g.setColor(color);
|
||||
g.drawString(battery + '%', w3, h3);
|
||||
g.drawString(battery + '%', w, h3);
|
||||
|
||||
g.drawImage(icon, w3 - 6, h3 + radiusOuter - 6);
|
||||
g.drawImage(icon, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
||||
function drawWeather(w) {
|
||||
if (!w) w = getCirclePosition("weather");
|
||||
const weather = getWeather();
|
||||
const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||
const humidity = weather ? weather.hum : undefined;
|
||||
const code = weather ? weather.code : -1;
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
if (humidity >= 0) {
|
||||
drawGauge(w, h3, humidity / 100, colorYellow);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
||||
g.fillPoly([w, h3, w - 25, h3 + radiusOuter + 5, w + 25, h3 + radiusOuter + 5]);
|
||||
|
||||
const content = tempString ? tempString : "?";
|
||||
g.setFont(content.length < 4 ? circleFont : circleFontSmall);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(content, w, h3);
|
||||
|
||||
if (code > 0) {
|
||||
const icon = getWeatherIconByCode(code);
|
||||
if (icon) g.drawImage(icon, w - 6, h3 + radiusOuter - 10);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Choose weather icon to display based on weather conditition code
|
||||
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||
*/
|
||||
function getWeatherIconByCode(code) {
|
||||
const codeGroup = Math.round(code / 100);
|
||||
switch (codeGroup) {
|
||||
case 2:
|
||||
return weatherStormy;
|
||||
case 3:
|
||||
return weatherCloudy;
|
||||
case 5:
|
||||
switch (code) {
|
||||
case 511:
|
||||
return weatherSnowy;
|
||||
case 520:
|
||||
return weatherPartlyRainy;
|
||||
case 521:
|
||||
return weatherPartlyRainy;
|
||||
case 522:
|
||||
return weatherPartlyRainy;
|
||||
case 531:
|
||||
return weatherPartlyRainy;
|
||||
default:
|
||||
return weatherRainy;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
return weatherSnowy;
|
||||
case 7:
|
||||
return weatherFoggy;
|
||||
case 8:
|
||||
switch (code) {
|
||||
case 800:
|
||||
return weatherSunny;
|
||||
case 801:
|
||||
return weatherPartlyCloudy;
|
||||
case 802:
|
||||
return weatherPartlyCloudy;
|
||||
default:
|
||||
return weatherCloudy;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function radians(a) {
|
||||
|
@ -162,22 +370,21 @@ function radians(a) {
|
|||
}
|
||||
|
||||
function drawGauge(cx, cy, percent, color) {
|
||||
let offset = 30;
|
||||
let end = 300;
|
||||
var i = 0;
|
||||
var r = radiusInner + 3;
|
||||
const offset = 15;
|
||||
const end = 345;
|
||||
const r = radiusInner + 3;
|
||||
|
||||
if (percent <= 0) return;
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
var startrot = -offset;
|
||||
var endrot = startrot - ((end - offset) * percent) - 15;
|
||||
const startrot = -offset;
|
||||
const endrot = startrot - ((end - offset) * percent);
|
||||
|
||||
g.setColor(color);
|
||||
|
||||
const size = 4;
|
||||
const size = radiusOuter - radiusInner - 2;
|
||||
// draw gauge
|
||||
for (i = startrot; i > endrot - size; i -= size) {
|
||||
for (let i = startrot; i > endrot - size; i -= size) {
|
||||
x = cx + r * Math.sin(radians(i));
|
||||
y = cy + r * Math.cos(radians(i));
|
||||
g.fillCircle(x, y, size);
|
||||
|
@ -198,54 +405,56 @@ function shortValue(v) {
|
|||
}
|
||||
|
||||
function getSteps() {
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
if (WIDGETS && WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
Bangle.setHRMPower(1, "watch");
|
||||
if (hrtValue == undefined) {
|
||||
hrtValue = '...';
|
||||
drawHeartRate();
|
||||
}
|
||||
} else {
|
||||
Bangle.setHRMPower(0, "watch");
|
||||
}
|
||||
drawHeartRate();
|
||||
drawSteps();
|
||||
});
|
||||
function getWeather() {
|
||||
const jsonWeather = storage.readJSON('weather.json');
|
||||
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
|
||||
}
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
//if(hrm.confidence > 90){
|
||||
hrtValue = hrm.bpm;
|
||||
if (Bangle.isLCDOn())
|
||||
function enableHRMSensor() {
|
||||
Bangle.setHRMPower(1, "circleclock");
|
||||
if (hrtValue == undefined) {
|
||||
hrtValue = '...';
|
||||
drawHeartRate();
|
||||
//} else {
|
||||
// hrtValue = undefined;
|
||||
//}
|
||||
});
|
||||
|
||||
Bangle.on('charging', function(charging) {
|
||||
drawBattery();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
/*
|
||||
* we are not drawing the widgets as we are taking over the whole screen
|
||||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
if (typeof WIDGETS === "object") {
|
||||
for (let wd of WIDGETS) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
}
|
||||
}
|
||||
loadSettings();
|
||||
setInterval(draw, 60000);
|
||||
draw();
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
if (isCircleEnabled("hr")) {
|
||||
enableHRMSensor();
|
||||
}
|
||||
draw();
|
||||
} else {
|
||||
Bangle.setHRMPower(0, "circleclock");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
if (isCircleEnabled("hr")) {
|
||||
hrtValue = hrm.bpm;
|
||||
if (Bangle.isLCDOn())
|
||||
drawHeartRate();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
|
||||
draw();
|
||||
setInterval(draw, 60000);
|
||||
|
||||
Bangle.on('charging', function(charging) {
|
||||
if (isCircleEnabled("battery")) drawBattery();
|
||||
});
|
||||
|
||||
if (isCircleEnabled("hr")) {
|
||||
enableHRMSensor();
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.5 KiB |
|
@ -6,13 +6,26 @@
|
|||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
var valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather"];
|
||||
var namesCircleTypes = ["steps", "distance", "heart", "battery", "weather"];
|
||||
E.showMenu({
|
||||
'': { 'title': 'circlesclock' },
|
||||
'< Back': back,
|
||||
'min heartrate': {
|
||||
value: "minHR" in settings ? settings.minHR : 40,
|
||||
min: 0,
|
||||
max : 250,
|
||||
step: 5,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('minHR', x),
|
||||
},
|
||||
'max heartrate': {
|
||||
value: "maxHR" in settings ? settings.maxHR : 200,
|
||||
min: 20,
|
||||
max : 250,
|
||||
step: 10,
|
||||
step: 5,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
|
@ -28,7 +41,27 @@
|
|||
},
|
||||
onchange: x => save('stepGoal', x),
|
||||
},
|
||||
'battery warn lvl': {
|
||||
'step length': {
|
||||
value: "stepLength" in settings ? settings.stepLength : 0.8,
|
||||
min: 0.1,
|
||||
max : 1.5,
|
||||
step: 0.01,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('stepLength', x),
|
||||
},
|
||||
'step dist goal': {
|
||||
value: "stepDistanceGoal" in settings ? settings.stepDistanceGoal : 8000,
|
||||
min: 2000,
|
||||
max : 30000,
|
||||
step: 1000,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('stepDistanceGoal', x),
|
||||
},
|
||||
'battery warn': {
|
||||
value: "batteryWarn" in settings ? settings.batteryWarn : 30,
|
||||
min: 10,
|
||||
max : 100,
|
||||
|
@ -38,6 +71,28 @@
|
|||
},
|
||||
onchange: x => save('batteryWarn', x),
|
||||
},
|
||||
'< Back': back,
|
||||
'show widgets': {
|
||||
value: "showWidgets" in settings ? settings.showWidgets : false,
|
||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: x => save('showWidgets', x),
|
||||
},
|
||||
'left': {
|
||||
value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0,
|
||||
min: 0, max: 4,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle1', valuesCircleTypes[x]),
|
||||
},
|
||||
'middle': {
|
||||
value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2,
|
||||
min: 0, max: 4,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle2', valuesCircleTypes[x]),
|
||||
},
|
||||
'right': {
|
||||
value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3,
|
||||
min: 0, max: 4,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle3', valuesCircleTypes[x]),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Andreas Rozek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,27 @@
|
|||
# Variable Analog Clock #
|
||||
|
||||
This app implements an analog clock with various faces, hands and colors to
|
||||
choose from.
|
||||
|
||||
You have the choice between:
|
||||
|
||||
* 4 different clock faces     and
|
||||
* 3 different clock hands (optionally with or without second hands)   
|
||||
|
||||
Additionally, you may use the currently configured global theme or configure
|
||||
your own colors for clock fore- and background and second hands.
|
||||
|
||||
Just swipe up or down to switch from clock display to configuration screen
|
||||
|
||||
  
|
||||
 
|
||||
|
||||
Chosen settings will be written to the Bangle.js's flash memory and restored
|
||||
whenever the clock is started again.
|
||||
|
||||
This clock also acts as an example for the building blocks found in the author's
|
||||
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Andreas Rozek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,29 @@
|
|||
# Configurable Analog Clock #
|
||||
|
||||
This app implements an analog clock with various faces, hands and colors to
|
||||
choose from.
|
||||
|
||||
You have the choice between:
|
||||
|
||||
* 4 different clock faces<br>    and
|
||||
* 3 different clock hands (optionally with or without second hands)<br>  
|
||||
|
||||
Additionally, you may use the currently configured global theme or configure
|
||||
your own colors for clock fore- and background and second hands.
|
||||
|
||||
Just swipe up or down to switch from clock display to the first configuration
|
||||
screen and continue from there
|
||||
|
||||
 
|
||||
 
|
||||

|
||||
|
||||
Chosen settings will be written to the Bangle.js's flash memory and restored
|
||||
whenever the clock is started again.
|
||||
|
||||
This clock also acts as an example for the building blocks found in the author's
|
||||
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgZC/AB1RgkQsAQMyUKAYMIkAPJgNFiEBgACBg0YCRMogEJkGSAwMSEZNAAQMAEAMGgBKHgXAlECwMgzcAmkAhgRGilRssUgMEEYcBwARFiBHBgQKB7AjCawIQEgoCCigDBjEBwwEBEwIAGlmSEYYABI4PAEYhEBNYIjCAYVtwCSElG2xdoAwQjDhpZEEAMUqAHDCIaPBEYlAiwjItkAgYjFqJHDCIdhI4j1CAAhlEZoTUEAAcGEYZKEEYWgCIgjEWYkBoqwCCITLBgcMmPXhgjCgUB2iFDm3pw0YLAMygEgc4QjF49cmA3BbQQjDgGkI5OwNZZ9FEYoRLEYxmBCI5jBEYQACyQRHgmAEYsEEZEka4kAhEEEY8BCIMJCIYjKgGChAFDCwKzDNYyKEJgUDlgRBAoPDRQQjEZQZzEjScIhgjBEwQjEH4aXEgIjBjYCBjQCBMYYADmAjDFIjcGKocAjBKCgJRCAAwaCEARQBmARIhBrEgSMEAApEBmHAAQJrCABUCjFhwwQMI4oA7"))
|
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: New app
|
||||
0.02: Cleanup interface and add settings, widget, add skin temp reporting.
|
||||
0.03: Move code for recording to this app
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
(function(recorders) {
|
||||
recorders.coretemp = function() {
|
||||
var core = "", skin = "";
|
||||
var hasCore = false;
|
||||
function onCore(c) {
|
||||
core=c.core;
|
||||
skin=c.skin;
|
||||
hasCore = true;
|
||||
}
|
||||
return {
|
||||
name : "Core",
|
||||
fields : ["Core","Skin"],
|
||||
getValues : () => {
|
||||
var r = [core,skin];
|
||||
core = "";
|
||||
skin = "";
|
||||
return r;
|
||||
},
|
||||
start : () => {
|
||||
hasCore = false;
|
||||
Bangle.on('CoreTemp', onCore);
|
||||
},
|
||||
stop : () => {
|
||||
hasCore = false;
|
||||
Bangle.removeListener('CoreTemp', onCore);
|
||||
},
|
||||
draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
|
||||
};
|
||||
}
|
||||
})
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
0.01: App created
|
||||
0.02: Persist state to storage to enable stopwatch to continue in the background
|
||||
0.03: Modified to use setUI, theme and different screens
|
||||
0.04: *bugfix* stopwatch broken with v0.03 setUI
|
||||
realigned quick n dirty screen positions
|
||||
help adjusted to fit bangle1 & bangle2 screen-size with widgets
|
||||
fixed bangle2 colors for chrono and last lap highlight
|
||||
added screen for bangle2 and a small README
|
|
@ -0,0 +1,18 @@
|
|||
# dev stop watch
|
||||
|
||||
stores state at kill
|
||||
|
||||
## Bangle 1
|
||||

|
||||
|
||||
* BTN1: start/lap
|
||||
* BTN2: launcher
|
||||
* BTN3: reset
|
||||
|
||||
## Bangle 2
|
||||

|
||||
|
||||
* TAP top right: start/lap
|
||||
* TAP bottom right: reset
|
||||
* Use BTN to get to launcher
|
||||
|
|
@ -3,11 +3,11 @@ const EMPTY_H = '00:00:000';
|
|||
const MAX_LAPS = 6;
|
||||
const XY_CENTER = g.getWidth() / 2;
|
||||
const big = g.getWidth()>200;
|
||||
const Y_CHRONO = 40;
|
||||
const Y_HEADER = big?80:60;
|
||||
const Y_LAPS = big?125:90;
|
||||
const Y_CHRONO = big?40:30;
|
||||
const Y_HEADER = big?95:65;
|
||||
const Y_LAPS = big?125:80;
|
||||
const H_LAPS = big?15:8;
|
||||
const Y_BTN3 = big?225:165;
|
||||
const Y_HELP = big?225:135;
|
||||
const FONT = '6x8';
|
||||
const CHRONO = '/* C H R O N O */';
|
||||
|
||||
|
@ -27,18 +27,17 @@ var state = require("Storage").readJSON("devstopwatch.state.json",1) || {
|
|||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clockupdown", btn=>{
|
||||
if (btn==0) {
|
||||
reset = false;
|
||||
|
||||
if (state.started) {
|
||||
changeLap();
|
||||
} else {
|
||||
if (!reset) {
|
||||
chronoInterval = setInterval(chronometer, 10);
|
||||
}
|
||||
switch (btn) {
|
||||
case -1:
|
||||
if (state.started) {
|
||||
changeLap();
|
||||
} else {
|
||||
chronoInterval = setInterval(chronometer, 10);
|
||||
}
|
||||
break;
|
||||
case 1: resetChrono(); break;
|
||||
default: Bangle.showLauncher(); break; //launcher handeled by ROM
|
||||
}
|
||||
}
|
||||
if (btn==1) resetChrono();
|
||||
});
|
||||
|
||||
function resetChrono() {
|
||||
|
@ -105,6 +104,7 @@ function printChrono() {
|
|||
|
||||
var print = '';
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont(FONT, big?2:1);
|
||||
print = CHRONO;
|
||||
g.drawString(print, XY_CENTER, Y_CHRONO, true);
|
||||
|
@ -124,7 +124,8 @@ function printChrono() {
|
|||
let suffix = ' ';
|
||||
if (state.currentLapIndex === i) {
|
||||
let suffix = '*';
|
||||
g.setColor("#f70");
|
||||
if (process.env.HWVERSION==2) g.setColor("#0ee");
|
||||
else g.setColor("#f70");
|
||||
}
|
||||
|
||||
const lapLine = `L${i - 1} ${state.laps[i]} ${suffix}\n`;
|
||||
|
@ -133,8 +134,17 @@ function printChrono() {
|
|||
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont(FONT, 1);
|
||||
print = 'Press 3 to reset';
|
||||
g.drawString(print, XY_CENTER, Y_BTN3, true);
|
||||
//help for model 2 or 1
|
||||
if (process.env.HWVERSION==2) {
|
||||
print = /*LANG*/'TAP right top/bottom';
|
||||
g.drawString(print, XY_CENTER, Y_HELP, true);
|
||||
print = /*LANG*/'start&lap/reset, BTN1: EXIT';
|
||||
g.drawString(print, XY_CENTER, Y_HELP+10, true);
|
||||
}
|
||||
else {
|
||||
print = /*LANG*/'BTNs 1:startlap 2:exit 3:reset';
|
||||
g.drawString(print, XY_CENTER, Y_HELP, true);
|
||||
}
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: New Clock Nifty A
|
||||
0.02: Shows the current week number (ISO8601), can be disabled via settings ""
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
# Nifty-A Clock
|
||||
|
||||
Colors are black/white - photos have non correct camera color "blue"
|
||||
|
||||
## This is the clock
|
||||
|
||||

|
||||
|
||||
## The week number (ISO8601) can be turned of in settings
|
||||
(default is **"On"**)
|
||||
|
||||

|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const locale = require("locale");
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true};
|
||||
|
||||
/* Clock *********************************************/
|
||||
const scale = g.getWidth() / 176;
|
||||
|
@ -16,6 +17,18 @@ const center = {
|
|||
y: Math.round(((viewport.height - widget) / 2) + widget),
|
||||
}
|
||||
|
||||
function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
|
||||
var tdt = new Date(date.valueOf());
|
||||
var dayn = (date.getDay() + 6) % 7;
|
||||
tdt.setDate(tdt.getDate() - dayn + 3);
|
||||
var firstThursday = tdt.valueOf();
|
||||
tdt.setMonth(0, 1);
|
||||
if (tdt.getDay() !== 4) {
|
||||
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
|
||||
}
|
||||
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
|
||||
}
|
||||
|
||||
function d02(value) {
|
||||
return ('0' + value).substr(-2);
|
||||
}
|
||||
|
@ -29,23 +42,26 @@ function draw() {
|
|||
const minutes = d02(now.getMinutes());
|
||||
const day = d02(now.getDate());
|
||||
const month = d02(now.getMonth() + 1);
|
||||
const year = now.getFullYear();
|
||||
|
||||
const month2 = locale.month(now, 3);
|
||||
const day2 = locale.dow(now, 3);
|
||||
const year = now.getFullYear(now);
|
||||
const weekNum = d02(ISO8601_week_no(now));
|
||||
const monthName = locale.month(now, 3);
|
||||
const dayName = locale.dow(now, 3);
|
||||
|
||||
const centerTimeScaleX = center.x + 32 * scale;
|
||||
g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
|
||||
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
|
||||
g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale);
|
||||
g.drawString(hour, centerTimeScaleX, center.y - 31 * scale);
|
||||
g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale);
|
||||
|
||||
g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale);
|
||||
|
||||
const centerDatesScaleX = center.x + 40 * scale;
|
||||
g.setFontAlign(-1, 0).setFont("Vector", 16 * scale);
|
||||
g.drawString(year, center.x + 40 * scale, center.y - 62 * scale);
|
||||
g.drawString(month, center.x + 40 * scale, center.y - 44 * scale);
|
||||
g.drawString(day, center.x + 40 * scale, center.y - 26 * scale);
|
||||
g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale);
|
||||
g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale);
|
||||
g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
|
||||
g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
|
||||
g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
|
||||
if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale);
|
||||
g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
|
||||
g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
|
||||
}
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,23 @@
|
|||
(function(back) {
|
||||
var FILE = "ffcniftya.json";
|
||||
// Load settings
|
||||
var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, cfg);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"" : { "title" : "Nifty-A Clock" },
|
||||
"< Back" : () => back(),
|
||||
'week number?': {
|
||||
value: cfg.showWeekNum,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
cfg.showWeekNum = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
|
@ -0,0 +1,4 @@
|
|||
timezonedb.csv.zip
|
||||
country.csv
|
||||
zone.csv
|
||||
timezone.csv
|
|
@ -0,0 +1,2 @@
|
|||
0.01: first release
|
||||
0.02: RAM efficient version of `fourTwentyTz.js` (as suggested by @gfwilliams).
|
|
@ -0,0 +1,24 @@
|
|||
# Four Twenty Clock
|
||||
|
||||
A clock that tells when and where it's going to be [4:20](https://en.wikipedia.org/wiki/420_%28cannabis_culture%29) next
|
||||
|
||||
 
|
||||
|
||||
## Generating `fourTwentyTz.js`
|
||||
|
||||
Once in a while we need to regenerate it for 2 reasons:
|
||||
|
||||
* One or more places got in or out of daylight saving time (DST) mode.
|
||||
* The database saying _when_ places enter/exit DST mode got updated.
|
||||
|
||||
I'll do my best to release a new version every time this happens,
|
||||
but if you ever need to do this yourself, here's how:
|
||||
|
||||
* `cd` to the `ftclock` folder
|
||||
* If you haven't done so yet, run `npm install` there (this would create the `node_modules` folder).
|
||||
* Get and unzip the latest `timezone.csv.zip` from https://timezonedb.com/download
|
||||
* Run `npm run make`
|
||||
|
||||
## Creator
|
||||
|
||||
[Nimrod Kerrett](zzzen.com)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AH4A/AH4A/AAMHu4ACuwHBs4HDsEGBIQLCsADBgwPDCAQGEuwXFBwI0GEAMHuAGCCoMHC4pMHEAIXEAgIGEBwI9BC4wSCC8IVCMAwIBs4XKUQJfITQgXCDwp8EHAqaECoLFEu4cDBIggBs6uFZozuGBAVmC4g+FMgZQEZQ5vGC4iRIC5IrDN4h5EC5J3BCoIKGgyaEC44VBC46yEDgoeDgxqLC5SCMAgoTFY47GFC4xFBdwwPBD4oWFAH4A/AH4A/AH4AjA=="))
|
|
@ -0,0 +1,51 @@
|
|||
let getNextFourTwenty = require("fourTwenty").getNextFourTwenty;
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
let leaf_img = "\x17\x18\x81\x00\x00\x10\x00\x00 \x00\x00@\x00\x01\xc0\x00\x03\x80\x00\x0f\x80\x00\x1f\x00\x00>\x00\x00|\x00\xc0\xf8\x19\xe1\xf0\xf1\xe3\xe3\xc3\xf7\xdf\x83\xff\xfe\x03\xff\xf8\x03\xff\xe0\x03\xff\x80\x03\xfe\x00\x7f\xff\xc0\xff\xff\xc0\x06\xe0\x00\x18\xc0\x00 \x80\x00\x00\x00";
|
||||
|
||||
// timeout used to update every minute
|
||||
let drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
let date = new Date();
|
||||
let timeStr = require("locale").time(date,1);
|
||||
let next420 = getNextFourTwenty();
|
||||
g.clearRect(0,26,g.getWidth(),g.getHeight());
|
||||
g.setColor("#00ff00").setFontAlign(0,-1).setFont("Teletext10x18Ascii",2);
|
||||
g.drawString(next420.minutes? timeStr: `\0${leaf_img}${timeStr}\0${leaf_img}`, g.getWidth()/2, 28);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontAlign(-1,-1).setFont("Teletext10x18Ascii");
|
||||
g.drawString(g.wrapString(next420.text, g.getWidth()-8).join("\n"),4,60);
|
||||
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,47 @@
|
|||
let ftz = require("fourTwentyTz"),
|
||||
offsets = ftz.offsets,
|
||||
timezones = ftz.timezones;
|
||||
|
||||
function get420offset() {
|
||||
let current_time = Math.floor((Date.now()%(24*3600*1000))/60000);
|
||||
let current_min = current_time%60;
|
||||
if (current_min>20 && current_min<25) {
|
||||
current_time -= current_min-20; // 5 minutes grace period
|
||||
}
|
||||
let offset = 16*60+20-current_time;
|
||||
if (offset<0) {
|
||||
offset += 24*60;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
function makeFourTwentyText(minutes, places) {
|
||||
//let plural = minutes==1? "": "s";
|
||||
//let msgprefix = minutes? `${minutes} minute${plural} to`: "It is now";
|
||||
let msgprefix = minutes? `${minutes}m to`: "It is now";
|
||||
let msgsuffix = places.length>1? ", and other fine places": "";
|
||||
let msgplace = places[Math.floor(Math.random()*places.length)];
|
||||
return `${msgprefix} 4:20 at ${msgplace}${msgsuffix}.`;
|
||||
}
|
||||
|
||||
function getNextFourTwenty() {
|
||||
let offs = get420offset();
|
||||
for (let i=0; i<offsets.length; i++) {
|
||||
if (offsets[i]<=offs) {
|
||||
let minutes = offs-offsets[i];
|
||||
let places = timezones(offsets[i]);
|
||||
return {
|
||||
minutes: minutes,
|
||||
places: places,
|
||||
text: makeFourTwentyText(minutes, places)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
minutes: 666,
|
||||
places: ["Snafu (Yes. It's a bug)"],
|
||||
text: "Snafu (Yes. It's a bug)"
|
||||
};
|
||||
}
|
||||
|
||||
exports.getNextFourTwenty = getNextFourTwenty;
|
|
@ -0,0 +1,33 @@
|
|||
// Generated by mkFourTwentyTz.js
|
||||
// Wed Jan 12 2022 19:35:36 GMT+0200 (Israel Standard Time)
|
||||
// Data source: https://timezonedb.com/files/timezonedb.csv.zip
|
||||
exports.offsets = [1380,1320,1260,1200,1140,1080,1020,960,900,840,780,720,660,600,540,480,420,360,300,240,180,120,60,0];
|
||||
exports.timezones = function(offs) {
|
||||
switch (offs) {
|
||||
case 1380: return ["Cape Verde, Cabo Verde","Scoresbysund, Greenland","Azores, Portugal"];
|
||||
case 1320: return ["Noronha, Brazil","South Georgia, South Georgia and the South Sandwich Islands"];
|
||||
case 1260: return ["Palmer, Antarctica","Rothera, Antarctica","Buenos Aires, Argentina","Cordoba, Argentina","Salta, Argentina","Jujuy, Argentina","Tucuman, Argentina","Catamarca, Argentina","La Rioja, Argentina","San Juan, Argentina","Mendoza, Argentina","San Luis, Argentina","Rio Gallegos, Argentina","Ushuaia, Argentina","Belem, Brazil","Fortaleza, Brazil","Recife, Brazil","Araguaina, Brazil","Maceio, Brazil","Bahia, Brazil","Sao Paulo, Brazil","Santarem, Brazil","Santiago, Chile","Punta Arenas, Chile","Stanley, Falkland Islands (Malvinas)","Cayenne, French Guiana","Nuuk, Greenland","Miquelon, Saint Pierre and Miquelon","Asuncion, Paraguay","Paramaribo, Suriname","Montevideo, Uruguay"];
|
||||
case 1200: return ["Antigua, Antigua and Barbuda","Anguilla, Anguilla","Aruba, Aruba","Barbados, Barbados","St Barthelemy, Saint Barthélemy","Bermuda, Bermuda","La Paz, Bolivia (Plurinational State of)","Kralendijk, Bonaire, Sint Eustatius and Saba","Campo Grande, Brazil","Cuiaba, Brazil","Porto Velho, Brazil","Boa Vista, Brazil","Manaus, Brazil","Halifax, Canada","Glace Bay, Canada","Moncton, Canada","Goose Bay, Canada","Blanc-Sablon, Canada","Curacao, Curaçao","Dominica, Dominica","Santo Domingo, Dominican Republic","Grenada, Grenada","Thule, Greenland","Guadeloupe, Guadeloupe","Guyana, Guyana","St Kitts, Saint Kitts and Nevis","St Lucia, Saint Lucia","Marigot, Saint Martin (French part)","Martinique, Martinique","Montserrat, Montserrat","Puerto Rico, Puerto Rico","Lower Princes, Sint Maarten (Dutch part)","Port of_Spain, Trinidad and Tobago","St Vincent, Saint Vincent and the Grenadines","Caracas, Venezuela (Bolivarian Republic of)","Tortola, Virgin Islands (British)","St Thomas, Virgin Islands (U.S.)"];
|
||||
case 1140: return ["Eirunepe, Brazil","Rio Branco, Brazil","Nassau, Bahamas","Toronto, Canada","Nipigon, Canada","Thunder Bay, Canada","Iqaluit, Canada","Pangnirtung, Canada","Atikokan, Canada","Easter, Chile","Bogota, Colombia","Havana, Cuba","Guayaquil, Ecuador","Port-au-Prince, Haiti","Jamaica, Jamaica","Cayman, Cayman Islands","Cancun, Mexico","Panama, Panama","Lima, Peru","Grand Turk, Turks and Caicos Islands","New York, United States of America","Detroit, United States of America","Louisville, Kentucky","Monticello, Kentucky","Indianapolis, Indiana","Vincennes, Indiana","Winamac, Indiana","Marengo, Indiana","Petersburg, Indiana","Vevay, Indiana"];
|
||||
case 1080: return ["Belize, Belize","Winnipeg, Canada","Rainy River, Canada","Resolute, Canada","Rankin Inlet, Canada","Regina, Canada","Swift Current, Canada","Costa Rica, Costa Rica","Galapagos, Ecuador","Guatemala, Guatemala","Tegucigalpa, Honduras","Mexico City, Mexico","Merida, Mexico","Monterrey, Mexico","Matamoros, Mexico","Bahia Banderas, Mexico","Managua, Nicaragua","El Salvador, El Salvador","Chicago, United States of America","Tell City, Indiana","Knox, Indiana","Menominee, United States of America","Center, North Dakota","New_Salem, North Dakota","Beulah, North Dakota"];
|
||||
case 1020: return ["Edmonton, Canada","Cambridge Bay, Canada","Yellowknife, Canada","Inuvik, Canada","Creston, Canada","Dawson Creek, Canada","Fort Nelson, Canada","Whitehorse, Canada","Dawson, Canada","Mazatlan, Mexico","Chihuahua, Mexico","Ojinaga, Mexico","Hermosillo, Mexico","Denver, United States of America","Boise, United States of America","Phoenix, United States of America"];
|
||||
case 960: return ["Vancouver, Canada","Tijuana, Mexico","Pitcairn, Pitcairn","Los Angeles, United States of America"];
|
||||
case 900: return ["Gambier, French Polynesia","Anchorage, United States of America","Juneau, United States of America","Sitka, United States of America","Metlakatla, United States of America","Yakutat, United States of America","Nome, United States of America"];
|
||||
case 840: return ["Rarotonga, Cook Islands","Kiritimati, Kiribati","Tahiti, French Polynesia","Adak, United States of America","Honolulu, United States of America"];
|
||||
case 780: return ["McMurdo, Antarctica","Pago Pago, American Samoa","Fiji, Fiji","Kanton, Kiribati","Niue, Niue","Auckland, New Zealand","Fakaofo, Tokelau","Tongatapu, Tonga","Midway, United States Minor Outlying Islands","Apia, Samoa"];
|
||||
case 720: return ["Tarawa, Kiribati","Majuro, Marshall Islands","Kwajalein, Marshall Islands","Norfolk, Norfolk Island","Nauru, Nauru","Kamchatka, Russian Federation","Anadyr, Russian Federation","Funafuti, Tuvalu","Wake, United States Minor Outlying Islands","Wallis, Wallis and Futuna"];
|
||||
case 660: return ["Casey, Antarctica","Lord Howe, Australia","Macquarie, Australia","Hobart, Australia","Melbourne, Australia","Sydney, Australia","Pohnpei, Micronesia (Federated States of)","Kosrae, Micronesia (Federated States of)","Noumea, New Caledonia","Bougainville, Papua New Guinea","Magadan, Russian Federation","Sakhalin, Russian Federation","Srednekolymsk, Russian Federation","Guadalcanal, Solomon Islands","Efate, Vanuatu"];
|
||||
case 600: return ["DumontDUrville, Antarctica","Brisbane, Australia","Lindeman, Australia","Chuuk, Micronesia (Federated States of)","Guam, Guam","Saipan, Northern Mariana Islands","Port Moresby, Papua New Guinea","Vladivostok, Russian Federation","Ust-Nera, Russian Federation"];
|
||||
case 540: return ["Jayapura, Indonesia","Tokyo, Japan","Pyongyang, Korea (Democratic People's Republic of)","Seoul, Korea, Republic of","Palau, Palau","Chita, Russian Federation","Yakutsk, Russian Federation","Khandyga, Russian Federation","Dili, Timor-Leste"];
|
||||
case 480: return ["Perth, Australia","Brunei, Brunei Darussalam","Shanghai, China","Hong Kong, Hong Kong","Makassar, Indonesia","Ulaanbaatar, Mongolia","Choibalsan, Mongolia","Macau, Macao","Kuala Lumpur, Malaysia","Kuching, Malaysia","Manila, Philippines","Irkutsk, Russian Federation","Singapore, Singapore","Taipei, Taiwan, Province of China"];
|
||||
case 420: return ["Davis, Antarctica","Christmas, Christmas Island","Jakarta, Indonesia","Pontianak, Indonesia","Phnom Penh, Cambodia","Vientiane, Lao People's Democratic Republic","Hovd, Mongolia","Novosibirsk, Russian Federation","Barnaul, Russian Federation","Tomsk, Russian Federation","Novokuznetsk, Russian Federation","Krasnoyarsk, Russian Federation","Bangkok, Thailand","Ho Chi_Minh, Viet Nam"];
|
||||
case 360: return ["Vostok, Antarctica","Dhaka, Bangladesh","Thimphu, Bhutan","Urumqi, China","Chagos, British Indian Ocean Territory","Bishkek, Kyrgyzstan","Almaty, Kazakhstan","Qostanay, Kazakhstan","Omsk, Russian Federation"];
|
||||
case 300: return ["Mawson, Antarctica","Qyzylorda, Kazakhstan","Aqtobe, Kazakhstan","Aqtau, Kazakhstan","Atyrau, Kazakhstan","Oral, Kazakhstan","Maldives, Maldives","Karachi, Pakistan","Yekaterinburg, Russian Federation","Kerguelen, French Southern Territories","Dushanbe, Tajikistan","Ashgabat, Turkmenistan","Samarkand, Uzbekistan","Tashkent, Uzbekistan"];
|
||||
case 240: return ["Dubai, United Arab Emirates","Yerevan, Armenia","Baku, Azerbaijan","Tbilisi, Georgia","Mauritius, Mauritius","Muscat, Oman","Reunion, Réunion","Astrakhan, Russian Federation","Saratov, Russian Federation","Ulyanovsk, Russian Federation","Samara, Russian Federation","Mahe, Seychelles"];
|
||||
case 180: return ["Syowa, Antarctica","Bahrain, Bahrain","Minsk, Belarus","Djibouti, Djibouti","Asmara, Eritrea","Addis Ababa, Ethiopia","Baghdad, Iraq","Nairobi, Kenya","Comoro, Comoros","Kuwait, Kuwait","Antananarivo, Madagascar","Qatar, Qatar","Moscow, Russian Federation","Simferopol, Ukraine","Kirov, Russian Federation","Volgograd, Russian Federation","Riyadh, Saudi Arabia","Mogadishu, Somalia","Istanbul, Turkey","Dar es_Salaam, Tanzania, United Republic of","Kampala, Uganda","Aden, Yemen","Mayotte, Mayotte"];
|
||||
case 120: return ["Mariehamn, Åland Islands","Sofia, Bulgaria","Bujumbura, Burundi","Gaborone, Botswana","Lubumbashi, Congo, Democratic Republic of the","Nicosia, Cyprus","Famagusta, Cyprus","Tallinn, Estonia","Cairo, Egypt","Helsinki, Finland","Athens, Greece","Jerusalem, Israel","Amman, Jordan","Beirut, Lebanon","Maseru, Lesotho","Vilnius, Lithuania","Riga, Latvia","Tripoli, Libya","Chisinau, Moldova, Republic of","Blantyre, Malawi","Maputo, Mozambique","Windhoek, Namibia","Gaza, Palestine, State of","Hebron, Palestine, State of","Bucharest, Romania","Kaliningrad, Russian Federation","Kigali, Rwanda","Khartoum, Sudan","Juba, South Sudan","Damascus, Syrian Arab Republic","Mbabane, Eswatini","Kiev, Ukraine","Uzhgorod, Ukraine","Zaporozhye, Ukraine","Johannesburg, South Africa","Lusaka, Zambia","Harare, Zimbabwe"];
|
||||
case 60: return ["Andorra, Andorra","Tirane, Albania","Luanda, Angola","Vienna, Austria","Sarajevo, Bosnia and Herzegovina","Brussels, Belgium","Porto-Novo, Benin","Kinshasa, Congo, Democratic Republic of the","Bangui, Central African Republic","Brazzaville, Congo","Zurich, Switzerland","Douala, Cameroon","Prague, Czechia","Berlin, Germany","Busingen, Germany","Copenhagen, Denmark","Algiers, Algeria","El Aaiun, Western Sahara","Madrid, Spain","Ceuta, Spain","Paris, France","Libreville, Gabon","Gibraltar, Gibraltar","Malabo, Equatorial Guinea","Zagreb, Croatia","Budapest, Hungary","Rome, Italy","Vaduz, Liechtenstein","Luxembourg, Luxembourg","Casablanca, Morocco","Monaco, Monaco","Podgorica, Montenegro","Skopje, North Macedonia","Malta, Malta","Niamey, Niger","Lagos, Nigeria","Amsterdam, Netherlands","Oslo, Norway","Warsaw, Poland","Belgrade, Serbia","Stockholm, Sweden","Ljubljana, Slovenia","Longyearbyen, Svalbard and Jan Mayen","Bratislava, Slovakia","San Marino, San Marino","Ndjamena, Chad","Tunis, Tunisia","Vatican, Holy See"];
|
||||
case 0: return ["Troll, Antarctica","Ouagadougou, Burkina Faso","Abidjan, Côte d'Ivoire","Canary, Spain","Faroe, Faroe Islands","London, United Kingdom of Great Britain and Northern Ireland","Guernsey, Guernsey","Accra, Ghana","Danmarkshavn, Greenland","Banjul, Gambia","Conakry, Guinea","Bissau, Guinea-Bissau","Dublin, Ireland","Isle of_Man, Isle of Man","Reykjavik, Iceland","Jersey, Jersey","Monrovia, Liberia","Bamako, Mali","Nouakchott, Mauritania","Lisbon, Portugal","Madeira, Portugal","St Helena, Saint Helena, Ascension and Tristan da Cunha","Freetown, Sierra Leone","Dakar, Senegal","Sao Tome, Sao Tome and Principe","Lome, Togo"];
|
||||
default: return ["Houston, we have a bug."];
|
||||
}
|
||||
};
|
|
@ -0,0 +1,88 @@
|
|||
let fs = require('fs');
|
||||
let csv = require('csv');
|
||||
|
||||
let countries = {},
|
||||
zones = {},
|
||||
offsdict = {},
|
||||
now = Date.now(); // we need this to find zone's current DST state
|
||||
|
||||
function handleWrite(err,bytes) {
|
||||
if (err) {
|
||||
console.log(`Error writing to file ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Generating fourTwentyTz.js...");
|
||||
fs.createReadStream(__dirname+'/country.csv')
|
||||
.pipe(csv.parse())
|
||||
.on('data', (r) => {
|
||||
countries[r[0]] = r[1];
|
||||
})
|
||||
.on('end', () => {
|
||||
fs.createReadStream(__dirname+'/zone.csv')
|
||||
.pipe(csv.parse())
|
||||
.on('data', (r) => {
|
||||
let parts = r[2].replace('_',' ').split('/');
|
||||
let city = parts[parts.length-1];
|
||||
let country ='';
|
||||
if (parts.length>2) { // e.g. America/North_Dakota/New_Salem
|
||||
country = parts[1]; // e.g. North Dakota
|
||||
} else {
|
||||
country = countries[r[1]]; // e.g. United States
|
||||
}
|
||||
zones[parseInt(r[0])] = {"name": `${city}, ${country}`};
|
||||
})
|
||||
.on('end', () => {
|
||||
fs.createReadStream(__dirname+'/timezone.csv')
|
||||
.pipe(csv.parse())
|
||||
.on('data', (r) => {
|
||||
code = parseInt(r[0]);
|
||||
if (!(code in zones)) return;
|
||||
starttime = parseInt(r[2] || "0"); // Bugger. They're feeding us blanks for UTC now
|
||||
offs = parseInt(r[3]);
|
||||
if (offs<0) {
|
||||
offs += 60*60*24;
|
||||
}
|
||||
zone = zones[code];
|
||||
if (starttime<now && (!("starttime" in zone) || zone.starttime<starttime)) {
|
||||
zone.starttime = starttime;
|
||||
zone.offs = Math.floor(offs/60);
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
for (z in zones) {
|
||||
zone = zones[z];
|
||||
if (zone.offs%60) continue; // One a dem funky timezones. Ignore.
|
||||
zonelist = offsdict[zone.offs] || [];
|
||||
zonelist.push(zone.name);
|
||||
offsdict[zone.offs] = zonelist;
|
||||
}
|
||||
offsets = [];
|
||||
for (o in offsdict) {
|
||||
offsets.unshift(parseInt(o));
|
||||
}
|
||||
fs.open("fourTwentyTz.js","w", (err, fd) => {
|
||||
if (err) {
|
||||
console.log("Can't open output file");
|
||||
return;
|
||||
}
|
||||
fs.write(fd, "// Generated by mkFourTwentyTz.js\n", handleWrite);
|
||||
fs.write(fd, `// ${Date()}\n`, handleWrite);
|
||||
fs.write(fd, "// Data source: https://timezonedb.com/files/timezonedb.csv.zip\n", handleWrite);
|
||||
fs.write(fd, "exports.offsets = ", handleWrite);
|
||||
fs.write(fd, JSON.stringify(offsets), handleWrite);
|
||||
fs.write(fd, ";\n", handleWrite);
|
||||
fs.write(fd, "exports.timezones = function(offs) {\n", handleWrite);
|
||||
fs.write(fd, " switch (offs) {\n", handleWrite);
|
||||
for (i=0; i<offsets.length; i++) {
|
||||
let o = offsets[i].toString();
|
||||
fs.write(fd, ` case ${o}: return ${JSON.stringify(offsdict[o])};\n`, handleWrite);
|
||||
}
|
||||
fs.write(fd, " default: return [\"Houston, we have a bug.\"];\n", handleWrite);
|
||||
fs.write(fd, " }\n", handleWrite);
|
||||
fs.write(fd, "};\n", handleWrite);
|
||||
console.log('Done.');
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "mkfourtwentytz",
|
||||
"version": "1.0.0",
|
||||
"description": "Convert timezonedb.com CSV to fourTwentyTz.js for BangleJS ftclock app",
|
||||
"main": "mkFourTwentyTz.js",
|
||||
"scripts": {
|
||||
"make": "node mkFourTwentyTz.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"csv": "^6.0.5"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 20 KiB |
|
@ -5,3 +5,4 @@
|
|||
0.06: Add number of satellites in view and fix crash with GPS time
|
||||
0.07: Resolve one FIFO_FULL case and exit App with button press
|
||||
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
|
||||
0.09: Fix FIFO_FULL error
|
||||
|
|