Merge remote-tracking branch 'upstream/master' into wohrm-bjs2
513
apps.json
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
"id": "fwupdate",
|
||||
"name": "Firmware Update",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
|
@ -16,7 +16,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.39",
|
||||
"version": "0.40",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
@ -77,7 +77,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.14",
|
||||
"version": "0.17",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -116,7 +116,7 @@
|
|||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Display notifications/music/etc from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
|
@ -167,7 +167,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.38",
|
||||
"version": "0.40",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
@ -768,7 +768,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder (BETA)",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.04",
|
||||
"version": "0.06",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -845,7 +845,7 @@
|
|||
{
|
||||
"id": "weather",
|
||||
"name": "Weather",
|
||||
"version": "0.13",
|
||||
"version": "0.15",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
@ -936,7 +936,7 @@
|
|||
"id": "widbatpc",
|
||||
"name": "Battery Level Widget (with percentage)",
|
||||
"shortName": "Battery Widget",
|
||||
"version": "0.14",
|
||||
"version": "0.16",
|
||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
@ -971,7 +971,7 @@
|
|||
{
|
||||
"id": "widbt",
|
||||
"name": "Bluetooth Widget",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Show the current Bluetooth connection status in the top right of the clock",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
@ -1040,16 +1040,19 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "boot",
|
||||
"type": "app",
|
||||
"tags": "health,bluetooth",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthrm.app.js","url":"bthrm.js"},
|
||||
{"name":"bthrm.recorder.js","url":"recorder.js"},
|
||||
{"name":"bthrm.boot.js","url":"boot.js"},
|
||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true}
|
||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"bthrm.settings.js","url":"settings.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1148,7 +1151,7 @@
|
|||
{
|
||||
"id": "qrcode",
|
||||
"name": "Custom QR Code",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Use this to upload a customised QR code to Bangle.js",
|
||||
"icon": "app.png",
|
||||
"tags": "qrcode",
|
||||
|
@ -1324,7 +1327,7 @@
|
|||
"icon": "gesture.png",
|
||||
"type": "app",
|
||||
"tags": "gesture,ai",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"gesture.app.js","url":"gesture.js"},
|
||||
{"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
|
||||
|
@ -1501,7 +1504,7 @@
|
|||
{
|
||||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"version": "0.05",
|
||||
"version": "0.08",
|
||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
|
@ -1590,7 +1593,7 @@
|
|||
{
|
||||
"id": "widpedom",
|
||||
"name": "Pedometer widget",
|
||||
"version": "0.20",
|
||||
"version": "0.22",
|
||||
"description": "Daily pedometer widget",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
@ -1750,8 +1753,9 @@
|
|||
"icon": "grocery.png",
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors,shopping,list",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"custom": "grocery.html",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"grocery.app.js","url":"app.js"},
|
||||
{"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
|
||||
|
@ -2028,7 +2032,7 @@
|
|||
"id": "chronowid",
|
||||
"name": "Chrono Widget",
|
||||
"shortName": "Chrono Widget",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Chronometer (timer) which runs as widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,widget",
|
||||
|
@ -2185,7 +2189,7 @@
|
|||
"id": "calculator",
|
||||
"name": "Calculator",
|
||||
"shortName": "Calculator",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||
"icon": "calculator.png",
|
||||
"screenshots": [{"url":"screenshot_calculator.png"}],
|
||||
|
@ -2430,7 +2434,7 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.04",
|
||||
"version": "0.06",
|
||||
"description": "Simple calendar",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
|
@ -2971,11 +2975,11 @@
|
|||
{
|
||||
"id": "cprassist",
|
||||
"name": "CPR Assist",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Provides assistance while performing a CPR",
|
||||
"icon": "cprassist-icon.png",
|
||||
"tags": "tool,firstaid",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}],
|
||||
|
@ -3533,7 +3537,7 @@
|
|||
"id": "mclockplus",
|
||||
"name": "Morph Clock+",
|
||||
"shortName": "Morph Clock+",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Morphing Clock with more readable seconds and date and additional stopwatch",
|
||||
"icon": "mclockplus.png",
|
||||
"type": "clock",
|
||||
|
@ -3987,11 +3991,12 @@
|
|||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"doztime.app.js","url":"app.js"},
|
||||
{"name":"doztime.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]},
|
||||
{"name":"doztime.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]},
|
||||
{"name":"doztime.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
@ -4065,7 +4070,7 @@
|
|||
{
|
||||
"id": "hcclock",
|
||||
"name": "Hi-Contrast Clock",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.",
|
||||
"icon": "hcclock-icon.png",
|
||||
"type": "clock",
|
||||
|
@ -4211,13 +4216,13 @@
|
|||
"id": "pastel",
|
||||
"name": "Pastel Clock",
|
||||
"shortName": "Pastel",
|
||||
"version": "0.09",
|
||||
"description": "A Configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||
"version": "0.10",
|
||||
"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"},
|
||||
"screenshots": [{"url":"screenshot_pastel.png"}],
|
||||
"dependencies": {"mylocation":"app", "widpedom":"app","weather":"app"},
|
||||
"screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock, weather, tool",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
|
@ -4238,8 +4243,9 @@
|
|||
{
|
||||
"id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"version": "0.03",
|
||||
"description": "A simple clock using the bold Anton font.",
|
||||
"version": "0.04",
|
||||
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
|
@ -4248,8 +4254,10 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"antonclk.app.js","url":"app.js"},
|
||||
{"name":"antonclk.settings.js","url":"settings.js"},
|
||||
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"antonclk.json"}]
|
||||
},
|
||||
{
|
||||
"id": "waveclk",
|
||||
|
@ -4383,7 +4391,7 @@
|
|||
{
|
||||
"id": "gpstouch",
|
||||
"name": "GPS Touch",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A touch based GPS watch, shows OS map reference",
|
||||
"icon": "gpstouch.png",
|
||||
"screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
|
||||
|
@ -4473,7 +4481,7 @@
|
|||
"name": "A Battery Widget (with percentage)",
|
||||
"shortName":"A Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"1.02",
|
||||
"version":"1.03",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
@ -4488,7 +4496,7 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.07",
|
||||
"version":"0.09",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
|
@ -4622,7 +4630,7 @@
|
|||
"shortName":"93 Dub",
|
||||
"icon": "93dub.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
@ -4653,7 +4661,7 @@
|
|||
"id": "sensible",
|
||||
"name": "SensiBLE",
|
||||
"shortName": "SensiBLE",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Collect, display and advertise real-time sensor data.",
|
||||
"icon": "sensible.png",
|
||||
"screenshots": [
|
||||
|
@ -4698,6 +4706,8 @@
|
|||
"tags": "tool,timer",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"a_speech_timer.app.js","url":"app.js"},
|
||||
{"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true}
|
||||
|
@ -4709,7 +4719,7 @@
|
|||
"icon": "mylocation.png",
|
||||
"type": "app",
|
||||
"screenshots": [{"url":"screenshot_1.png"}],
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
|
||||
"readme": "README.md",
|
||||
"tags": "tool,utility",
|
||||
|
@ -4726,7 +4736,7 @@
|
|||
"id": "pebble",
|
||||
"name": "Pebble Clock",
|
||||
"shortName": "Pebble",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "A pebble style clock to keep the rebellion going",
|
||||
"dependencies": {"widpedom":"app"},
|
||||
"readme": "README.md",
|
||||
|
@ -4734,7 +4744,7 @@
|
|||
"screenshots": [{"url":"pebble_screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"pebble.app.js","url":"pebble.app.js"},
|
||||
{"name":"pebble.settings.js","url":"pebble.settings.js"},
|
||||
|
@ -4768,7 +4778,7 @@
|
|||
"screenshots": [{"url":"screenshot_widbata_1.png"}],
|
||||
"version":"0.01",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Shows the current battery level status in the top right using the clocks colour theme",
|
||||
"tags": "widget,battery",
|
||||
|
@ -4779,7 +4789,7 @@
|
|||
{
|
||||
"id": "weatherClock",
|
||||
"name": "Weather Clock",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screens/screen1.png"}],
|
||||
|
@ -4839,6 +4849,25 @@
|
|||
{"name": "flow.img", "url": "app-icon.js","evaluate": true }
|
||||
]
|
||||
},
|
||||
{ "id": "tinydraw",
|
||||
"name": "TinyDraw",
|
||||
"shortName":"TinyDraw",
|
||||
"version":"0.01",
|
||||
"type": "app",
|
||||
"description": "Draw stuff in your wrist",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
"tags": "tools, keyboard, text, scribble",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"tinydraw.app.js","url":"app.js"},
|
||||
{"name":"tinydraw.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"screenshots":[
|
||||
{ "url":"screenshot.png" }
|
||||
]
|
||||
},
|
||||
{ "id": "scribble",
|
||||
"name": "Scribble",
|
||||
"shortName":"Scribble",
|
||||
|
@ -4862,7 +4891,7 @@
|
|||
"id": "ptlaunch",
|
||||
"name": "Pattern Launcher",
|
||||
"shortName": "Pattern Launcher",
|
||||
"version": "0.11",
|
||||
"version": "0.13",
|
||||
"description": "Directly launch apps from the clock screen with custom patterns.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"manage_patterns_light.png"}],
|
||||
|
@ -4876,11 +4905,25 @@
|
|||
],
|
||||
"data": [{"name":"ptlaunch.patterns.json"}]
|
||||
},
|
||||
{ "id": "slimehunt",
|
||||
"name": "Slime Hunt",
|
||||
"shortName":"SlimeHunt",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"description": "Fight against slimes in turn based combat, try to get the highscore!",
|
||||
"tags": "rpg,slime",
|
||||
"supports" : ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"slimehunt.app.js","url":"app.js"},
|
||||
{"name":"slimehunt.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
"shortName": "Rebble",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||
"readme": "README.md",
|
||||
"icon": "rebble.png",
|
||||
|
@ -4932,10 +4975,12 @@
|
|||
"id":"awairmonitor",
|
||||
"name":"Awair Monitor",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"version":"0.01",
|
||||
"version":"0.03",
|
||||
"description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
|
||||
"tags": "tool,health",
|
||||
"type": "clock",
|
||||
"tags": "clock,tool,health",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
|
@ -4946,7 +4991,7 @@
|
|||
{ "id": "pooqround",
|
||||
"name": "pooq Round watch face",
|
||||
"shortName":"pooq Round",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A 24 hour analogue watchface with high legibility and a novel style.",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
|
@ -4964,8 +5009,8 @@
|
|||
},
|
||||
{
|
||||
"id": "coretemp",
|
||||
"name": "Core Temp Display",
|
||||
"version": "0.01",
|
||||
"name": "CoreTemp",
|
||||
"version": "0.02",
|
||||
"description": "Display CoreTemp device sensor data",
|
||||
"icon": "coretemp.png",
|
||||
"type": "app",
|
||||
|
@ -4973,10 +5018,14 @@
|
|||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"coretemp.boot.js","url":"boot.js"},
|
||||
{"name":"coretemp.wid.js","url":"widget.js"},
|
||||
{"name":"coretemp.app.js","url":"coretemp.js"},
|
||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true}
|
||||
]
|
||||
{"name":"coretemp.settings.js","url":"settings.js"},
|
||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
|
||||
{"name":"coretemp.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"coretemp.json","url":"app-settings.json"}],
|
||||
"screenshots": [{"url":"screenshot.png"}]
|
||||
},
|
||||
{
|
||||
"id": "showimg",
|
||||
|
@ -5030,9 +5079,10 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"dependencies": {"widpedom":"app"},
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
@ -5048,6 +5098,22 @@
|
|||
{"name":"circlesclock.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version":"0.01",
|
||||
"icon": "app.png",
|
||||
"description": "A Minimalist clockface with large Digits. Looks best with the dark theme",
|
||||
"screenshots" : [{"url":"screenshot.png"}],
|
||||
"tags": "clock",
|
||||
"allow_emulator":true,
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"contourclock.app.js","url":"app.js"},
|
||||
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ltherm",
|
||||
"name": "Localized Thermometer",
|
||||
|
@ -5063,5 +5129,342 @@
|
|||
{"name":"ltherm.app.js","url":"app.js"},
|
||||
{"name":"ltherm.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "presentor",
|
||||
"name": "Presentor",
|
||||
"version": "3.0",
|
||||
"description": "Use your Bangle to present!",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "tool,bluetooth",
|
||||
"interface": "interface.html",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"presentor.app.js","url":"app.js"},
|
||||
{"name":"presentor.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"presentor.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "slash",
|
||||
"name": "Slash Watch",
|
||||
"shortName":"Slash",
|
||||
"icon": "slash.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.01",
|
||||
"description": "Slash Watch based on Pebble watch face by Nikki.",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"supports":["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"slash.app.js","url":"app.js"},
|
||||
{"name":"slash.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "promenu",
|
||||
"name": "Pro Menu",
|
||||
"version": "0.02",
|
||||
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
|
||||
"icon": "icon.png",
|
||||
"type": "boot",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"pro-menu-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"promenu.boot.js","url":"boot.js","supports": ["BANGLEJS"]},
|
||||
{"name":"promenu.boot.js","url":"bootb2.js","supports": ["BANGLEJS2"]},
|
||||
{"name":"promenu.img","url":"promenuIcon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "touchtimer",
|
||||
"name": "Touch Timer",
|
||||
"shortName": "Touch Timer",
|
||||
"version": "0.02",
|
||||
"description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.",
|
||||
"icon": "app.png",
|
||||
"tags": "tools",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"0_light_timer_edit.png"},{"url":"1_light_timer_ready.png"},{"url":"2_light_timer_running.png"},{"url":"3_light_timer_finished.png"}],
|
||||
"storage": [
|
||||
{ "name": "touchtimer.app.js", "url": "app.js" },
|
||||
{ "name":"touchtimer.settings.js", "url":"settings.js"},
|
||||
{ "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true }
|
||||
],
|
||||
"data": [{"name":"touchtimer.data.json"}]
|
||||
},
|
||||
{
|
||||
"id": "teatimer",
|
||||
"name": "Tea Timer",
|
||||
"version": "1.00",
|
||||
"description": "A simple timer. You can easyly set up the time.",
|
||||
"icon": "teatimer.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"teatimer.app.js","url":"app.js"},
|
||||
{"name":"teatimer.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"screenshots": [
|
||||
{"url":"TeatimerStart.jpg"},
|
||||
{"url":"TeatimerHelp.jpg"},
|
||||
{"url":"TeatimerRun.jpg"},
|
||||
{"url":"TeatimerUp.jpg"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "swp2clk",
|
||||
"name": "Swipe back to the Clock",
|
||||
"shortName": "Swipe to Clock",
|
||||
"version": "0.01",
|
||||
"description": "Let's you swipe from left to right on any app to return back to the clock face. Please configure in the settings app after installing to activate, since its disabled by default.",
|
||||
"icon": "app.png",
|
||||
"type": "boot",
|
||||
"tags": "tools",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "swp2clk.boot.js", "url": "boot.js" },
|
||||
{"name":"swp2clk.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"swp2clk.data.json"}]
|
||||
},
|
||||
{
|
||||
"id":"colorwheel",
|
||||
"name":"Color Wheel",
|
||||
"tags":"app,tool",
|
||||
"version":"0.01",
|
||||
"description":"a tappable wheel of good-looking colors",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"icon":"colorwheel.png",
|
||||
"storage": [
|
||||
{"name":"colorwheel.app.js","url":"app.js"},
|
||||
{"name":"colorwheel.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "minimal_clock",
|
||||
"name": "Minimal Analog Clock",
|
||||
"shortName":"Minimal Clock",
|
||||
"version":"0.03",
|
||||
"description": "a minimal analog clock - just with some hands and no clock face",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"minimal_clock.app.js","url":"app.js"},
|
||||
{"name":"minimal_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "simple_clock",
|
||||
"name": "Simple Analog Clock",
|
||||
"shortName":"Simple Clock",
|
||||
"version":"0.02",
|
||||
"description": "a simple, yet stylish, analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"simple_clock.app.js","url":"app.js"},
|
||||
{"name":"simple_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "colorful_clock",
|
||||
"name": "Colorful Analog Clock",
|
||||
"shortName":"Colorful Clock",
|
||||
"version":"0.02",
|
||||
"description": "a colorful analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"colorful_clock.app.js","url":"app.js"},
|
||||
{"name":"colorful_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "themesetter",
|
||||
"name": "Theme Setter",
|
||||
"shortName":"Theme Setter",
|
||||
"version":"0.04",
|
||||
"description": "a comfortable way to configure theme colors",
|
||||
"icon": "app-icon.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"themesetter.app.js","url":"app.js"},
|
||||
{"name":"themesetter.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widviztime",
|
||||
"name": "Widget Autohide Widget",
|
||||
"shortName": "Viz Time Widget",
|
||||
"version": "0.01",
|
||||
"description": "The widgets will be shown for four seconds after the device is unlocked.",
|
||||
"icon": "eye.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widviztime.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "supf",
|
||||
"name": "Simple Clock with Date",
|
||||
"shortName": "supf Clock",
|
||||
"version": "0.01",
|
||||
"description": "Simple Clock with seconds and date in custom language. Install 'Languages' to get localized names.",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot_supf.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"supf.app.js","url":"app.js"},
|
||||
{"name":"supf.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "andark",
|
||||
"name": "Analog Dark",
|
||||
"shortName":"AnDark",
|
||||
"version":"0.04",
|
||||
"description": "analog clock face without disturbing widgets",
|
||||
"icon": "andark_icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"andark.app.js","url":"app.js"},
|
||||
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "diract",
|
||||
"name": "DirAct",
|
||||
"shortName": "DirAct",
|
||||
"version": "0.01",
|
||||
"description": "Proximity interaction detection.",
|
||||
"icon": "diract.png",
|
||||
"type": "app",
|
||||
"tags": "tool,sensors",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
"allow_emulator": false,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "diract.app.js", "url": "diract.js" },
|
||||
{ "name": "diract.img", "url": "diract-icon.js", "evaluate": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sonicclk",
|
||||
"name": "Sonic Clock",
|
||||
"version": "1.01",
|
||||
"description": "A classic sonic clock featuring run, stop and wait animations.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"sonicclk.app.js","url":"app.js"},
|
||||
{"name":"sonicclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "touchmenu",
|
||||
"name": "TouchMenu",
|
||||
"version": "0.01",
|
||||
"description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2",
|
||||
"screenshots": [{"url":"touchmenu.gif"}],
|
||||
"icon": "touchmenu.png",
|
||||
"type": "bootloader",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"touchmenu.boot.js","url":"touchmenu.boot.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "puzzle15",
|
||||
"name": "15 puzzle",
|
||||
"version": "0.05",
|
||||
"description": "A 15 puzzle game with drag gesture interface",
|
||||
"readme":"README.md",
|
||||
"icon": "puzzle15.app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"puzzle15.app.js","url":"puzzle15.app.js"},
|
||||
{"name":"puzzle15.settings.js","url":"puzzle15.settings.js"},
|
||||
{"name":"puzzle15.img","url":"puzzle15.app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"puzzle15.json"}]
|
||||
},
|
||||
{
|
||||
"id": "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",
|
||||
"screenshots": [{"url":"flipper.png"}],
|
||||
"icon": "flipper.png",
|
||||
"type": "app",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"flipper.app.js","url":"flipper.app.js"},
|
||||
{"name":"flipper.img","url":"flipper.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "ruuviwatch",
|
||||
"name": "Ruuvi Watch",
|
||||
"shortName":"Ruuvi Watch",
|
||||
"icon": "ruuviwatch.png",
|
||||
"version":"1.01",
|
||||
"description": "Keep an eye on RuuviTag devices (https://ruuvi.com). Only shows RuuviTags using the v5 format.",
|
||||
"readme":"README.md",
|
||||
"tags": "bluetooth",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"ruuviwatch.app.js","url":"ruuviwatch.app.js"},
|
||||
{"name":"ruuviwatch.img","url":"ruuviwatch.app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Code style cleanup
|
||||
0.04: Set 00:00 to 12:00 for 12 hour time
|
||||
0.05: Display time, even on Thursday
|
||||
0.06: Fix light theme issue, where widgets would end up on a light strip
|
||||
|
|
|
@ -122,7 +122,13 @@ function draw(){
|
|||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This watch is mostly dark, it does not make sense to respect the
|
||||
* light theme as you end up with a white strip at the top for the
|
||||
* widgets and black watch. So set the colours to the dark theme.
|
||||
*
|
||||
*/
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
draw();
|
||||
|
||||
//the following section is also from waveclk
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.01: Release
|
||||
0.02: Rename app
|
||||
0.03: Add type "clock"
|
||||
0.04: changed update cylce, when locked
|
|
@ -0,0 +1,10 @@
|
|||
# Analog Clock
|
||||
|
||||
## Features
|
||||
|
||||
* second hand
|
||||
* date
|
||||
* battery percantage
|
||||
* no widgets
|
||||
|
||||

|
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.6 KiB |
|
@ -0,0 +1,125 @@
|
|||
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
||||
let zahlpos=[];
|
||||
let unlock = false;
|
||||
|
||||
function zeiger(len,dia,tim){
|
||||
const x =c.x+ Math.cos(tim)*len/2,
|
||||
y =c.y + Math.sin(tim)*len/2,
|
||||
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
||||
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
||||
return pol;
|
||||
|
||||
}
|
||||
|
||||
function draw(){
|
||||
const d=new Date();
|
||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||
//draw black rectangle in the middle to clear screen from scale and hands
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
g.setColor(1,1,1);
|
||||
|
||||
if(h>12){
|
||||
h=h-12;
|
||||
}
|
||||
//calculates the position of the minute, second and hour hand
|
||||
h=2*Math.PI/12*(h+m/60)-Math.PI/2;
|
||||
//more accurate
|
||||
//m=2*Math.PI/60*(m+s/60)-Math.PI/2;
|
||||
m=2*Math.PI/60*(m)-Math.PI/2;
|
||||
|
||||
s=2*Math.PI/60*s-Math.PI/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",10);
|
||||
let dateStr = " "+require("locale").date(d)+" ";
|
||||
g.drawString(dateStr, c.x, c.y+20, true);
|
||||
// g.drawString(d.getDate(),1.4*c.x,c.y,true);
|
||||
g.drawString(Math.round(E.getBattery()/5)*5+"%",c.x,c.y+40,true);
|
||||
drawlet();
|
||||
//g.setColor(1,0,0);
|
||||
const hz = zeiger(100,5,h);
|
||||
g.fillPoly(hz,true);
|
||||
// g.setColor(1,1,1);
|
||||
const minz = zeiger(150,5,m);
|
||||
g.fillPoly(minz,true);
|
||||
if (unlock){
|
||||
const sekz = zeiger(150,2,s);
|
||||
g.fillPoly(sekz,true);
|
||||
}
|
||||
g.fillCircle(c.x,c.y,4);
|
||||
|
||||
|
||||
|
||||
}
|
||||
//draws the scale once the app is startet
|
||||
function drawScale(){
|
||||
for(let i=-14;i<47;i++){
|
||||
const win=i*2*Math.PI/60;
|
||||
let d=2;
|
||||
if(i%5==0){d=5;}
|
||||
g.fillPoly(zeiger(300,d,win),true);
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
g.setColor(1,1,1);
|
||||
}
|
||||
}
|
||||
|
||||
//draws the numbers on the screen
|
||||
|
||||
function drawlet(){
|
||||
g.setFont("Vector",20);
|
||||
for(let i = 0;i<12;i++){
|
||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]);
|
||||
}
|
||||
}
|
||||
//calcultes the Position of the numbers when app starts and saves them in an array
|
||||
function setlet(){
|
||||
let sk=1;
|
||||
for(let i=-10;i<50;i+=5){
|
||||
let win=i*2*Math.PI/60;
|
||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||
if(sk==3){xsk-=10;}
|
||||
if(sk==6){ysk-=10;}
|
||||
if(sk==9){xsk+=10;}
|
||||
if(sk==12){ysk+=10;}
|
||||
if(sk==10){xsk+=3;}
|
||||
zahlpos.push([sk,xsk,ysk]);
|
||||
sk+=1;
|
||||
}
|
||||
}
|
||||
setlet();
|
||||
// Clear the screen once, at startup
|
||||
g.setBgColor(0,0,0);
|
||||
g.clear();
|
||||
drawScale();
|
||||
draw();
|
||||
|
||||
let secondInterval= setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}else{
|
||||
}
|
||||
});
|
||||
Bangle.on('lock',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (!on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
unlock = true;
|
||||
draw(); // draw immediately
|
||||
}else{
|
||||
secondInterval = setInterval(draw, 60000);
|
||||
unlock = false;
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIEBoUAiAKCgUCBQUEColEAYUQhAmKCwgeCAAcCgEDjwEBkEAg8TBocNgYFDh8GAYMDxkPjEA8EAwkHJgIcBAoPfAoYWCBYYFIgfvAoX4FYRJEAp9gAomYNAOAArPwAogAC4AFiRoIFJLgIFJuADCg//Q4U//4FDj4FEAAV4Aoi0CSxBsCA=="))
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
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,0 +1,77 @@
|
|||
# Anton Clock - Large font digital watch with seconds and date
|
||||
|
||||
Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
|
||||
|
||||
## Features
|
||||
|
||||
The basic time representation only shows hours and minutes of the current time. However, Anton clock can show additional information:
|
||||
|
||||
* Seconds can be shown, either always or only if the screen is unlocked.
|
||||
* To help easy recognition, the seconds can be coloured in blue on the Bangle.js 2.
|
||||
* Date can be shown in three different formats:
|
||||
* ISO-8601: 2021-12-19
|
||||
* short local format: 19/12/2021, 19.12.2021
|
||||
* long local format: DEC 19 2021
|
||||
* Weekday can be shown (on seconds screen only instead of year)
|
||||
|
||||
## Usage
|
||||
|
||||
Install Anton clock through the Bangle.js app loader.
|
||||
Configure it through the default Bangle.js configuration mechanism
|
||||
(Settings app, "Apps" menu, "Anton clock" submenu).
|
||||
If you like it, make it your default watch face
|
||||
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
||||
|
||||
## Configuration
|
||||
|
||||
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
||||
Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu.
|
||||
You configure Anton clock through several "on/off" switches in two menus.
|
||||
|
||||
### The main menu
|
||||
|
||||
The main menu contains several settings covering Anton clock in general.
|
||||
|
||||
* **Seconds...** - Opens the submenu for configuring the presentation of the current time's seconds.
|
||||
* **Date** - Format of the date representation. Possible values are
|
||||
* **Long** - "Long" date format in the current locale. Usually with the month as name, not number.
|
||||
* **Short** - "Short" date format in the current locale. Usually with the month as number.
|
||||
* **ISO8601** - Show the date in ISO-8601 format (YYYY-MM-DD), irrespective of the current locale.
|
||||
* **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.
|
||||
* **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.
|
||||
|
||||
### The "Seconds" submenu
|
||||
|
||||
The "Seconds" submenu configures how (and if) seconds are shown on the "Anton" watch face.
|
||||
|
||||
* **Show** - Configure when the seconds should be shown at all:
|
||||
* **Never** - Seconds are never shown.
|
||||
In this case, hour and minute are a bit more centered on the screen and the clock will always only update every minute.
|
||||
This saves battery power.
|
||||
* **Unlocked** - Seconds are shown if the display is unlocked.
|
||||
On locked displays, only hour, minutes, date and optionally the weekday are shown.
|
||||
_This option is highly recommended on the Bangle.js 2!_
|
||||
* **Always** - Seconds are _always_ shown, irrespective of the display's unlock state.
|
||||
_Enabling this option increases power consumption as the watch face will update once per second instead of once per minute._
|
||||
* **With ":"** - If enabled, a colon ":" is prepended to the seconds.
|
||||
This resembles the usual time representation "hh:mm:ss", even though the seconds are printed on a separate line.
|
||||
* **Color** - If enabled, seconds are shown in blue instead of black.
|
||||
If the date is shown on the seconds screen, it is colored read instead of black.
|
||||
This make the visual orientation much easier on the watch face.
|
||||
* **Date** - It is possible to show the date together with the seconds:
|
||||
* **No** - Date is _not_ shown in the seconds screen.
|
||||
In this case, the seconds are centered below hour and minute.
|
||||
* **Year** - Date is shown with day, month, and year. If "Date" in the main settings is configured to _ISO8601_, this is used here, too. Otherwise, the short local format is used.
|
||||
* **Weekday** - Date is shown with day, month, and weekday.
|
||||
|
||||
The date is coloured in red if the "Coloured" option is chosen.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Anton clock makes use of core Bangle.js 2 features (coloured display, display lock state). It also runs on the Bangle.js 1 but these features are not available there due to hardware restrictions.
|
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,99 @@
|
|||
// Settings menu for the enhanced Anton clock
|
||||
|
||||
(function(back) {
|
||||
var FILE = "antonclk.json";
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
secondsOnUnlock: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(startvalue, writer, values) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
writer(values[v]);
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(settings[name], v => settings[name] = v, values);
|
||||
}
|
||||
|
||||
var mainmenu = {
|
||||
"": {
|
||||
"title": "Anton clock"
|
||||
},
|
||||
"< Back": () => back(),
|
||||
"Seconds...": () => E.showMenu(secmenu),
|
||||
"Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]),
|
||||
"Show Weekday": {
|
||||
value: (settings.weekDay !== undefined ? settings.weekDay : true),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.weekDay = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Uppercase": {
|
||||
value: (settings.upperCase !== undefined ? settings.upperCase : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.upperCase = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Vector font": {
|
||||
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.vectorFont = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Submenu
|
||||
var secmenu = {
|
||||
"": {
|
||||
"title": "Show seconds..."
|
||||
},
|
||||
"< Back": () => E.showMenu(mainmenu),
|
||||
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
|
||||
"With \":\"": {
|
||||
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.secondsWithColon = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Color": {
|
||||
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.secondsColoured = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"])
|
||||
};
|
||||
|
||||
// Actually display the menu
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
|
||||
// end of file
|
|
@ -1 +1,3 @@
|
|||
0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11)
|
||||
0.02: The app is now a clock, the data is greyed after the connection is lost (2021/12/22)
|
||||
0.03: Set the Awair's IP directly on the webpage (2021/12/27)
|
||||
|
|
|
@ -5,11 +5,10 @@ Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awai
|
|||
* What you need:
|
||||
* A BangleJS 2
|
||||
* An Awair device [with local API enabled](https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature)
|
||||
* The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrive the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
|
||||
* The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrieve the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
|
||||
* How to get started
|
||||
* Open awair_to_bangle.html with a text/code editor and input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
* Launch the Awair Monitor app on your BangleJS
|
||||
* Open awair_to_bangle.html on Chrome and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
|
||||
* Open awair_to_bangle.html on Chrome (desktop or Android), input the IP address of your Awair device, and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
|
||||
* Once connected to the watch with the app running, the watch app is updated once per second
|
||||
|
||||

|
||||
|
|
|
@ -30,6 +30,8 @@ var bt_temp_history = new Array(10).fill(0);
|
|||
|
||||
var internal_last_update = -1;
|
||||
|
||||
var display_frozen = false;
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
|
||||
|
@ -47,14 +49,8 @@ function draw() {
|
|||
g.drawString("Humi", 125, 100);
|
||||
g.drawString("Temp", 160, 100);
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(""+bt_current_co2, 18, 110);
|
||||
g.drawString(""+bt_current_voc, 53, 110);
|
||||
g.drawString(""+bt_current_pm25, 88, 110);
|
||||
g.drawString(""+bt_current_humi, 123, 110);
|
||||
g.drawString(""+bt_current_temp, 158, 110);
|
||||
|
||||
if (last_update != bt_last_update) {
|
||||
display_frozen = false;
|
||||
last_update = bt_last_update;
|
||||
internal_last_update = last_update;
|
||||
if (last_update % 10 == 0) {
|
||||
|
@ -65,16 +61,29 @@ function draw() {
|
|||
bt_temp_history.shift(); bt_temp_history.push(bt_current_temp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (internal_last_update == -1) {
|
||||
g.drawString("Waiting for connection", 88, 164);
|
||||
} else if (internal_last_update > last_update + 5) {
|
||||
} else if ((internal_last_update > last_update + 5) && (internal_last_update < last_update + 60)) {
|
||||
g.drawString("Trying to reconnect since " + (internal_last_update - last_update), 88, 164);
|
||||
} else if (internal_last_update > last_update + 5) {
|
||||
display_frozen = true;
|
||||
g.drawString("Waiting for connection", 88, 164);
|
||||
}
|
||||
|
||||
if (display_frozen) { g.setColor("#888"); }
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(""+bt_current_co2, 18, 110);
|
||||
g.drawString(""+bt_current_voc, 53, 110);
|
||||
g.drawString(""+bt_current_pm25, 88, 110);
|
||||
g.drawString(""+bt_current_humi, 123, 110);
|
||||
g.drawString(""+bt_current_temp, 158, 110);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
// max height = 32
|
||||
if (display_frozen) { g.setColor("#888"); }
|
||||
|
||||
// max height = 32
|
||||
g.drawLine(10+i*2, 150-(Math.min(Math.max(bt_co2_history[i],400), 1200)-400)/25, 10+i*2, 150);
|
||||
g.drawLine(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
|
||||
g.drawLine(80+i*2, 150-(Math.min(Math.max(bt_pm25_history[i],0), 32)-0)/1, 80+i*2, 150);
|
||||
|
@ -91,6 +100,7 @@ function draw() {
|
|||
}
|
||||
|
||||
// init
|
||||
Bangle.setUI("clock");
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
// Don't forget to enable the Local API on your Awair before using this
|
||||
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
|
||||
const awair_ip_1 = "192.168.2.2"; // <- INPUT YOUR AWAIR IP ADDRESS HERE
|
||||
const awair_name_1 = "Awair";
|
||||
|
||||
var bt_connection;
|
||||
var is_connected = false;
|
||||
var reconnect_counter = 5;
|
||||
var reconnect_attempt_counter = 1;
|
||||
var is_chart_started = false;
|
||||
|
||||
window.onload = function() {
|
||||
function initChart() {
|
||||
var chart_co2;
|
||||
var chart_voc;
|
||||
var chart_pm;
|
||||
|
@ -23,6 +23,8 @@ window.onload = function() {
|
|||
var chart_humidity;
|
||||
var dataPoints_1 = [];
|
||||
var posx = 0;
|
||||
|
||||
var awair_ip_1 = document.getElementById('inputawairip').value;
|
||||
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
|
@ -105,11 +107,12 @@ window.onload = function() {
|
|||
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
||||
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
||||
let last_update = dataPoints_1['temp'].length-1;
|
||||
if (is_connected && bt_connection.isOpen) {
|
||||
|
||||
if (is_connected && bt_connection && bt_connection.isOpen) {
|
||||
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_last_update='+last_update+';\n');
|
||||
|
||||
console.log("Sent data through Bluetooth");
|
||||
} else if (is_connected && !bt_connection.isOpen) {
|
||||
} else if (is_connected && bt_connection && !bt_connection.isOpen) {
|
||||
console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
|
||||
reconnect_counter--;
|
||||
|
||||
|
@ -131,7 +134,6 @@ window.onload = function() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
setTimeout(function(){updateChart()}, 1000);
|
||||
});
|
||||
}
|
||||
|
@ -148,10 +150,16 @@ function connectBT() {
|
|||
bt_connection = c;
|
||||
is_connected = true;
|
||||
reconnect_attempt_counter = 1;
|
||||
if (!is_chart_started) {
|
||||
initChart();
|
||||
is_chart_started = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function disconnectBT() {
|
||||
console.log("Disconnect Bluetooth button pressed. bt_connection value below.")
|
||||
console.log(bt_connection);
|
||||
if (is_connected && bt_connection) {
|
||||
bt_connection.close();
|
||||
is_connected = false;
|
||||
|
@ -167,23 +175,21 @@ function disconnectBT() {
|
|||
|
||||
<p style="color: #F7FAFC">
|
||||
<b>How to use</b>
|
||||
<br/><br/>
|
||||
<br><br>
|
||||
Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
<br/><br/>
|
||||
Step 2: Modify this HTML file to input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
<br/><br/>
|
||||
Step 3: Launch the Awair Monitor app on your BangleJS
|
||||
<br/><br/>
|
||||
Step 4: Click "Connect BangleJS"
|
||||
<br/><br/>
|
||||
Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs
|
||||
<br><br>
|
||||
Step 2: Launch the Awair Monitor app on your BangleJS
|
||||
<br><br>
|
||||
Step 3: Input your Awair IP address and click the Connect button:
|
||||
<input type="text" id="inputawairip" value="192.168.2.1">
|
||||
<input type="button" value="Connect BangleJS" onclick="connectBT();">
|
||||
<br><br>
|
||||
Step 4: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the Bluetooth logs
|
||||
<br><br>
|
||||
Step 5: Once you are done, click the Disconnect button to properly close the Blutooth connection
|
||||
<center><button onclick="disconnectBT();">Disconnect BangleJS</button></center>
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<button onclick="connectBT();">Connect BangleJS</button>
|
||||
<button onclick="disconnectBT();">Disconnect BangleJS</button>
|
||||
</center>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
|
@ -192,4 +198,476 @@ Step 5: Optionally, open the web inspector's console (Right click > Inspector >
|
|||
<div id="chartContainer_humidity" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_temperature" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
</html>(buf));
|
||||
}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length);
|
||||
var bufView = new Uint8Array(buf);
|
||||
for (var i=0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function handleQueue() {
|
||||
if (!queue.length) return;
|
||||
var q = queue.shift();
|
||||
log(3,"Executing "+JSON.stringify(q)+" from queue");
|
||||
if (q.type == "write") puck.write(q.data, q.callback, q.callbackNewline);
|
||||
else log(1,"Unknown queue item "+JSON.stringify(q));
|
||||
}
|
||||
|
||||
function connect(callback) {
|
||||
if (!checkIfSupported()) return;
|
||||
|
||||
var connection = {
|
||||
on : function(evt,cb) { this["on"+evt]=cb; },
|
||||
emit : function(evt,data) { if (this["on"+evt]) this["on"+evt](data); },
|
||||
isOpen : false,
|
||||
isOpening : true,
|
||||
txInProgress : false
|
||||
};
|
||||
var btServer = undefined;
|
||||
var btService;
|
||||
var connectionDisconnectCallback;
|
||||
var txCharacteristic;
|
||||
var rxCharacteristic;
|
||||
var txDataQueue = [];
|
||||
var flowControlXOFF = false;
|
||||
var chunkSize = DEFAULT_CHUNKSIZE;
|
||||
|
||||
connection.close = function() {
|
||||
connection.isOpening = false;
|
||||
if (connection.isOpen) {
|
||||
connection.isOpen = false;
|
||||
connection.emit('close');
|
||||
} else {
|
||||
if (callback) callback(null);
|
||||
}
|
||||
if (btServer) {
|
||||
btServer.disconnect();
|
||||
btServer = undefined;
|
||||
txCharacteristic = undefined;
|
||||
rxCharacteristic = undefined;
|
||||
chunkSize = DEFAULT_CHUNKSIZE;
|
||||
}
|
||||
};
|
||||
|
||||
connection.write = function(data, callback) {
|
||||
if (data) txDataQueue.push({data:data,callback:callback,maxLength:data.length});
|
||||
if (connection.isOpen && !connection.txInProgress) writeChunk();
|
||||
|
||||
function writeChunk() {
|
||||
if (flowControlXOFF) { // flow control - try again later
|
||||
setTimeout(writeChunk, 50);
|
||||
return;
|
||||
}
|
||||
var chunk;
|
||||
if (!txDataQueue.length) {
|
||||
puck.writeProgress();
|
||||
return;
|
||||
}
|
||||
var txItem = txDataQueue[0];
|
||||
puck.writeProgress(txItem.maxLength - txItem.data.length, txItem.maxLength);
|
||||
if (txItem.data.length <= chunkSize) {
|
||||
chunk = txItem.data;
|
||||
txItem.data = undefined;
|
||||
} else {
|
||||
chunk = txItem.data.substr(0,chunkSize);
|
||||
txItem.data = txItem.data.substr(chunkSize);
|
||||
}
|
||||
connection.txInProgress = true;
|
||||
log(2, "Sending "+ JSON.stringify(chunk));
|
||||
txCharacteristic.writeValue(str2ab(chunk)).then(function() {
|
||||
log(3, "Sent");
|
||||
if (!txItem.data) {
|
||||
txDataQueue.shift(); // remove this element
|
||||
if (txItem.callback)
|
||||
txItem.callback();
|
||||
}
|
||||
connection.txInProgress = false;
|
||||
writeChunk();
|
||||
}).catch(function(error) {
|
||||
log(1, 'SEND ERROR: ' + error);
|
||||
txDataQueue = [];
|
||||
connection.close();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
navigator.bluetooth.requestDevice({
|
||||
filters:[
|
||||
{ namePrefix: 'Puck.js' },
|
||||
{ namePrefix: 'Pixl.js' },
|
||||
{ namePrefix: 'MDBT42Q' },
|
||||
{ namePrefix: 'RuuviTag' },
|
||||
{ namePrefix: 'iTracker' },
|
||||
{ namePrefix: 'Thingy' },
|
||||
{ namePrefix: 'Espruino' },
|
||||
{ services: [ NORDIC_SERVICE ] }
|
||||
], optionalServices: [ NORDIC_SERVICE ]}).then(function(device) {
|
||||
log(1, 'Device Name: ' + device.name);
|
||||
log(1, 'Device ID: ' + device.id);
|
||||
// Was deprecated: Should use getPrimaryServices for this in future
|
||||
//log('BT> Device UUIDs: ' + device.uuids.join('\n' + ' '.repeat(21)));
|
||||
device.addEventListener('gattserverdisconnected', function() {
|
||||
log(1, "Disconnected (gattserverdisconnected)");
|
||||
connection.close();
|
||||
});
|
||||
connection.device = device;
|
||||
connection.reconnect(callback);
|
||||
}).catch(function(error) {
|
||||
log(1, 'ERROR: ' + error);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
connection.reconnect = function(callback) {
|
||||
connection.device.gatt.connect().then(function(server) {
|
||||
log(1, "Connected");
|
||||
btServer = server;
|
||||
return server.getPrimaryService(NORDIC_SERVICE);
|
||||
}).then(function(service) {
|
||||
log(2, "Got service");
|
||||
btService = service;
|
||||
return btService.getCharacteristic(NORDIC_RX);
|
||||
}).then(function (characteristic) {
|
||||
rxCharacteristic = characteristic;
|
||||
log(2, "RX characteristic:"+JSON.stringify(rxCharacteristic));
|
||||
rxCharacteristic.addEventListener('characteristicvaluechanged', function(event) {
|
||||
var dataview = event.target.value;
|
||||
var data = ab2str(dataview.buffer);
|
||||
if (data.length > chunkSize) {
|
||||
log(2, "Received packet of length "+data.length+", increasing chunk size");
|
||||
chunkSize = data.length;
|
||||
}
|
||||
if (puck.flowControl) {
|
||||
for (var i=0;i<data.length;i++) {
|
||||
var ch = data.charCodeAt(i);
|
||||
var remove = true;
|
||||
if (ch==19) {// XOFF
|
||||
log(2,"XOFF received => pause upload");
|
||||
flowControlXOFF = true;
|
||||
} else if (ch==17) {// XON
|
||||
log(2,"XON received => resume upload");
|
||||
flowControlXOFF = false;
|
||||
} else
|
||||
remove = false;
|
||||
if (remove) { // remove character
|
||||
data = data.substr(0,i-1)+data.substr(i+1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
log(3, "Received "+JSON.stringify(data));
|
||||
connection.emit('data', data);
|
||||
});
|
||||
return rxCharacteristic.startNotifications();
|
||||
}).then(function() {
|
||||
return btService.getCharacteristic(NORDIC_TX);
|
||||
}).then(function (characteristic) {
|
||||
txCharacteristic = characteristic;
|
||||
log(2, "TX characteristic:"+JSON.stringify(txCharacteristic));
|
||||
}).then(function() {
|
||||
connection.txInProgress = false;
|
||||
connection.isOpen = true;
|
||||
connection.isOpening = false;
|
||||
isBusy = false;
|
||||
queue = [];
|
||||
callback(connection);
|
||||
connection.emit('open');
|
||||
// if we had any writes queued, do them now
|
||||
connection.write();
|
||||
}).catch(function(error) {
|
||||
log(1, 'ERROR: ' + error);
|
||||
connection.close();
|
||||
});
|
||||
};
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------
|
||||
var connection;
|
||||
/* convenience function... Write data, call the callback with data:
|
||||
callbackNewline = false => if no new data received for ~0.2 sec
|
||||
callbackNewline = true => after a newline */
|
||||
function write(data, callback, callbackNewline) {
|
||||
if (!checkIfSupported()) return;
|
||||
|
||||
let result;
|
||||
/// If there wasn't a callback function, then promisify
|
||||
if (typeof callback !== 'function') {
|
||||
callbackNewline = callback;
|
||||
|
||||
result = new Promise((resolve, reject) => callback = (value, err) => {
|
||||
if (err) reject(err);
|
||||
else resolve(value);
|
||||
});
|
||||
}
|
||||
|
||||
if (isBusy) {
|
||||
log(3, "Busy - adding Puck.write to queue");
|
||||
queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
|
||||
return result;
|
||||
}
|
||||
|
||||
var cbTimeout;
|
||||
function onWritten() {
|
||||
if (callbackNewline) {
|
||||
connection.cb = function(d) {
|
||||
var newLineIdx = connection.received.indexOf("\n");
|
||||
if (newLineIdx>=0) {
|
||||
var l = connection.received.substr(0,newLineIdx);
|
||||
connection.received = connection.received.substr(newLineIdx+1);
|
||||
connection.cb = undefined;
|
||||
if (cbTimeout) clearTimeout(cbTimeout);
|
||||
cbTimeout = undefined;
|
||||
if (callback)
|
||||
callback(l);
|
||||
isBusy = false;
|
||||
handleQueue();
|
||||
}
|
||||
};
|
||||
}
|
||||
// wait for any received data if we have a callback...
|
||||
var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
|
||||
var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
|
||||
var maxDataTime = dataWaitTime; // max time we wait after having received data
|
||||
cbTimeout = setTimeout(function timeout() {
|
||||
cbTimeout = undefined;
|
||||
if (maxTime) maxTime--;
|
||||
if (maxDataTime) maxDataTime--;
|
||||
if (connection.hadData) maxDataTime=dataWaitTime;
|
||||
if (maxDataTime && maxTime) {
|
||||
cbTimeout = setTimeout(timeout, 100);
|
||||
} else {
|
||||
connection.cb = undefined;
|
||||
if (callback)
|
||||
callback(connection.received);
|
||||
isBusy = false;
|
||||
handleQueue();
|
||||
connection.received = "";
|
||||
}
|
||||
connection.hadData = false;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
if (connection && (connection.isOpen || connection.isOpening)) {
|
||||
if (!connection.txInProgress) connection.received = "";
|
||||
isBusy = true;
|
||||
connection.write(data, onWritten);
|
||||
return result
|
||||
}
|
||||
|
||||
connection = connect(function(puck) {
|
||||
if (!puck) {
|
||||
connection = undefined;
|
||||
if (callback) callback(null);
|
||||
return;
|
||||
}
|
||||
connection.received = "";
|
||||
connection.on('data', function(d) {
|
||||
connection.received += d;
|
||||
connection.hadData = true;
|
||||
if (connection.cb) connection.cb(d);
|
||||
});
|
||||
connection.on('close', function(d) {
|
||||
connection = undefined;
|
||||
});
|
||||
isBusy = true;
|
||||
connection.write(data, onWritten);
|
||||
});
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
var puck = {
|
||||
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||||
debug : 1,
|
||||
/// Should we use flow control? Default is true
|
||||
flowControl : true,
|
||||
/// Used internally to write log information - you can replace this with your own function
|
||||
log : function(level, s) { if (level <= this.debug) console.log("<BLE> "+s)},
|
||||
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
||||
writeProgress : function (charsSent, charsTotal) {
|
||||
//console.log(charsSent + "/" + charsTotal);
|
||||
},
|
||||
/** Connect to a new device - this creates a separate
|
||||
connection to the one `write` and `eval` use. */
|
||||
connect : connect,
|
||||
/// Write to Puck.js and call back when the data is written. Creates a connection if it doesn't exist
|
||||
write : write,
|
||||
/// Evaluate an expression and call cb with the result. Creates a connection if it doesn't exist
|
||||
eval : function(expr, cb) {
|
||||
|
||||
const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
|
||||
.then(function (d) {
|
||||
try {
|
||||
return JSON.parse(d);
|
||||
} catch (e) {
|
||||
log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
|
||||
return Promise.reject(d);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (cb) {
|
||||
return void response.then(cb, (err) => cb(null, err));
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
|
||||
},
|
||||
/// Write the current time to the Puck
|
||||
setTime : function(cb) {
|
||||
var d = new Date();
|
||||
var cmd = 'setTime('+(d.getTime()/1000)+');';
|
||||
// in 1v93 we have timezones too
|
||||
cmd += 'if (E.setTimeZone) E.setTimeZone('+d.getTimezoneOffset()/-60+');\n';
|
||||
write(cmd, cb);
|
||||
},
|
||||
/// Did `write` and `eval` manage to create a connection?
|
||||
isConnected : function() {
|
||||
return connection!==undefined;
|
||||
},
|
||||
/// get the connection used by `write` and `eval`
|
||||
getConnection : function() {
|
||||
return connection;
|
||||
},
|
||||
/// Close the connection used by `write` and `eval`
|
||||
close : function() {
|
||||
if (connection)
|
||||
connection.close();
|
||||
},
|
||||
/** Utility function to fade out everything on the webpage and display
|
||||
a window saying 'Click to continue'. When clicked it'll disappear and
|
||||
'callback' will be called. This is useful because you can't initialise
|
||||
Web Bluetooth unless you're doing so in response to a user input.*/
|
||||
modal : function(callback) {
|
||||
var e = document.createElement('div');
|
||||
e.style = 'position:absolute;top:0px;left:0px;right:0px;bottom:0px;opacity:0.5;z-index:100;background:black;';
|
||||
e.innerHTML = '<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-family: Sans-Serif;font-size:400%;color:white;">Click to Continue...</div>';
|
||||
e.onclick = function(evt) {
|
||||
callback();
|
||||
evt.preventDefault();
|
||||
document.body.removeChild(e);
|
||||
};
|
||||
document.body.appendChild(e);
|
||||
}
|
||||
};
|
||||
return puck;
|
||||
}));
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// Don't forget to enable the Local API on your Awair before using this
|
||||
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
|
||||
const awair_name_1 = "Awair";
|
||||
|
||||
var bt_connection;
|
||||
var is_connected = false;
|
||||
var reconnect_counter = 5;
|
||||
var reconnect_attempt_counter = 1;
|
||||
var is_chart_started = false;
|
||||
|
||||
function initChart() {
|
||||
var chart_co2;
|
||||
var chart_voc;
|
||||
var chart_pm;
|
||||
var chart_temperature;
|
||||
var chart_humidity;
|
||||
var dataPoints_1 = [];
|
||||
var posx = 0;
|
||||
|
||||
var awair_ip_1 = document.getElementById('inputawairip').value;
|
||||
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||
});
|
||||
|
||||
posx++;
|
||||
|
||||
chart_co2 = new CanvasJS.Chart("chartContainer_co2",{
|
||||
title:{ text:"CO2", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.co2 }]
|
||||
});
|
||||
chart_voc = new CanvasJS.Chart("chartContainer_voc",{
|
||||
title:{ text:"VOC", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.voc }]
|
||||
});
|
||||
chart_pm = new CanvasJS.Chart("chartContainer_pm",{
|
||||
title:{ text:"PM", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.pm25 }]
|
||||
});
|
||||
chart_humidity = new CanvasJS.Chart("chartContainer_humidity",{
|
||||
title:{ text:"Humidity", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.humid }]
|
||||
});
|
||||
chart_temperature = new CanvasJS.Chart("chartContainer_temperature",{
|
||||
title:{ text:"Temperature", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.temp }]
|
||||
});
|
||||
|
||||
chart_co2.set("backgroundColor", "#1A202C");
|
||||
chart_voc.set("backgroundColor", "#1A202C");
|
||||
chart_pm.set("backgroundColor", "#1A202C");
|
||||
chart_humidity.set("backgroundColor", "#1A202C");
|
||||
chart_temperature.set("backgroundColor", "#1A202C");
|
||||
|
||||
updateChart();
|
||||
});
|
||||
|
||||
function updateChart() {
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||
});
|
||||
|
||||
posx++;
|
||||
chart_co2.render();
|
||||
chart_voc.render();
|
||||
chart_pm.render();
|
||||
chart_temperature.render();
|
||||
chart_humidity.render();
|
||||
|
||||
chart_co2.title.set("text", "CO2 level (ppm)");
|
||||
chart_voc.title.set("text", "VOC level (ppb)");
|
||||
chart_pm.title.set("text", "PM2.5 level (ug/m³)");
|
||||
chart_humidity.title.set("text", "Humidity level (%)");
|
||||
chart_temperature.title.set("text", "Temperature level (°C)");
|
||||
|
||||
let current_co2 = dataPoints_1['co2'][dataPoints_1['co2'].length-1].y;
|
||||
let current_voc = dataPoints_1['voc'][dataPoints_1['voc'].length-1].y;
|
||||
let current_pm25 = dataPoints_1['pm25'][dataPoints_1['pm25'].length-1].y;
|
||||
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
||||
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
||||
let last_update = dataPoints_1['temp'].length-1;
|
||||
|
||||
if (is_connected && bt_connection && bt_connection.isOpen) {
|
||||
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_
|
||||
|
|
|
@ -68,7 +68,7 @@ ${track.map(pt=>` <gx:value>${pt.distance}</gx:value>\n`).join("")}
|
|||
|
||||
function saveGPX(track, title) {
|
||||
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<metadata>
|
||||
<time>${track[0].date.toISOString()}</time>
|
||||
</metadata>
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "bluetoothdock",
|
||||
"name": "Bluetooth Dock",
|
||||
"shortName":"Dock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
|
||||
"tags": "bluetooth",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bluetoothdock.app.js","url":"app.js"},
|
||||
{"name":"bluetoothdock.boot.js","url":"boot.js"},
|
||||
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -43,3 +43,4 @@
|
|||
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
||||
0.38: Option to log to file if settings.log==2
|
||||
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||
0.40: Bootloader now rebuilds for new firmware versions
|
||||
|
|
|
@ -6,11 +6,11 @@ var s = require('Storage').readJSON('setting.json',1)||{};
|
|||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
var boot = "";
|
||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`;
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
} else {
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`;
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
}
|
||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||
boot += `E.setFlags({pretokenise:1});\n`;
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Make overriding the HRM event optional
|
||||
Emit BTHRM event for external sensor
|
||||
Add recorder app plugin
|
||||
|
|
|
@ -2,24 +2,43 @@
|
|||
var log = function() {};//print
|
||||
var gatt;
|
||||
var status;
|
||||
|
||||
Bangle.isHRMOn = function() {
|
||||
|
||||
var origIsHRMOn = Bangle.isHRMOn;
|
||||
|
||||
Bangle.isBTHRMOn = function(){
|
||||
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
||||
}
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
|
||||
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){
|
||||
return Bangle.isBTHRMOn();
|
||||
}
|
||||
return origIsHRMOn() || Bangle.isBTHRMOn();
|
||||
}
|
||||
|
||||
Bangle.setBTHRMPower = function(isOn, app) {
|
||||
|
||||
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
log("setHRMPower ->", isOn, app);
|
||||
log("setBTHRMPower ->", isOn, app);
|
||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
|
||||
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
|
||||
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
|
||||
isOn = Bangle._PWR.HRM.length;
|
||||
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
||||
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
||||
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app);
|
||||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
log("setHRMPower on", app);
|
||||
if (!Bangle.isHRMOn()) {
|
||||
log("HRM not already on");
|
||||
log("setBTHRMPower on", app);
|
||||
if (!Bangle.isBTHRMOn()) {
|
||||
log("BTHRM not already on");
|
||||
status = "searching";
|
||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||
log("Found device "+device.id);
|
||||
|
@ -49,7 +68,11 @@
|
|||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
Bangle.emit('HRM',{
|
||||
|
||||
|
||||
var eventName = settings.replace ? "HRM" : "BTHRM";
|
||||
|
||||
Bangle.emit(eventName, {
|
||||
bpm:bpm,
|
||||
confidence:100
|
||||
});
|
||||
|
@ -65,15 +88,27 @@
|
|||
});
|
||||
}
|
||||
} else { // not on
|
||||
log("setHRMPower off", app);
|
||||
log("setBTHRMPower off", app);
|
||||
if (gatt) {
|
||||
log("HRM connected - disconnecting");
|
||||
log("BTHRM connected - disconnecting");
|
||||
status = undefined;
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("HRM disconnect error", e);
|
||||
log("BTHRM disconnect error", e);
|
||||
}
|
||||
gatt = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var origSetHRMPower = Bangle.setHRMPower;
|
||||
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
if (settings.enabled || !isOn){
|
||||
Bangle.setBTHRMPower(isOn, app);
|
||||
}
|
||||
if (settings.enabled && !settings.replace || !isOn){
|
||||
origSetHRMPower(isOn, app);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
var btm = g.getHeight()-1;
|
||||
var eventInt = null;
|
||||
var eventBt = null;
|
||||
var counterInt = 0;
|
||||
var counterBt = 0;
|
||||
|
||||
|
||||
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);
|
||||
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;
|
||||
g.setFontVector(12).drawString(str,px,y+60);
|
||||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
print("Event for BT " + JSON.stringify(e));
|
||||
counterBt += 5;
|
||||
eventBt = e;
|
||||
}
|
||||
|
||||
function onHrm(e) {
|
||||
print("Event for Int " + JSON.stringify(e));
|
||||
counterInt += 5;
|
||||
eventInt = e;
|
||||
}
|
||||
|
||||
Bangle.on('BTHRM', onBtHrm);
|
||||
Bangle.on('HRM', onHrm);
|
||||
|
||||
Bangle.setHRMPower(1,'bthrm')
|
||||
Bangle.setBTHRMPower(1,'bthrm')
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
|
||||
function drawInt(){
|
||||
counterInt--;
|
||||
if (counterInt < 0) counterInt = 0;
|
||||
if (counterInt > 5) counterInt = 5;
|
||||
draw(24, eventInt, "HRM", counterInt);
|
||||
}
|
||||
function drawBt(){
|
||||
counterBt--;
|
||||
if (counterBt < 0) counterBt = 0;
|
||||
if (counterBt > 5) counterBt = 5;
|
||||
draw(100, eventBt, "BTHRM", counterBt);
|
||||
}
|
||||
|
||||
var interval = setInterval(drawInt, 1000);
|
||||
var interval = setInterval(drawBt, 1000);
|
|
@ -0,0 +1,27 @@
|
|||
(function(recorders) {
|
||||
recorders.bthrm = function() {
|
||||
var bpm = 0;
|
||||
function onHRM(h) {
|
||||
bpm = h.bpm;
|
||||
}
|
||||
return {
|
||||
name : "BTHR",
|
||||
fields : ["BT Heartrate"],
|
||||
getValues : () => {
|
||||
result = [bpm];
|
||||
bpm = 0;
|
||||
return result;
|
||||
},
|
||||
start : () => {
|
||||
Bangle.on('BTHRM', onHRM);
|
||||
Bangle.setBTHRMPower(1,"recorder");
|
||||
},
|
||||
stop : () => {
|
||||
Bangle.removeListener('BTHRM', onHRM);
|
||||
Bangle.setBTHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor(Bangle.isBTHRMOn()?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
};
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
(function(back) {
|
||||
var FILE = "bthrm.json";
|
||||
|
||||
var settings = Object.assign({
|
||||
enabled: true,
|
||||
replace: true,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Bluetooth HRM' },
|
||||
'< Back': back,
|
||||
'Use BT HRM': {
|
||||
value: !!settings.enabled,
|
||||
format: v => settings.enabled ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.enabled = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Use HRM event': {
|
||||
value: !!settings.replace,
|
||||
format: v => settings.replace ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.replace = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
|
@ -2,3 +2,4 @@
|
|||
0.02: fix precision rounding issue + no reset when equals pressed
|
||||
0.03: Support for different screen sizes and touchscreen
|
||||
0.04: Display current operation on LHS
|
||||
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
g.clear();
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
var DEFAULT_SELECTION = '5';
|
||||
var DEFAULT_SELECTION_NUMBERS = '5', DEFAULT_SELECTION_OPERATORS = '=', DEFAULT_SELECTION_SPECIALS = 'R';
|
||||
var RIGHT_MARGIN = 20;
|
||||
var RESULT_HEIGHT = 40;
|
||||
var COLORS = {
|
||||
|
@ -18,97 +18,45 @@ var COLORS = {
|
|||
SPECIAL: ['#65686C', '#7F8183']
|
||||
};
|
||||
|
||||
var keys = {
|
||||
'0': {
|
||||
xy: [0, 200, 120, 240],
|
||||
trbl: '2.00'
|
||||
},
|
||||
'.': {
|
||||
xy: [120, 200, 180, 240],
|
||||
trbl: '3=.0'
|
||||
},
|
||||
'=': {
|
||||
xy: [181, 200, 240, 240],
|
||||
trbl: '+==.',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'1': {
|
||||
xy: [0, 160, 60, 200],
|
||||
trbl: '4201'
|
||||
},
|
||||
'2': {
|
||||
xy: [60, 160, 120, 200],
|
||||
trbl: '5301'
|
||||
},
|
||||
'3': {
|
||||
xy: [120, 160, 180, 200],
|
||||
trbl: '6+.2'
|
||||
},
|
||||
'+': {
|
||||
xy: [181, 160, 240, 200],
|
||||
trbl: '-+=3',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'4': {
|
||||
xy: [0, 120, 60, 160],
|
||||
trbl: '7514'
|
||||
},
|
||||
'5': {
|
||||
xy: [60, 120, 120, 160],
|
||||
trbl: '8624'
|
||||
},
|
||||
'6': {
|
||||
xy: [120, 120, 180, 160],
|
||||
trbl: '9-35'
|
||||
},
|
||||
'-': {
|
||||
xy: [181, 120, 240, 160],
|
||||
trbl: '*-+6',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'7': {
|
||||
xy: [0, 80, 60, 120],
|
||||
trbl: 'R847'
|
||||
},
|
||||
'8': {
|
||||
xy: [60, 80, 120, 120],
|
||||
trbl: 'N957'
|
||||
},
|
||||
'9': {
|
||||
xy: [120, 80, 180, 120],
|
||||
trbl: '%*68'
|
||||
},
|
||||
'*': {
|
||||
xy: [181, 80, 240, 120],
|
||||
trbl: '/*-9',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'R': {
|
||||
xy: [0, 40, 60, 79],
|
||||
trbl: 'RN7R',
|
||||
color: COLORS.SPECIAL,
|
||||
val: 'AC'
|
||||
},
|
||||
'N': {
|
||||
xy: [60, 40, 120, 79],
|
||||
trbl: 'N%8R',
|
||||
color: COLORS.SPECIAL,
|
||||
val: '+/-'
|
||||
},
|
||||
'%': {
|
||||
xy: [120, 40, 180, 79],
|
||||
trbl: '%/9N',
|
||||
color: COLORS.SPECIAL
|
||||
},
|
||||
'/': {
|
||||
xy: [181, 40, 240, 79],
|
||||
trbl: '//*%',
|
||||
color: COLORS.OPERATOR
|
||||
}
|
||||
var KEY_AREA = [0, RESULT_HEIGHT, g.getWidth(), g.getHeight()];
|
||||
|
||||
var screen, screenColor;
|
||||
var globalGrid = [4, 5];
|
||||
var swipeEnabled;
|
||||
|
||||
var numbersGrid = [3, 4];
|
||||
var numbers = {
|
||||
'0': {grid: [1, 3], globalGrid: [1, 4], trbl: '2.00'},
|
||||
'.': {grid: [2, 3], globalGrid: [2, 4], trbl: '3=.0'},
|
||||
'1': {grid: [0, 2], globalGrid: [0, 3], trbl: '4201'},
|
||||
'2': {grid: [1, 2], globalGrid: [1, 3], trbl: '5301'},
|
||||
'3': {grid: [2, 2], globalGrid: [2, 3], trbl: '6+.2'},
|
||||
'4': {grid: [0, 1], globalGrid: [0, 2], trbl: '7514'},
|
||||
'5': {grid: [1, 1], globalGrid: [1, 2], trbl: '8624'},
|
||||
'6': {grid: [2, 1], globalGrid: [2, 2], trbl: '9-35'},
|
||||
'7': {grid: [0, 0], globalGrid: [0, 1], trbl: 'R847'},
|
||||
'8': {grid: [1, 0], globalGrid: [1, 1], trbl: 'N957'},
|
||||
'9': {grid: [2, 0], globalGrid: [2, 1], trbl: '%*68'},
|
||||
};
|
||||
|
||||
var selected = DEFAULT_SELECTION;
|
||||
var prevSelected = DEFAULT_SELECTION;
|
||||
var operatorsGrid = [2, 3];
|
||||
var operators = {
|
||||
'+': {grid: [0, 0], globalGrid: [3, 3], trbl: '-+=3'},
|
||||
'-': {grid: [1, 0], globalGrid: [3, 2], trbl: '*-+6'},
|
||||
'*': {grid: [0, 1], globalGrid: [3, 1], trbl: '/*-9'},
|
||||
'/': {grid: [1, 1], globalGrid: [3, 0], trbl: '//*%'},
|
||||
'=': {grid: [1, 2], globalGrid: [3, 4], trbl: '+==.'},
|
||||
};
|
||||
|
||||
var specialsGrid = [2, 2];
|
||||
var specials = {
|
||||
'R': {grid: [0, 0], globalGrid: [0, 0], trbl: 'RN7R', val: 'AC'},
|
||||
'N': {grid: [1, 0], globalGrid: [1, 0], trbl: 'N%8R', val: '+/-'},
|
||||
'%': {grid: [0, 1], globalGrid: [2, 0], trbl: '%/9N'},
|
||||
};
|
||||
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevNumber = null;
|
||||
var currNumber = null;
|
||||
var operator = null;
|
||||
|
@ -116,6 +64,27 @@ var results = null;
|
|||
var isDecimal = false;
|
||||
var hasPressedEquals = false;
|
||||
|
||||
function prepareScreen(screen, grid, defaultColor) {
|
||||
for (var k in screen) {
|
||||
if (screen.hasOwnProperty(k)) {
|
||||
screen[k].color = screen[k].color || defaultColor;
|
||||
var position = [];
|
||||
var xGrid = (KEY_AREA[2]-KEY_AREA[0])/grid[0];
|
||||
var yGrid = (KEY_AREA[3]-KEY_AREA[1])/grid[1];
|
||||
if (swipeEnabled) {
|
||||
position[0] = KEY_AREA[0]+xGrid*screen[k].grid[0];
|
||||
position[1] = KEY_AREA[1]+yGrid*screen[k].grid[1];
|
||||
} else {
|
||||
position[0] = KEY_AREA[0]+xGrid*screen[k].globalGrid[0];
|
||||
position[1] = KEY_AREA[1]+yGrid*screen[k].globalGrid[1];
|
||||
}
|
||||
position[2] = position[0]+xGrid-1;
|
||||
position[3] = position[1]+yGrid-1;
|
||||
screen[k].xy = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawKey(name, k, selected) {
|
||||
var rMargin = 0;
|
||||
var bMargin = 0;
|
||||
|
@ -142,6 +111,56 @@ function drawKey(name, k, selected) {
|
|||
g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2);
|
||||
}
|
||||
|
||||
function drawKeys() {
|
||||
g.setColor(screenColor[0]);
|
||||
g.fillRect(KEY_AREA[0], KEY_AREA[1], KEY_AREA[2], KEY_AREA[3]);
|
||||
for (var k in screen) {
|
||||
if (screen.hasOwnProperty(k)) {
|
||||
drawKey(k, screen[k], k == selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
function drawGlobal() {
|
||||
screen = {};
|
||||
screenColor = COLORS.DEFAULT;
|
||||
prepareScreen(numbers, globalGrid, COLORS.DEFAULT);
|
||||
for (var k in numbers) {
|
||||
screen[k] = numbers[k];
|
||||
}
|
||||
prepareScreen(operators, globalGrid, COLORS.OPERATOR);
|
||||
for (var k in operators) {
|
||||
screen[k] = operators[k];
|
||||
}
|
||||
prepareScreen(specials, globalGrid, COLORS.SPECIAL);
|
||||
for (var k in specials) {
|
||||
screen[k] = specials[k];
|
||||
}
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
}
|
||||
function drawNumbers() {
|
||||
screen = numbers;
|
||||
screenColor = COLORS.DEFAULT;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
}
|
||||
function drawOperators() {
|
||||
screen = operators;
|
||||
screenColor =COLORS.OPERATOR;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_OPERATORS;
|
||||
var prevSelected = DEFAULT_SELECTION_OPERATORS;
|
||||
}
|
||||
function drawSpecials() {
|
||||
screen = specials;
|
||||
screenColor = COLORS.SPECIAL;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_SPECIALS;
|
||||
var prevSelected = DEFAULT_SELECTION_SPECIALS;
|
||||
}
|
||||
|
||||
function getIntWithPrecision(x) {
|
||||
var xStr = x.toString();
|
||||
var xRadix = xStr.indexOf('.');
|
||||
|
@ -218,8 +237,8 @@ function displayOutput(num) {
|
|||
hasPressedEquals = false;
|
||||
prevNumber = null;
|
||||
operator = null;
|
||||
keys.R.val = 'AC';
|
||||
drawKey('R', keys.R);
|
||||
specials.R.val = 'AC';
|
||||
if (!swipeEnabled) drawKey('R', specials.R);
|
||||
g.setFont('Vector', 22);
|
||||
} else {
|
||||
// might not be a number due to display of dot "."
|
||||
|
@ -299,12 +318,12 @@ function buttonPress(val) {
|
|||
results = null;
|
||||
isDecimal = false;
|
||||
hasPressedEquals = false;
|
||||
if (keys.R.val == 'AC') {
|
||||
if (specials.R.val == 'AC') {
|
||||
prevNumber = null;
|
||||
operator = null;
|
||||
} else {
|
||||
keys.R.val = 'AC';
|
||||
drawKey('R', keys.R, true);
|
||||
specials.R.val = 'AC';
|
||||
drawKey('R', specials.R, true);
|
||||
}
|
||||
wasPressedEquals = false;
|
||||
hasPressedNumber = false;
|
||||
|
@ -331,10 +350,11 @@ function buttonPress(val) {
|
|||
case '+':
|
||||
calculatorLogic(val);
|
||||
hasPressedNumber = false;
|
||||
if (swipeEnabled) drawNumbers();
|
||||
break;
|
||||
case '.':
|
||||
keys.R.val = 'C';
|
||||
drawKey('R', keys.R);
|
||||
specials.R.val = 'C';
|
||||
if (!swipeEnabled) drawKey('R', specials.R);
|
||||
isDecimal = true;
|
||||
displayOutput(currNumber == null ? 0 + '.' : currNumber + '.');
|
||||
break;
|
||||
|
@ -348,8 +368,8 @@ function buttonPress(val) {
|
|||
hasPressedNumber = false;
|
||||
break;
|
||||
default:
|
||||
keys.R.val = 'C';
|
||||
drawKey('R', keys.R);
|
||||
specials.R.val = 'C';
|
||||
if (!swipeEnabled) drawKey('R', specials.R);
|
||||
const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity);
|
||||
if (isDecimal) {
|
||||
currNumber = currNumber == null || hasPressedEquals === 1 ? 0 + '.' + val : currNumber + '.' + val;
|
||||
|
@ -367,23 +387,31 @@ function buttonPress(val) {
|
|||
}
|
||||
|
||||
function moveDirection(d) {
|
||||
drawKey(selected, keys[selected]);
|
||||
drawKey(selected, screen[selected]);
|
||||
prevSelected = selected;
|
||||
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : keys[selected].trbl[d];
|
||||
drawKey(selected, keys[selected], true);
|
||||
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : screen[selected].trbl[d];
|
||||
drawKey(selected, screen[selected], true);
|
||||
}
|
||||
|
||||
if (global.BTN4) {
|
||||
if (process.env.HWVERSION==1) {
|
||||
setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100});
|
||||
setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100});
|
||||
swipeEnabled = false;
|
||||
drawGlobal();
|
||||
} else { // touchscreen?
|
||||
selected = "NONE";
|
||||
swipeEnabled = true;
|
||||
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
||||
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
||||
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
||||
drawNumbers();
|
||||
Bangle.on('touch',(n,e)=>{
|
||||
for (var key in keys) {
|
||||
var r = keys[key].xy;
|
||||
for (var key in screen) {
|
||||
if (typeof screen[key] == "undefined") break;
|
||||
var r = screen[key].xy;
|
||||
if (e.x>=r[0] && e.y>=r[1] &&
|
||||
e.x<r[2] && e.y<r[3]) {
|
||||
//print("Press "+key);
|
||||
|
@ -391,19 +419,26 @@ if (global.BTN4) {
|
|||
}
|
||||
}
|
||||
});
|
||||
var lastX = 0, lastY = 0;
|
||||
Bangle.on('drag', (e) => {
|
||||
if (!e.b) {
|
||||
if (lastX > 50) { // right
|
||||
drawSpecials();
|
||||
} else if (lastX < -50) { // left
|
||||
drawOperators();
|
||||
} else if (lastY > 50) { // down
|
||||
drawNumbers();
|
||||
} else if (lastY < -50) { // up
|
||||
drawNumbers();
|
||||
}
|
||||
lastX = 0;
|
||||
lastY = 0;
|
||||
} else {
|
||||
lastX = lastX + e.dx;
|
||||
lastY = lastY + e.dy;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// rescale for non-240px screens
|
||||
if (g.getWidth()!=240) {
|
||||
RESULT_HEIGHT = RESULT_HEIGHT*g.getWidth()/240;
|
||||
for (var k in keys) {
|
||||
keys[k].xy = keys[k].xy.map(n => n*g.getWidth()/240);
|
||||
}
|
||||
}
|
||||
// draw keys
|
||||
for (var k in keys) {
|
||||
if (keys.hasOwnProperty(k)) {
|
||||
drawKey(k, keys[k], k == selected);
|
||||
}
|
||||
}
|
||||
|
||||
displayOutput(0);
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
0.02: Make Bangle 2 compatible
|
||||
0.03: Add setting to start week on Sunday
|
||||
0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed).
|
||||
0.05: Update calendar weekend colors for start on Sunday
|
||||
0.06: Use larger font for dates
|
||||
|
|
|
@ -110,10 +110,19 @@ function drawCalendar(date) {
|
|||
g.clearRect(0, 0, maxX, maxY);
|
||||
g.setBgColor(bgColorMonth);
|
||||
g.clearRect(0, 0, maxX, headerH);
|
||||
g.setBgColor(bgColorDow);
|
||||
g.clearRect(0, headerH, maxX, headerH + rowH);
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(colW * 5, headerH + rowH, maxX, maxY);
|
||||
if (settings.startOnSun){
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(0, headerH + rowH, colW, maxY);
|
||||
g.setBgColor(bgColorDow);
|
||||
g.clearRect(0, headerH, maxX, headerH + rowH);
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(colW * 6, headerH + rowH, maxX, maxY);
|
||||
} else {
|
||||
g.setBgColor(bgColorDow);
|
||||
g.clearRect(0, headerH, maxX, headerH + rowH);
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(colW * 5, headerH + rowH, maxX, maxY);
|
||||
}
|
||||
for (let y = headerH; y < maxY; y += rowH) {
|
||||
g.drawLine(0, y, maxX, y);
|
||||
}
|
||||
|
@ -197,6 +206,8 @@ function drawCalendar(date) {
|
|||
y2 - 1
|
||||
);
|
||||
}
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12", fontSize);
|
||||
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
|
||||
g.drawString(
|
||||
(day > 50 ? day - 50 : day).toString(),
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -3,3 +3,4 @@
|
|||
0.03: Display only minutes:seconds when less than 1 hour left
|
||||
0.04: Change to 7 segment font, move to top widget bar
|
||||
Better auto-update behaviour, less RAM used
|
||||
0.05: Fix error running app on new firmwares (fix #1140)
|
||||
|
|
|
@ -36,13 +36,7 @@ E.on('kill', () => {
|
|||
function showMenu() {
|
||||
const timerMenu = {
|
||||
'': {
|
||||
'title': 'Set timer',
|
||||
'predraw': function() {
|
||||
timerMenu.hours.value = settingsChronowid.hours;
|
||||
timerMenu.minutes.value = settingsChronowid.minutes;
|
||||
timerMenu.seconds.value = settingsChronowid.seconds;
|
||||
timerMenu.started.value = settingsChronowid.started;
|
||||
}
|
||||
'title': 'Set timer'
|
||||
},
|
||||
'< Back' : ()=>{load();},
|
||||
'Reset values': function() {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New clock
|
||||
0.02: Fix icon & add battery warn functionality
|
||||
0.03: Theming support & minor fixes
|
||||
|
|
|
@ -13,6 +13,8 @@ It shows besides time, date and day of week the following information:
|
|||
|
||||
## TODO
|
||||
* Show weather information
|
||||
* Configure which information to show in each circle
|
||||
* Configure visibility of widgets
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
|
|
@ -7,19 +7,23 @@ const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbB
|
|||
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
|
||||
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
|
||||
|
||||
const SETTINGS_FILE = "circlesclock.json";
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = require("Storage").readJSON(SETTINGS_FILE, 1) || {
|
||||
settings = require("Storage").readJSON("circlesclock.json", 1) || {
|
||||
'maxHR': 200,
|
||||
'stepGoal': 10000,
|
||||
'batteryWarn': 30
|
||||
};
|
||||
// Load step goal from pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
const d = require('Storage').readJSON("wpedom.json", 1) || {};
|
||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
}
|
||||
}
|
||||
|
||||
const colorFg = '#fff';
|
||||
const colorBg = '#000';
|
||||
const colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
const colorBg = g.theme.dark ? '#000' : '#fff';
|
||||
const colorGrey = '#808080';
|
||||
const colorRed = '#ff0000';
|
||||
const colorGreen = '#00ff00';
|
||||
|
@ -73,7 +77,7 @@ function drawSteps() {
|
|||
g.setColor(colorGrey);
|
||||
g.fillCircle(w1, h3, radiusOuter);
|
||||
|
||||
const stepGoal = settings.stepGoal;
|
||||
const stepGoal = settings.stepGoal || 10000;
|
||||
if (stepGoal > 0) {
|
||||
let percent = steps / stepGoal;
|
||||
if (stepGoal < steps) percent = 1;
|
||||
|
@ -97,8 +101,9 @@ function drawHeartRate() {
|
|||
g.setColor(colorGrey);
|
||||
g.fillCircle(w2, h3, radiusOuter);
|
||||
|
||||
if (hrtValue != undefined) {
|
||||
const percent = hrtValue / settings.maxHR;
|
||||
if (hrtValue != undefined && hrtValue > 0) {
|
||||
const minHR = 40;
|
||||
const percent = (hrtValue - minHR) / (settings.maxHR - minHR);
|
||||
drawGauge(w2, h3, percent, colorRed);
|
||||
}
|
||||
|
||||
|
@ -156,25 +161,26 @@ function radians(a) {
|
|||
return a * Math.PI / 180;
|
||||
}
|
||||
|
||||
|
||||
function drawGauge(cx, cy, percent, color) {
|
||||
let offset = 30;
|
||||
let end = 300;
|
||||
var i = 0;
|
||||
var r = radiusInner + 3;
|
||||
|
||||
if (percent <= 0) return;
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
var startrot = -offset;
|
||||
var endrot = startrot - ((end - offset) * percent);
|
||||
var endrot = startrot - ((end - offset) * percent) - 15;
|
||||
|
||||
g.setColor(color);
|
||||
|
||||
const size = 4;
|
||||
// draw gauge
|
||||
for (i = startrot; i > endrot; i -= 4) {
|
||||
for (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, 4);
|
||||
g.fillCircle(x, y, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,6 +207,10 @@ function getSteps() {
|
|||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
Bangle.setHRMPower(1, "watch");
|
||||
if (hrtValue == undefined) {
|
||||
hrtValue = '...';
|
||||
drawHeartRate();
|
||||
}
|
||||
} else {
|
||||
Bangle.setHRMPower(0, "watch");
|
||||
}
|
||||
|
@ -218,6 +228,10 @@ Bangle.on('HRM', function(hrm) {
|
|||
//}
|
||||
});
|
||||
|
||||
Bangle.on('charging', function(charging) {
|
||||
drawBattery();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
/*
|
||||
|
@ -225,9 +239,11 @@ Bangle.loadWidgets();
|
|||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
for (let wd of WIDGETS) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
if (typeof WIDGETS === "object") {
|
||||
for (let wd of WIDGETS) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
}
|
||||
}
|
||||
loadSettings();
|
||||
setInterval(draw, 60000);
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Colorful Analog Clock #
|
||||
|
||||
This app displays an analog clock with a colorful face. It considers the
|
||||
currently configured "theme" (and may therefore look different than shown in
|
||||
the screenshot on your watch depending on which theme you prefer).
|
||||
|
||||

|
||||
|
||||
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 @@
|
|||
require("heatshrink").decompress(atob("mEwgZC/AB0BkmCCBsShEAiFBkARLwEBkGSgEECBQdBCIMSAwMYCBEKCgeQgE2gFgCA0C6Moiw1Dk0AhoRGikACIIHDgzECCI5ECg/gEYOACI+ggMti2ACIUkCIImCABARCAAMNCYIADgu0hdACI1twHACQm0rdoCI0BEYsoilRCI9sUgkBoG2rY1JgYjDCINLCI4fCa5ARHAAggCfYIjLgUB0AECCIy7BFwUCR4IKChIRFm1ACJAjGgwRL+AFDiwREI4YABn41FI4hxFn6IJPoh1B/AQFUI4ABh4RGUIsEyARC4ALEwAjECIl/CIkECIsICId+EQkkwEIA4gRDAAojBLwwHFexAADhaFDgETBw6UChdgA4cbCIKuGggCBCIMDCIkQCI8BEwMbCgMSAQIRGgGQQoQRCEYJrGAAMGIgZKDmBzIjARFTwpuHAARoGAAsMwQVCzARLAAPbtq5KAH4AEA"))
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,247 @@
|
|||
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
|
||||
|
||||
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
/**** updateClockFaceSize ****/
|
||||
|
||||
function updateClockFaceSize () {
|
||||
CenterX = ScreenWidth/2;
|
||||
CenterY = ScreenHeight/2;
|
||||
|
||||
outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
|
||||
if (global.WIDGETS == null) { return; }
|
||||
|
||||
let WidgetLayouts = {
|
||||
tl:{ x:0, y:0, Direction:0 },
|
||||
tr:{ x:ScreenWidth-1, y:0, Direction:1 },
|
||||
bl:{ x:0, y:ScreenHeight-24, Direction:0 },
|
||||
br:{ x:ScreenWidth-1, y:ScreenHeight-24, Direction:1 }
|
||||
};
|
||||
|
||||
for (let Widget of WIDGETS) {
|
||||
let WidgetLayout = WidgetLayouts[Widget.area]; // reference, not copy!
|
||||
if (WidgetLayout == null) { continue; }
|
||||
|
||||
Widget.x = WidgetLayout.x - WidgetLayout.Direction * Widget.width;
|
||||
Widget.y = WidgetLayout.y;
|
||||
|
||||
WidgetLayout.x += Widget.width * (1-2*WidgetLayout.Direction);
|
||||
}
|
||||
|
||||
let x,y, dx,dy;
|
||||
let cx = CenterX, cy = CenterY, r = outerRadius, r2 = r*r;
|
||||
|
||||
x = WidgetLayouts.tl.x; y = WidgetLayouts.tl.y+24; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.tr.x; y = WidgetLayouts.tr.y+24; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.bl.x; y = WidgetLayouts.bl.y; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.br.x; y = WidgetLayouts.br.y; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
CenterX = cx; CenterY = cy; outerRadius = r * 0.9;
|
||||
}
|
||||
|
||||
updateClockFaceSize();
|
||||
|
||||
/**** custom version of Bangle.drawWidgets (does not clear the widget areas) ****/
|
||||
|
||||
Bangle.drawWidgets = function () {
|
||||
var w = g.getWidth(), h = g.getHeight();
|
||||
|
||||
var pos = {
|
||||
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
|
||||
tr:{x:w-1, y:0, r:1, c:0},
|
||||
bl:{x:0, y:h-24, r:0, c:0},
|
||||
br:{x:w-1, y:h-24, r:1, c:0}
|
||||
};
|
||||
|
||||
if (global.WIDGETS) {
|
||||
for (var wd of WIDGETS) {
|
||||
var p = pos[wd.area];
|
||||
if (!p) continue;
|
||||
|
||||
wd.x = p.x - p.r*wd.width;
|
||||
wd.y = p.y;
|
||||
|
||||
p.x += wd.width*(1-2*p.r);
|
||||
p.c++;
|
||||
}
|
||||
|
||||
g.reset(); // also loads the current theme
|
||||
|
||||
if (pos.tl.c || pos.tr.c) {
|
||||
g.setClipRect(0,h-24,w-1,h-1);
|
||||
g.reset(); // also (re)loads the current theme
|
||||
}
|
||||
|
||||
if (pos.bl.c || pos.br.c) {
|
||||
g.setClipRect(0,h-24,w-1,h-1);
|
||||
g.reset(); // also (re)loads the current theme
|
||||
}
|
||||
|
||||
try {
|
||||
for (wd of WIDGETS) {
|
||||
g.clearRect(wd.x,wd.y, wd.x+wd.width-1,23);
|
||||
wd.draw(wd);
|
||||
}
|
||||
} catch (e) { print(e); }
|
||||
}
|
||||
};
|
||||
|
||||
let innerRadius = Math.min(CenterX,CenterY) * 0.8 - 14;
|
||||
|
||||
let HourHandLength = outerRadius * 0.5;
|
||||
let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2;
|
||||
|
||||
let MinuteHandLength = outerRadius * 0.7;
|
||||
let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2;
|
||||
|
||||
let SecondHandLength = outerRadius * 0.9;
|
||||
let SecondHandOffset = 6;
|
||||
|
||||
let twoPi = 2*Math.PI;
|
||||
let Pi = Math.PI;
|
||||
let halfPi = Math.PI/2;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
|
||||
let HourHandPolygon = [
|
||||
-halfHourHandWidth,halfHourHandWidth,
|
||||
-halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
||||
halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
||||
halfHourHandWidth,halfHourHandWidth,
|
||||
];
|
||||
|
||||
let MinuteHandPolygon = [
|
||||
-halfMinuteHandWidth,halfMinuteHandWidth,
|
||||
-halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
||||
halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
||||
halfMinuteHandWidth,halfMinuteHandWidth,
|
||||
];
|
||||
|
||||
/**** drawClockFace ****/
|
||||
|
||||
function drawClockFace () {
|
||||
for (let i = 0; i < 60; i++) {
|
||||
let Phi = i * twoPi/60;
|
||||
|
||||
let x = CenterX + outerRadius * sin(Phi);
|
||||
let y = CenterY - outerRadius * cos(Phi);
|
||||
|
||||
let Color = E.HSBtoRGB(i/60,1,1, true);
|
||||
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
|
||||
|
||||
g.fillCircle(x,y, 1);
|
||||
}
|
||||
|
||||
g.setFont('Vector', 20);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
let Phi = i * twoPi/12;
|
||||
|
||||
let Radius = innerRadius;
|
||||
if (i >= 10) { Radius -= 4; }
|
||||
|
||||
let x = CenterX + Radius * sin(Phi);
|
||||
let y = CenterY - Radius * cos(Phi);
|
||||
|
||||
let Color = E.HSBtoRGB(i/12,1,1, true);
|
||||
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
|
||||
|
||||
g.drawString(i == 0 ? '12' : '' + i, x,y);
|
||||
}
|
||||
}
|
||||
|
||||
/**** transforme polygon ****/
|
||||
|
||||
let transformedPolygon = new Array(HourHandPolygon.length);
|
||||
|
||||
function transformPolygon (originalPolygon, OriginX,OriginY, Phi) {
|
||||
let sPhi = sin(Phi), cPhi = cos(Phi), x,y;
|
||||
|
||||
for (let i = 0, l = originalPolygon.length; i < l; i+=2) {
|
||||
x = originalPolygon[i];
|
||||
y = originalPolygon[i+1];
|
||||
|
||||
transformedPolygon[i] = OriginX + x*cPhi + y*sPhi;
|
||||
transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi;
|
||||
}
|
||||
}
|
||||
|
||||
/**** draw clock hands ****/
|
||||
|
||||
function drawClockHands () {
|
||||
let now = new Date();
|
||||
|
||||
let Hours = now.getHours() % 12;
|
||||
let Minutes = now.getMinutes();
|
||||
let Seconds = now.getSeconds();
|
||||
|
||||
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
|
||||
let MinutesAngle = (Minutes/60) * twoPi - Pi;
|
||||
let SecondsAngle = (Seconds/60) * twoPi - Pi;
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
|
||||
|
||||
g.setColor(g.theme.fg2);
|
||||
g.drawLine(
|
||||
CenterX + SecondHandOffset*sPhi,
|
||||
CenterY - SecondHandOffset*cPhi,
|
||||
CenterX - SecondHandLength*sPhi,
|
||||
CenterY + SecondHandLength*cPhi
|
||||
);
|
||||
}
|
||||
|
||||
/**** refreshDisplay ****/
|
||||
|
||||
let Timer;
|
||||
function refreshDisplay () {
|
||||
g.clear(true); // also loads current theme
|
||||
|
||||
Bangle.drawWidgets();
|
||||
|
||||
drawClockFace();
|
||||
drawClockHands();
|
||||
|
||||
let Pause = 1000 - (Date.now() % 1000);
|
||||
Timer = setTimeout(refreshDisplay,Pause);
|
||||
}
|
||||
|
||||
setTimeout(refreshDisplay, 500); // enqueue first draw request
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
|
||||
refreshDisplay();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
Bangle.setUI('clock');
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
|
@ -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,12 @@
|
|||
# ColorWheel #
|
||||
|
||||
Choosing the right color on a Bangle.js 2 is not always easy. This little app therefore displays a wheel of rather good looking colors and reveals the associated color code by tapping on it
|
||||
|
||||

|
||||

|
||||
|
||||
Please note: you may also tap outside the wheel (for black) or inside it (for white).
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgYtr4cEiAQMku27ckyVICBUDlmy5YRBpMkCBEE7dtEYYRBwARHm3LEY3QCA0BEAIjFk3boARFhoOBEYs06dNCIogCEYoHCNAoOCEYlNEYUgCIcbEZekCIYODtgjHmgRHtu3///yQrESQfTBIYQBAAPNEYU2SQUGEYd/CIf9EYYRCDAYRF/4KBCIioBAwPfCAn/+wKC7QjEmgRG/xADZAIyBAwIQFCIgjFmoRKEYL4DRgQAFGoojKCIoje7Nly1ZEYLzCkojLNYIRCNZAjIkm/EZ4RH/1ZEYYRMWYpZDy4jJrARBggRBlrYG+VJEYgRBDIVfCIgtCy1QCIZhDCAfkKIW24ARBgJeBEYNbvoQBvOkCIQjDgE2EYYCD2gRCyQQCgEGEYYRBzVp0wRCyAREEY+2CIWAEY4OCEYoQDAAMbEY/SpMgCAkCjIjHzVJEQoABWYIjF7VICI8BBwYjDe4IAHSQ3QCBBuBLgQjCCBIAChu26dMCBgAdA"))
|
|
@ -0,0 +1,80 @@
|
|||
//----------------------------------------------------------------------------//
|
||||
//-- ColorWheel - draws a "wheel" of good looking colors --//
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
let ColorList = [
|
||||
'#0000FF', '#8000FF', '#FF00FF', '#FF0080', '#FF0000', '#FF8000',
|
||||
'#FFFF00', '#80FF00', '#00FF00', '#00FF80', '#00FFFF', '#0080FF'
|
||||
];
|
||||
|
||||
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
|
||||
|
||||
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
let innerRadius = outerRadius*0.5;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
let twoPi = 2*Math.PI, halfPi = Math.PI/2;
|
||||
|
||||
let DeltaPhi = twoPi/72;
|
||||
let Epsilon = 0.001;
|
||||
|
||||
g.clear();
|
||||
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(0,0, ScreenWidth,ScreenHeight);
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
let Phi0 = i * twoPi/12, Phi1 = (i+1) * twoPi/12;
|
||||
|
||||
let Polygon = [];
|
||||
for (let Phi = Phi0; Phi <= Phi1+Epsilon; Phi += DeltaPhi) {
|
||||
Polygon.push(CenterX + outerRadius * sin(Phi));
|
||||
Polygon.push(CenterY - outerRadius * cos(Phi));
|
||||
}
|
||||
|
||||
for (let Phi = Phi1; Phi >= Phi0-Epsilon; Phi -= DeltaPhi) {
|
||||
Polygon.push(CenterX + innerRadius * sin(Phi));
|
||||
Polygon.push(CenterY - innerRadius * cos(Phi));
|
||||
}
|
||||
g.setColor(ColorList[i]);
|
||||
g.fillPoly(Polygon);
|
||||
}
|
||||
|
||||
g.setColor(1,1,1);
|
||||
g.fillCircle(CenterX,CenterY, innerRadius);
|
||||
|
||||
g.setFont12x20();
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(0,0,0);
|
||||
|
||||
g.drawString('Tap', CenterX,CenterY-20);
|
||||
g.drawString('on a', CenterX,CenterY);
|
||||
g.drawString('Color', CenterX,CenterY+20);
|
||||
|
||||
Bangle.on('touch', function (Button,Position) {
|
||||
Bangle.buzz();
|
||||
|
||||
let dx = Position.x - CenterX;
|
||||
let dy = Position.y - CenterY;
|
||||
|
||||
let Radius = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
let Color;
|
||||
switch (true) {
|
||||
case (Radius > outerRadius): Color = '#000000'; break;
|
||||
case (Radius < innerRadius): Color = '#FFFFFF'; break;
|
||||
default:
|
||||
let Phi = Math.atan2(dy,dx) + halfPi;
|
||||
if (Phi < 0) { Phi += twoPi; }
|
||||
if (Phi > twoPi) { Phi -= twoPi; }
|
||||
|
||||
let Index = Math.floor(12*Phi/twoPi);
|
||||
Color = ColorList[Index];
|
||||
}
|
||||
g.setColor(1,1,1);
|
||||
g.fillCircle(CenterX,CenterY, innerRadius);
|
||||
|
||||
g.setColor(0,0,0);
|
||||
g.drawString(Color, CenterX,CenterY);
|
||||
});
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/ABsH4/wv/H/EMlkMsF4hkYmEEwEwg0gmHCwEh4VAmPi/0j8Vkkcj4MjkU8kckocx4UEmPMoUQgkEEYNGnAFBnEGxFwg0Ek/jzFh8UEkEjkOikUcnFH8MiFIM3wnA8PisEwhnAkECAoMc4EYgk///3//n/Cl/AFYA="))
|
|
@ -0,0 +1,54 @@
|
|||
const digits = [
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVqlVVVVVVVVVVVVVaqqqqpVVVVVVVVVVWqqqqqqqVVVVVVVVVWqqqAKqqpVVVVVVVVaqgAAAACqpVVVVVVVaqAAAAAACqlVVVVVVaoAAAAAAACqVVVVVVaoAAAAAAAAKpVVVVVaoAAAAAAAAAqlVVVVaoAAAAAAAAACqVVVVWoAAAAAAAAAAKlVVVWoAAAAAAAAAAAqVVVWqAAAAAAAAAAAKpVVVqAAAAACgAAAAAqVVVagAAAAKqgAAAAKlVVagAAAAqqqgAAAAqVVWoAAAAKpaoAAAAKlVVqAAAAKlVagAAAAqVVqAAAACpVWoAAAAKlVagAAACpVVagAAACpVWoAAAAqVVWoAAAAqVVqAAAAqVVVagAAAKlVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlWoAAAAqVVVagAAACpVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlWoAAAAqVVVWoAAACpVqAAAAKlVVVqAAAAqVagAAAKlVVVagAAAKlWoAAACpVVVWoAAACpVqAAAAqVVVVqAAAAqVagAAAKlVVVagAAAKlWoAAACpVVVWoAAACpVqAAAAqVVVVqAAAAqVagAAACpVVVagAAAKlWoAAAAqVVVWoAAACpVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlWoAAAAqVVVagAAACpVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlVqAAAAqVVVagAAACpVagAAACpVVWoAAACpVWoAAAAqVVWoAAAAqVVqAAAAKlVVqAAAAKlVagAAAAqVVqAAAAKlVVqAAAAKpVqgAAACpVVagAAAAqqqgAAAAqVVVqAAAACqqgAAAAKlVVagAAAACqAAAAAKlVVWoAAAAAAAAAAACpVVVagAAAAAAAAAACpVVVWqAAAAAAAAAACqVVVVagAAAAAAAAAAqVVVVVqAAAAAAAAAAqVVVVVaoAAAAAAAAAqlVVVVVqgAAAAAAAAqlVVVVVWqgAAAAAAAqlVVVVVVaqAAAAAACqlVVVVVVVaqgAAAAqqlVVVVVVVVqqqqqqqqVVVVVVVVVVqqqqqqpVVVVVVVVVVVaqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVVVVVaqqqVVVVVVVVVVVVVqqqqqVVVVVVVVVVVWqqqqqpVVVVVVVVVVaqgAAAqVVVVVVVVVVaqAAAACpVVVVVVVVVqoAAAAAqVVVVVVVVVqoAAAAAKlVVVVVVVWqgAAAAACpVVVVVVVaqgAAAAAAqVVVVVVVaqAAAAAAAKlVVVVVVaoAAAAAAACpVVVVVVqoAAAAAAAAqVVVVVVaoAAAAAAAAKlVVVVVagAAAAAAAACpVVVVVWoAAAAAAAAAqVVVVVVqAAAAAAAAAKlVVVVVagAAAAAAAACpVVVVVWoAAAAAAAAAqVVVVVVqAAAAAAAAAKlVVVVVagAAAgAAAACpVVVVVWoAACogAAAAqVVVVVVagAKqoAAAAKlVVVVVWqqqqagAAACpVVVVVVaqqpWoAAAAqVVVVVVVaqlVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVaqqqAAAAAqqqpVVVWqqqqgAAAAKqqqpVVWqqqqAAAAAAKqqqlVVqgAAAAAAAAAAACpVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVWoAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWqAAAAAAAAAAAACqVVaqqqqqqqqqqqqqqVVVqqqqqqqqqqqqqqVVVWqqqqqqqqqqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVWqVVVVVVVVVVVVVaqqqqqlVVVVVVVVVaqqqqqqqqVVVVVVVVqqqqoAqqqqVVVVVVWqqgAAAAAKqpVVVVVaqgAAAAAAAAqpVVVVaqAAAAAAAAACqlVVVWoAAAAAAAAAACpVVVWoAAAAAAAAAAAKlVVVqAAAAAAAAAAACqVVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVVqAAAAAAAAAAAAKlVVagAAAAAAAAAAAAqVVWoAAAKqqAAAAAAKlVVagACqqqqAAAAACpVVWqqqqqqqqAAAAAqVVVaqqqlVVqgAAAAKlVVVaqpVVVVqAAAACpVVVVVVVVVVagAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVqAAAACpVVVVVVVVVVagAAAAqVVVVVVVVVVagAAAAqVVVVVVVVVVaoAAAAKlVVVVVVVVVaoAAAACpVVVVVVVVVaoAAAACpVVVVVVVVVWoAAAACqVVVVVVVVVWoAAAAAqVVVVVVVVVWqAAAAAqVVVVVVVVVWqAAAAAqlVVVVVVVVWqAAAAAKlVVVVVVVVWqAAAAAKlVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAqpVVVVVVVVWqAAAAACpVVVVVVVVWqAAAAAKlVVVVVVVVVqAAAAAACqqqqpVVVVqAAAAAAKqqqqqpVVVqgAAAAAAKqqqqqpVVagAAAAAAAAAAACqVVWoAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVWoAAAAAAAAAAAACpVVqAAAAAAAAAAAAAqVVaoAAAAAAAAAAAAqVVVqqqqqqqqqqqqqqlVVWqqqqqqqqqqqqqlVVVaqqqqqqqqqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVapVVVVVVVVVVVVVaqqqqqlVVVVVVVVVaqqqqqqqqVVVVVVVWqqqqgCqqqpVVVVVVaqqgAAAAAKqpVVVVVaqgAAAAAAACqpVVVVaoAAAAAAAAACqlVVVaoAAAAAAAAAACqVVVWoAAAAAAAAAAAKlVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVVqAAAAAAAAAAAAKlVVagAAAAAAAAAAAAqVVVqAAAKqqgAAAAAKlVVaoAKqqqqgAAAACpVVVqqqqqqqqAAAAAqVVVWqqqlVVaoAAAAKlVVVaqlVVVVqAAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVaoAAAAqVVVVVVVVaqqqAAAAKlVVVVVVaqqqoAAAAKlVVVVVVaqqqgAAAAKpVVVVVVaqgAAAAAAKpVVVVVVaoAAAAAAAKpVVVVVVWoAAAAAAACpVVVVVVVqAAAAAAACpVVVVVVVagAAAAAAAqVVVVVVVWoAAAAAAACpVVVVVVVqAAAAAAAAqpVVVVVVagAAAAAAACqlVVVVVWoAAAAAAAACqVVVVVVaoAAAAAAAAKpVVVVVWqqqqgAAAAAqVVVVVVWqqqqgAAAACpVVVVVVaqqqqgAAAAqVVVVVVVVVVaqAAAAKlVVVVVVVVVVagAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVlVVVVVVqAAAAKlVVaqqVVVVVqAAAACpVVaqqqpVVWqgAAAAqVVaqKqqqqqqgAAAAKlVaoAAqqqqqAAAAACpVaoAAACqqoAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAAKlVWoAAAAAAAAAAAAKpVVqAAAAAAAAAAAACpVVagAAAAAAAAAAAKpVVVqAAAAAAAAAAAKqVVVaqAAAAAAAAAAKpVVVVqqAAAAAAAACqpVVVVVqqoAAAAACqqpVVVVVWqqqqqqqqqqlVVVVVVVqqqqqqqqpVVVVVVVVVWqqqqqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVVVVVVVaqqlVVVVVVVVVVVVVqqqqlVVVVVVVVVVVVqqqqqVVVVVVVVVVVVqgAAKpVVVVVVVVVVVqgAAAqVVVVVVVVVVVqgAAACpVVVVVVVVVVqgAAAAqVVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVaoAAAAAqVVVVVVVVVWoAAAAAKlVVVVVVVVWoAAAAACpVVVVVVVVWqAAAAAAqVVVVVVVVVqAAAAAAKlVVVVVVVVqAAAAAACpVVVVVVVVqgAAAAAAqVVVVVVVVagAAAAAAKlVVVVVVVagAAAAAACpVVVVVVVaoAAAAAAAqVVVVVVVaoAAAAAAAKlVVVVVVWoAAAAAAACpVVVVVVWoAAAAAAAAqVVVVVVWqAAAAAAAAKlVVVVVVqAAAAAAAACpVVVVVVqAAAAAAAAAqVVVVVVqgAAAAAAAAKlVVVVVagAAAgAAAACpVVVVVagAACogAAAAqVVVVVaoAACqoAAAAKlVVVVWoAAAqagAAACpVVVVWoAAAqWoAAAAqVVVVWqAAAqlqAAAAKlVVVVqAAAKlagAAACpVVVVqAAAKlWoAAAAqVVVVqgAAKpVqAAAAKlVVVagAACpVagAAACpVVVagAACpVWoAAAAqVVVaoAAAqVVqAAAAKlVVWoAAACqqqAAAAAqpVVqAAAAqqqgAAAAKqpVqAAAAAqqAAAAAAKqpagAAAAAAAAAAAAACqWoAAAAAAAAAAAAAACpqAAAAAAAAAAAAAAAqagAAAAAAAAAAAAAAKmoAAAAAAAAAAAAAACpqAAAAAAAAAAAAAAAqagAAAAAAAAAAAAAAKlqAAAAAAAAAAAAAACpaqAAAAAAAAAAAAACpVqqqqqqqgAAAAACqqVVqqqqqqqgAAAAKqqVVVqqqqqqoAAAACqpVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVVqAAACpVVVVVVVVVVVagAAAqVVVVVVVVVVVVqgACqVVVVVVVVVVVVaqqqqlVVVVVVVVVVVVaqqqVVVVVVVVVVVVVVqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVaqqqqqqqqpVVVVVVaqqqqqqqqqqqlVVVVaqqqqqqqqqqqqVVVVaqgAAAAAAAACqpVVVaoAAAAAAAAAAAqlVVWoAAAAAAAAAAACpVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVVqAAAAAAAAAAAAKlVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAACpVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAKqVVVWoAAAACqqqqqqqlVVVqAAAAKqqqqqqqVVVVagAAACqqqqqqlVVVVWoAAACpVVVVVVVVVVVqAAAAqVVVVVVVVVVVagAAAKlVVVVVVVVVVWoAAACpVVVVVVVVVVVqAAAAqVVVVVVVVVVVagAAACqqqqqVVVVVVWoAAAAqqqqqqlVVVVVqAAAAAqqqqqqlVVVVagAAAAAAAAAqqVVVVWoAAAAAAAAAAKpVVVVqAAAAAAAAAAAqlVVVagAAAAAAAAAACqVVVWoAAAAAAAAAAAKpVVVqAAAAAAAAAAAAqlVVagAAAAAAAAAAACpVVWoAAAAAAAAAAAAKlVVqAAAAAAAAAAAACpVVagAAAACoAAAAAAKlVWoAAACqqqAAAAACpVVqAAAKqqqqAAAAAqVVWqgqqqpWqoAAAAKlVVqqqqpVVVqAAAACpVVWqqqlVVVWoAAAAqVVVVaVVVVVVqAAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAAKlVVWqpVVVVVagAAACpVVaqqqVVVVaoAAAAqVVaqqqqlVVqoAAAAKlVWoACqqqqqoAAAACpVWoAAAqqqqgAAAAAqVVqAAAAKqqAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAAKlVWoAAAAAAAAAAAACpVVqAAAAAAAAAAAACpVVagAAAAAAAAAAACqVVVqAAAAAAAAAAACqVVVaoAAAAAAAAAAKqVVVVqgAAAAAAAAAKqVVVVWqoAAAAAAAAqpVVVVVaqqgAAAAAqqpVVVVVVaqqqqqqqqqlVVVVVVVWqqqqqqqqVVVVVVVVVVaqqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVqVVVVVVVVVVVVVVqqqqqpVVVVVVVVVVqqqqqqqqVVVVVVVVWqqqqAqqqqlVVVVVVWqqAAAAACqqlVVVVVaqAAAAAAAAqpVVVVVaqAAAAAAAAAKlVVVVaoAAAAAAAAACqVVVVqoAAAAAAAAAAKlVVVaoAAAAAAAAAACpVVVagAAAAAAAAAAAqVVVaoAAAAAAAAAAAKlVVaoAAAAAAAAAAACpVVWoAAAAAAAAAAAAqVVWoAAAAACqqqAAAqVVVqAAAAAKqqqqoAqlVVqAAAAAqqqqqqqqlVVagAAAAqpVVVqqqlVVagAAAAqlVVVVWqVVVWoAAAAqlVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAAKlVVVVVVVVVVagAAACpVaqqqVVVVVWoAAAAqVqqqqqVVVVVqAAAAqWqqqqqqVVVVagAAAImqgAAAqpVVVWoAAAAoqAAAAAqlVVVqAAAAIqAAAAACqlVVqAAAAAIAAAAAAKpVVagAAAAAAAAAAAAKlVWoAAAAAAAAAAAACqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAAAqVWoAAAAAAAAAAAAAKlVqAAAAAACqgAAAACpVagAAAAAKqqgAAAAKlWoAAAAAKqqqAAAACpVagAAAACpVaoAAAAqVWoAAAACpVVqAAAAKlVqAAAAAqVVWoAAACpVagAAAAqVVVqAAAAKlWoAAAAKlVVagAAACpVqAAAACpVVWoAAAAqVagAAAAqVVVqAAAAKlVqAAAAKlVVagAAAKlVagAAACpVVWoAAACpVWoAAAAKlVVqAAAAqVVagAAACpVVqAAAAKlVWoAAAAKlVqgAAACpVVqAAAACqqqgAAAAqVVWoAAAAKqqgAAAAqVVVqAAAAAKqAAAAAKlVVWoAAAAAAAAAAAKlVVVqgAAAAAAAAAACpVVVWoAAAAAAAAAACpVVVVagAAAAAAAAACqVVVVWqAAAAAAAAACqVVVVVaqAAAAAAAACqVVVVVVqoAAAAAAACqVVVVVVVqoAAAAAAKqVVVVVVVWqqAAAACqqVVVVVVVVWqqqqqqqpVVVVVVVVVWqqqqqqlVVVVVVVVVVVqqqqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVqqqqqqqqqqqlVVVVqqqqqqqqqqqqqqVVVqqqqqqqqqqqqqqpVVqqAAAAAAAAAAAKqlVqgAAAAAAAAAAAACqVagAAAAAAAAAAAAAKlWoAAAAAAAAAAAAAAqVqAAAAAAAAAAAAAAKlagAAAAAAAAAAAAACpWoAAAAAAAAAAAAAAqVqAAAAAAAAAAAAAAKlagAAAAAAAAAAAAACpWqAAAAAAAAAAAAAAqVaqgAAAAAAAAAAAAqVVqqqqqqqqAAAAAAKlVWqqqqqqqqAAAAACpVVVaqqqqqqAAAAACpVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqgAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAACqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVagAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVaoAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAAKpVVVVVVVVVaoAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVWqAAAAAqVVVVVVVVVVqAAAAAqlVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVWoAAAAKpVVVVVVVVVVqAAAACpVVVVVVVVVVqAAAACpVVVVVVVVVVWoAAACqVVVVVVVVVVVqAAACqVVVVVVVVVVVaoAACqVVVVVVVVVVVVqqqqqVVVVVVVVVVVVWqqqqVVVVVVVVVVVVVWqqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVqVVVVVVVVVVVVVWqqqqqpVVVVVVVVVVqqqqqqqqlVVVVVVVWqqqqAqqqqlVVVVVVaqoAAAAACqqVVVVVVaqAAAAAAAAKqVVVVVaoAAAAAAAAAqpVVVVaoAAAAAAAAAAqlVVVaoAAAAAAAAAACqVVVaoAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVWoAAAAAAAAAAAAKlVVqAAAAAAAAAAAAAqVVagAAAACqqAAAAAKlVagAAAAKqqqAAAACpVWoAAAAKqqqoAAAAqVVqAAAAKpVVqAAAAKlVagAAACpVVWoAAAAqVWoAAACpVVVqAAAAKlVqAAAAqVVVagAAAKlVagAAAKlVVWoAAACpVWoAAACpVVVqAAAAqVVagAAAKlVVagAAAKlVWoAAACqVVagAAACpVVqAAAAKqqqoAAACpVVWoAAAAqqqoAAAAqVVVqgAAAAqqgAAAAqVVVWqAAAAAAAAAAAqlVVVaoAAAAAAAAAAqlVVVVqAAAAAAAAAAqlVVVVWoAAAAAAAAAKlVVVVVqAAAAAAAAAKlVVVVVqAAAAAAAAAAKlVVVVqgAAAAAAAAAKqVVVWqgAAAAAAAAAAKpVVVqgAAAAAAAAAAAqlVVqAAAAAAAAAAAACqVVqgAAAAKqqAAAAAKlVagAAAAqqqqAAAAAqVWoAAAAqqqqqAAAAKlWoAAAAqlVVqoAAACpVqAAAAKlVVVqAAAAKlagAAAKlVVVWoAAACpWoAAACpVVVVqAAAAqVqAAACpVVVVagAAAKlqAAAAKlVVVWoAAACpagAAACpVVVVqAAAAqVqAAAAKlVVVqAAAAKlagAAACqVVWqgAAACpWoAAAAKqqqqgAAAAqVqAAAAAqqqqAAAAAKlagAAAAAqqoAAAAAKlVqAAAAAAAAAAAAACpVagAAAAAAAAAAAACpVVqAAAAAAAAAAAAAqVVaoAAAAAAAAAAAAqVVVqgAAAAAAAAAAAqlVVWqAAAAAAAAAACqlVVVaqAAAAAAAAACqlVVVVqqAAAAAAAAKqVVVVVVqqoAAAAAKqqVVVVVVVqqqqqqqqqpVVVVVVVVqqqqqqqqlVVVVVVVVVWqqqqqlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVValVVVVVVVVVVVVVWqqqqqVVVVVVVVVVVqqqqqqqVVVVVVVVVWqqqgKqqqVVVVVVVVWqoAAAAAqqVVVVVVVaqAAAAAAAqpVVVVVVaqAAAAAAAAqpVVVVVaoAAAAAAAACqVVVVVWoAAAAAAAAACpVVVVWoAAAAAAAAAAqlVVVWqAAAAAAAAAACqVVVVqAAAAAAAAAAAKpVVVqAAAAAAAAAAAAqVVVagAAAAKqgAAAACpVVagAAAAqqqgAAAAqVVWoAAAAKqqoAAAACpVVqAAAAKlVagAAAAqVVqAAAAKpVWqAAAAKlVagAAACpVVagAAACpVWoAAACpVVVqAAAAKlVqAAAAqVVVagAAACpVagAAAKlVVWoAAAAqVWoAAACpVVVqAAAACpVqAAAAqVVVagAAAAqVagAAACpVVagAAAAKlWoAAAAqVVWoAAAACpVqAAAACpVWoAAAAAqVagAAAAqlWqAAAAAKlVqAAAACqqqAAAAACpVagAAAAKqqAAAAAAqVWoAAAAAKoAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVagAAAAAAAAAAAAKlVWqAAAAAAAAAAAACpVVagAAAAAACAAAAAqVVVqAAAAAAKiAAAAKlVVaqAAAAAKigAAACpVVVqoAAAAKpiAAAAqVVVVqoAAAqpagAAAKlVVVWqqqqqpagAAACpVVVVWqqqqlWoAAACpVVVVVWqqqVVqAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVqgAAACpVVVVVVVVVVqgAAACpVVVaqlVVVVqgAAAAqVVVqqqqVVWqgAAAAKlVVqqqqqqqqgAAAAKlVVagAKqqqqAAAAACpVVagAAAqqoAAAAACpVVWoAAAAAAAAAAACqVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAAqVVVWoAAAAAAAAAAAqlVVVqAAAAAAAAAAAqlVVVagAAAAAAAAAAqlVVVWoAAAAAAAAACqlVVVVagAAAAAAAACqlVVVVWqoAAAAAAAqqVVVVVVaqqAAAAAKqqVVVVVVVaqqqqqqqqpVVVVVVVVWqqqqqqqVVVVVVVVVVVqqqqqlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 25 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaqlVVVVWqqqlVVVWqqqqlVVWqgAKqVVWqAAAKlVWqAAAAqVVqAAAAKlVagAAAAqVWoAAAAKlVqAAAACpVagAAAAqVWoAAAAKlVqAAAACpVagAAACpVWqAAAAqVVagAAAqlVVqgAAqlVVaqqqqlVVVaqqqlVVVVaqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVqqqVVVVVqqqqVVVWqqqqpVVVqgAAqlVVqAAACqVVqgAAAKlVagAAAAqVWoAAAAKlVqAAAACpVagAAAAqVWoAAAAKlVqAAAACpVagAAAAqVWoAAAAqVVagAAAKlVWqAAAKlVVaqACqpVVVqqqqpVVVWqqqlVVVVVqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV "))}
|
||||
];
|
||||
|
||||
var drawTimeout;
|
||||
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2-31;
|
||||
g.reset();
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
// draw time
|
||||
g.clearRect(0,y,g.getWidth()-1,y+73+24+18);
|
||||
//use custom font spacing for overlapping digits
|
||||
g.drawImage(digits[parseInt(date.getHours()/10)],0,y);
|
||||
g.drawImage(digits[parseInt(date.getHours()%10)],37,y);
|
||||
g.drawImage(digits[10],74,y);
|
||||
g.drawImage(digits[parseInt(date.getMinutes()/10)],86,y);
|
||||
g.drawImage(digits[parseInt(date.getMinutes()%10)],123,y);
|
||||
// Draw day of the week
|
||||
y += 73;
|
||||
g.setFontAlign(0,-1).setFont("Teletext10x18Ascii");
|
||||
g.drawString(require("locale").dow(date).toUpperCase(),x,y);
|
||||
// Draw Date
|
||||
y += 24;
|
||||
g.drawString(require('locale').date(new Date(),1),x,y);
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
Bangle.setUI("clock");
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: New app
|
||||
0.02: Cleanup interface and add settings, widget, add skin temp reporting.
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
# CoreTemp display
|
||||
|
||||
Basic bare-bones example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current body core temperature readings.
|
||||
Basic example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current skin and body core temperature readings.
|
||||
|
||||
## Usage
|
||||
|
||||
On startup connects to a CoreTemp device (1809/2A1C) and emits a "Core, temp" value for each reading.
|
||||
The app simply displays these readings on screen.
|
||||
Background task connects to any CoreTemp device (2100/2101) and emits a CoreTemp signal value for each reading.
|
||||
Application contains three components, one is a background task that monitors the sensor and emits a 'CoreTemp' signal on activity if activated in settings.
|
||||
The widget shows when the sensor is enabled with a mini value and blinks on use.
|
||||
The app listens for 'CoreTemp' signals and shows the current skin and core temperatures in large numbers.
|
||||
|
||||
## TODO
|
||||
|
||||
* Integrate with other tracking/sports apps to log data.
|
||||
* Add device selection
|
||||
* Provide enable/disable option
|
||||
* Check status, add Retry/reconnect
|
||||
* Also provide skin temp reading
|
||||
* Add specific device selection
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"enabled":false
|
||||
}
|
|
@ -1,23 +1,114 @@
|
|||
//
|
||||
// If enabled in settings run constantly in background
|
||||
//
|
||||
(function() {
|
||||
var gatt;
|
||||
|
||||
//Would it be better to scan by uuid rather than name?
|
||||
NRF.requestDevice({ timeout: 20000, filters: [{ name: 'CORE [a]' }] }).then(function(device) {
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService("1809");
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic("2A1C");
|
||||
}).then(function(characteristic) {
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var core = (dv.buffer[2]*256+dv.buffer[1])/100;
|
||||
Bangle.emit('Core',{
|
||||
temp:core
|
||||
});
|
||||
});
|
||||
var log = function() {};//print
|
||||
var settings = {};
|
||||
var device;
|
||||
var gatt;
|
||||
var service;
|
||||
var characteristic;
|
||||
|
||||
class CoreSensor {
|
||||
constructor() {
|
||||
this.unit = "";
|
||||
this.core = -1;
|
||||
this.skin = -1;
|
||||
this.battery = 0;
|
||||
}
|
||||
|
||||
updateSensor(event) {
|
||||
if (event.target.uuid == "00002101-5b1e-4347-b07c-97b514dae121") {
|
||||
var dv = event.target.value;
|
||||
var flags = dv.buffer[0];
|
||||
|
||||
if (flags & 8) {
|
||||
this.unit = "F";
|
||||
} else {
|
||||
this.unit = "C";
|
||||
}
|
||||
|
||||
if (flags & 1) {
|
||||
this.skin = (dv.buffer[4] * 256 + dv.buffer[3]) / 100;
|
||||
} else {
|
||||
this.skin = 0;
|
||||
}
|
||||
if (flags & 2) {
|
||||
this.core = (dv.buffer[2] * 256 + dv.buffer[1]) / 100;
|
||||
} else {
|
||||
this.core = 0;
|
||||
}
|
||||
|
||||
Bangle.emit('CoreTemp',
|
||||
{core : this.core, skin : this.skin, unit : this.unit});
|
||||
}
|
||||
}
|
||||
|
||||
updateBatteryLevel(event) {
|
||||
if (event.target.uuid == "0x2a19")
|
||||
this.battery = event.target.value.getUint8(0);
|
||||
}
|
||||
}
|
||||
|
||||
var mySensor = new CoreSensor();
|
||||
|
||||
function getSensorBatteryLevel(gatt) {
|
||||
gatt.getPrimaryService("180f")
|
||||
.then(function(s) { return s.getCharacteristic("2a19"); })
|
||||
.then(function(c) {
|
||||
c.on('characteristicvaluechanged',
|
||||
(event) => mySensor.updateBatteryLevel(event));
|
||||
return c.startNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
function connection_setup() {
|
||||
log("Scanning for CoreTemp sensor...");
|
||||
NRF.requestDevice({active:true,timeout : 20000, filters : [ {namePrefix : 'CORE'} ]})
|
||||
.then(function(d) {
|
||||
device = d;
|
||||
log("Found device");
|
||||
return device.gatt.connect();
|
||||
})
|
||||
.then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService('00002100-5b1e-4347-b07c-97b514dae121');
|
||||
})
|
||||
.then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic(
|
||||
'00002101-5b1e-4347-b07c-97b514dae121');
|
||||
})
|
||||
.then(function(c) {
|
||||
characteristic = c;
|
||||
characteristic.on('characteristicvaluechanged',
|
||||
(event) => mySensor.updateSensor(event));
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
log("Done!");
|
||||
// getSensorBatteryLevel(gatt);
|
||||
})
|
||||
.catch(function(e) {
|
||||
log(e.toString(), "ERROR");
|
||||
log(e);
|
||||
});
|
||||
}
|
||||
|
||||
function connection_end() {
|
||||
if (gatt != undefined)
|
||||
gatt.disconnect();
|
||||
}
|
||||
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
log("Settings:");
|
||||
log(settings);
|
||||
|
||||
if (settings.enabled) {
|
||||
connection_setup();
|
||||
NRF.on('disconnect', connection_setup);
|
||||
}
|
||||
|
||||
E.on('kill', () => { connection_end(); });
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,19 +1,66 @@
|
|||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
var btm = g.getHeight()-1;
|
||||
// Simply listen for core events and show data
|
||||
|
||||
var btm = g.getHeight() - 1;
|
||||
var px = g.getWidth() / 2;
|
||||
|
||||
// Dark or light logo
|
||||
var col = (process.env.HWVERSION == 1) ? 65535 : 0;
|
||||
|
||||
var corelogo = {
|
||||
width : 146,
|
||||
height : 48,
|
||||
bpp : 4,
|
||||
transparent : 0,
|
||||
palette : new Uint16Array([ col, col, 2854, 1419 ]),
|
||||
buffer :
|
||||
require("heatshrink")
|
||||
.decompress(atob(
|
||||
"AEUDmczmBD/I4xJ/AAMCkBHFAAJG8kQABJAJHFSVURAAUQRphHCkQGBJAySngJHDJRhHEJALZDAgiSBEQ0RPBIAKHAwQQI4xIEaoQFEEZpIULSRHFkDZDBwZIMEYhITa44SKSAxIDSARIDJ4IjKJCpHNEoiQGJDA2CJCQSOCYaQGJDBsCGiKQGTZIJCI4xBEJBAAEFpQAPDQoMGBQyOGIJJPGF6AALC5glCbJAQEgZCEAoowTSBypJBwKQMIQaSBAgZIJWw5ITB5RTDSBLbEAAjDOPRIVabIiQFJBCQKPYhIVCRxIEBg7WDSBpIVbJ5IQJIqQBgZIiCh7ZLJIriDbhJI3JoxIebIZITI6BIjCZ5IRI4RIPHAYAJJH4AIUAJIzHIhI/SAwzBJH6QGJH5HIHApI2HCIAJL4pITkATOJQJIMHCJeFJD8zaZCQHJCEBJCUCJCKPBJBhWGJEcia5oACJBSfHJB4QMJA6SLI4ZIKPAg3QJCUAJCbbBJETbPJAbbKbIhIBYJpIQbZ5UDbZzZFPBxIVSRIOBJA5JISAhIIF4ZIUfQpJHEwQKDJAhJHbJbBJJCIZECY4KGSQoABBIZOBSBbbIJC6IEBQqSJJoyQLbZBIRbYoAKJAaSHJAjbCF541RSRISLSRkgJAKQKbY5ISJJyQDSRyQMbYxITChhHFSRhGMbY5IUCpRHHJJZITiBIVbpBHJbpJHPFhBITfI4ANIwcgI6AAV"))
|
||||
};
|
||||
|
||||
function onCore(c) {
|
||||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,24,g.getWidth(),80);
|
||||
var str = c.temp + "C";
|
||||
g.setFontVector(40).drawString(str,px,45);
|
||||
// Large or small font
|
||||
var sz = ((process.env.HWVERSION == 1) ? 3 : 2);
|
||||
|
||||
g.setFontAlign(0, 0);
|
||||
g.clearRect(0, 32 + 48, g.getWidth(), 32 + 48 + 24 * 4);
|
||||
g.setColor(g.theme.dark ? "#CCC" : "#333"); // gray
|
||||
g.setFont("6x8", sz).drawString(
|
||||
"Core: " + ((c.core < 327) ? (c.core + c.unit) : 'n/a'), px, 48 + 48);
|
||||
g.setFont("6x8", sz).drawString("Skin: " + c.skin + c.unit, px, 48 + 48 + 24);
|
||||
}
|
||||
Bangle.on('Core', onCore);
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// Background task will activate once settings are enabled.
|
||||
function enableSensor() {
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
if (!settings.enabled) {
|
||||
settings.enabled = true;
|
||||
require("Storage").write("coretemp.json", settings);
|
||||
|
||||
drawBackground("Waiting for\ndata...");
|
||||
}
|
||||
}
|
||||
|
||||
function drawBackground(message) {
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
g.reset().setFont("6x8", 2).setFontAlign(0, 0);
|
||||
g.drawImage(corelogo, px - 146 / 2, 30);
|
||||
g.drawString(message, g.getWidth() / 2, g.getHeight() / 2 + 16);
|
||||
}
|
||||
|
||||
Bangle.on('CoreTemp', onCore);
|
||||
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
|
||||
if (!settings.enabled) {
|
||||
drawBackground("Sensor off\nBTN" +
|
||||
((process.env.HWVERSION == 1) ? '2' : '1') + " to enable");
|
||||
} else {
|
||||
drawBackground("Waiting for\ndata...");
|
||||
}
|
||||
|
||||
setWatch(() => { enableSensor(); }, (process.env.HWVERSION == 1) ? BTN2 : BTN1,
|
||||
{repeat : false});
|
||||
|
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,47 @@
|
|||
// This file should contain exactly one function, which shows the app's settings
|
||||
/**
|
||||
* @param {function} back Use back() to return to settings menu
|
||||
*/
|
||||
(function(back) {
|
||||
const SETTINGS_FILE = 'coretemp.json'
|
||||
// initialize with default settings...
|
||||
let s = {
|
||||
'enabled': true,
|
||||
}
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||
for (const key in saved) {
|
||||
s[key] = saved[key];
|
||||
}
|
||||
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||
function save(key) {
|
||||
return function (value) {
|
||||
s[key] = value;
|
||||
storage.write(SETTINGS_FILE, s);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
require("Storage").write("coretemp.json", s);
|
||||
if (WIDGETS["coretemp"])
|
||||
WIDGETS["coretemp"].reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const menu = {
|
||||
'' : {'title' : 'CoreTemp sensor'},
|
||||
'< Back' : back,
|
||||
'Enabled' : {
|
||||
value : !!s.enabled,
|
||||
format : v => v ? "Yes" : "No",
|
||||
onchange : v => {
|
||||
s.enabled = v;
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
E.showMenu(menu);
|
||||
})
|
|
@ -0,0 +1,66 @@
|
|||
// TODO Change to a generic multiple sensor widget?
|
||||
|
||||
(() => {
|
||||
var settings = {};
|
||||
var count = 0;
|
||||
var core = 0;
|
||||
|
||||
// draw your widget
|
||||
function draw() {
|
||||
if (!settings.enabled)
|
||||
return;
|
||||
g.reset();
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0);
|
||||
g.setFontAlign(0, 0);
|
||||
g.clearRect(this.x, this.y, this.x + 23, this.y + 23);
|
||||
|
||||
if (count & 1) {
|
||||
g.setColor("#0f0"); // green
|
||||
} else {
|
||||
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
|
||||
}
|
||||
|
||||
g.drawImage(
|
||||
atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),
|
||||
this.x+(24-12)/2,this.y+1);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(parseInt(core)+"\n."+parseInt((core*100)%100), this.x + 24 / 2, this.y + 18);
|
||||
|
||||
g.setColor(-1);
|
||||
}
|
||||
|
||||
// Set a listener to 'blink'
|
||||
function onTemp(temp) {
|
||||
count = count + 1;
|
||||
core = temp.core;
|
||||
WIDGETS["coretemp"].draw();
|
||||
}
|
||||
|
||||
// Called by sensor app to update status
|
||||
function reload() {
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
|
||||
Bangle.removeListener('CoreTemp', onTemp);
|
||||
|
||||
if (settings.enabled) {
|
||||
WIDGETS["coretemp"].width = 24;
|
||||
Bangle.on('CoreTemp', onTemp);
|
||||
} else {
|
||||
WIDGETS["coretemp"].width = 0;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// add the widget
|
||||
WIDGETS["coretemp"] = {
|
||||
area : "tl",
|
||||
width : 24,
|
||||
draw : draw,
|
||||
reload : function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
}
|
||||
};
|
||||
// load settings, set correct widget width
|
||||
reload();
|
||||
})()
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Ported to Banglejs2
|
||||
|
|
|
@ -35,23 +35,24 @@ function provideFeedback() {
|
|||
}
|
||||
|
||||
function drawHeart() {
|
||||
g.fillCircle(40, 92, 12);
|
||||
g.fillCircle(60, 92, 12);
|
||||
g.fillPoly([29, 98, 50, 120, 71, 98]);
|
||||
var lowestPoint = g.getHeight()*3/5;
|
||||
g.fillCircle(40, lowestPoint-29, 12);
|
||||
g.fillCircle(60, lowestPoint-29, 12);
|
||||
g.fillPoly([29, lowestPoint-22, 50, lowestPoint, 71, lowestPoint-22]);
|
||||
}
|
||||
|
||||
function updateScreen() {
|
||||
const colors = [0xFFFF, 0x9492];
|
||||
g.reset().clearRect(0, 50, 250, 150);
|
||||
const colors = [0xFFFF-g.getBgColor(), 0x9492];
|
||||
g.reset().clearRect(0, 24, g.getWidth(), g.getHeight()*5/6);
|
||||
if (counter > 0) {
|
||||
g.setFont("Vector", 40).setFontAlign(0, 0);
|
||||
g.setColor(colors[counter%2]);
|
||||
drawHeart();
|
||||
g.drawString(counter + "", g.getWidth()/2, 100);
|
||||
g.drawString(counter, 120, g.getHeight()*3/5-20);
|
||||
} else {
|
||||
g.setFont("Vector", 20).setFontAlign(0, 0);
|
||||
g.drawString("RESCUE", g.getWidth()/2, 70);
|
||||
g.drawString("BREATHS", g.getWidth()/2, 120);
|
||||
g.drawString("RESCUE", g.getWidth()/2, g.getHeight()/3);
|
||||
g.drawString("BREATHS", g.getWidth()/2, g.getHeight()*3/5);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +74,7 @@ function tick() {
|
|||
interval = setInterval(tick, 60000/setting('compression_rpm'));
|
||||
|
||||
g.clear(1).setFont("6x8");
|
||||
g.drawString(setting('compression_count') + ' / ' + setting('breath_count'), 30, 200);
|
||||
g.drawString(setting('compression_count') + ' / ' + setting('breath_count'), 30, g.getHeight()*5/6);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,28 @@
|
|||
# DirAct
|
||||
|
||||
[DirAct](https://www.reelyactive.com/diract/) implementation for the Bangle.js.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/).
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
Currently implements DirAct real-time functionality.
|
||||
|
||||
|
||||
## Controls
|
||||
|
||||
None.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
[Contact reelyActive](https://www.reelyactive.com/contact/) for support/updates.
|
||||
|
||||
|
||||
## Creator
|
||||
|
||||
Developed by [jeffyactive](https://github.com/jeffyactive) of [reelyActive](https://www.reelyactive.com). DirAct is jointly developed by reelyActive and Code Blue Consulting.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkE/4AImUQgMjBpIAI+UQFAMBn4XRmJBDiYXRFwQwCC9HzF93/kAXDgSPWj4XRMAZGSDAUhiTWSAH4Ag+URAAUvRBkSAofxgUzmchX4khSw3ygIID+UiFwMiF4bIBGowIBiYvEmUjF4kxEwgABmU/mMCC4cBiUhiAXDkET+cjC4cgj5IE+MAI4MAC4RGCHQIXEgRREF44kCCIIeCDoIIBMAYkDHQJeDEwJBBn/xgYGBn8hC5cS+YoBmEfFoP/IoMxgYXJmETJIISBj/zgBOBDgITCSgYXDBoYUCDQIXBiYXNBIIXBA4IXHI44XEJIJHIC5JHEVwRkBO5qKBbYqPGgMikTXDmKPDEAPyAIJJCX4cAAAQXDIoQtBl6IEGIIXJFoYmCSAQ4BSYIXJRYJWBDQIACkM/eYQXJdYcSC4fzG4J2CC5MwK4I+CAAR4BLwQXJBwowDiQfDC5HzLAIXFGwoXILAQALC5IANC/4X/C+w"))
|
|
@ -0,0 +1,548 @@
|
|||
/**
|
||||
* Copyright reelyActive 2017-2021
|
||||
* We believe in an open Internet of Things
|
||||
*
|
||||
* DirAct is jointly developed by reelyActive and Code Blue Consulting
|
||||
*/
|
||||
|
||||
// User-configurable constants
|
||||
const INSTANCE_ID = [ 0x00, 0x00, 0x00, 0x01 ];
|
||||
const NAMESPACE_FILTER_ID = [ 0xc0, 0xde, 0xb1, 0x0e, 0x1d,
|
||||
0xd1, 0xe0, 0x1b, 0xed, 0x0c ];
|
||||
const EXCITER_INSTANCE_IDS = new Uint32Array([ 0xe8c17e45 ]);
|
||||
const RESETTER_INSTANCE_IDS = new Uint32Array([ 0x4e5e77e4 ]);
|
||||
const PROXIMITY_RSSI_THRESHOLD = -65;
|
||||
const PROXIMITY_LED_RSSI_THRESHOLD = -65;
|
||||
const PROXIMITY_TABLE_SIZE = 8;
|
||||
const DIGEST_TABLE_SIZE = 32;
|
||||
const OBSERVE_PERIOD_MILLISECONDS = 400;
|
||||
const BROADCAST_PERIOD_MILLISECONDS = 3600;
|
||||
const BROADCAST_DIGEST_PAGE_MILLISECONDS = 400;
|
||||
const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 400;
|
||||
const DIGEST_PACKET_INTERVAL_MILLISECONDS = 100;
|
||||
const DIGEST_TIME_CYCLE_THRESHOLD = 86400;
|
||||
const EXCITER_HOLDOFF_SECONDS = 60;
|
||||
const BLINK_ON_PROXIMITY = true;
|
||||
const BLINK_ON_DISTANCING = true;
|
||||
const BLINK_ON_DIGEST = true;
|
||||
const BLINK_ON_RESET = true;
|
||||
|
||||
|
||||
// Eddystone protocol constants
|
||||
const EDDYSTONE_UUID = 'feaa';
|
||||
const EDDYSTONE_UID_FRAME = 0x00;
|
||||
const EDDYSTONE_NAMESPACE_OFFSET = 2;
|
||||
const EDDYSTONE_NAMESPACE_LENGTH = 10;
|
||||
const EDDYSTONE_INSTANCE_OFFSET = 14;
|
||||
|
||||
|
||||
// DirAct constants
|
||||
const DIRACT_MANUFACTURER_ID = 0x0583; // Code Blue Consulting
|
||||
const DIRACT_PROXIMITY_FRAME = 0x01;
|
||||
const DIRACT_DIGEST_FRAME = 0x11;
|
||||
const DIRACT_DEFAULT_COUNT_LENGTH = 0x07;
|
||||
const DIRACT_INSTANCE_LENGTH = 4;
|
||||
const DIRACT_INSTANCE_OFFSET = 2;
|
||||
const MAX_NUMBER_STRONGEST = 3;
|
||||
const MAX_BATTERY_VOLTAGE = 3.3;
|
||||
const MIN_BATTERY_VOLTAGE = 3.0;
|
||||
const MAX_RSSI_TO_ENCODE = -28;
|
||||
const MIN_RSSI_TO_ENCODE = -92;
|
||||
const MAX_ACCELERATION_TO_ENCODE = 2;
|
||||
const MAX_ACCELERATION_MAGNITUDE = 0x1f;
|
||||
const INVALID_ACCELERATION_CODE = 0x20;
|
||||
const SCAN_OPTIONS = {
|
||||
filters: [
|
||||
{ manufacturerData: { 0x0583: {} } },
|
||||
{ services: [ EDDYSTONE_UUID ] }
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
// Other constants
|
||||
const BITS_PER_BYTE = 8;
|
||||
const DUMMY_INSTANCE_ID = 0;
|
||||
const DUMMY_RSSI = MIN_RSSI_TO_ENCODE;
|
||||
|
||||
|
||||
// Global variables
|
||||
let proximityInstances = new Uint32Array(PROXIMITY_TABLE_SIZE);
|
||||
let proximityRssis = new Int8Array(PROXIMITY_TABLE_SIZE);
|
||||
let digestInstances = new Uint32Array(DIGEST_TABLE_SIZE);
|
||||
let digestCounts = new Uint16Array(DIGEST_TABLE_SIZE);
|
||||
let digestTime = new Uint8Array([ 0, 0, 0 ]);
|
||||
let numberOfDigestPages = 0;
|
||||
let sensorData = [ 0x82, 0x08, 0x3f ];
|
||||
let cyclicCount = 0;
|
||||
let lastDigestTime = Math.round(getTime());
|
||||
let lastResetTime = Math.round(getTime());
|
||||
let isExciterPresent = false;
|
||||
let isResetterPresent = false;
|
||||
let isProximityDetected = false;
|
||||
let menu = {
|
||||
"": { "title": "-- DirAct --" }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initiate observer mode, scanning for devices in proximity.
|
||||
*/
|
||||
function observe() {
|
||||
proximityInstances.fill(DUMMY_INSTANCE_ID); // Reset proximity
|
||||
proximityRssis.fill(DUMMY_RSSI); // table data
|
||||
isExciterPresent = false;
|
||||
isResetterPresent = false;
|
||||
|
||||
NRF.setScan(handleDiscoveredDevice, SCAN_OPTIONS); // Start scanning
|
||||
setTimeout(broadcast, OBSERVE_PERIOD_MILLISECONDS); // ...until period end
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the scan results and initiate broadcaster mode, advertising either
|
||||
* proximity or digest packets, in consequence.
|
||||
*/
|
||||
function broadcast() {
|
||||
NRF.setScan(); // Stop scanning
|
||||
|
||||
let sortedProximityIndices = getSortedIndices(proximityRssis);
|
||||
|
||||
updateDigestTable(sortedProximityIndices);
|
||||
updateSensorData();
|
||||
|
||||
let currentTime = Math.round(getTime());
|
||||
let isExcited = isExciterPresent &&
|
||||
((currentTime - lastDigestTime) > EXCITER_HOLDOFF_SECONDS);
|
||||
|
||||
if(isResetterPresent) {
|
||||
if(BLINK_ON_RESET) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
lastResetTime = currentTime;
|
||||
resetDigest();
|
||||
broadcastProximity(sortedProximityIndices);
|
||||
}
|
||||
else if(isExcited) {
|
||||
let sortedDigestIndices = getSortedIndices(digestCounts);
|
||||
compileDigest();
|
||||
broadcastDigest(sortedDigestIndices, 0);
|
||||
}
|
||||
else {
|
||||
broadcastProximity(sortedProximityIndices);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiate broadcaster mode advertising proximity packets.
|
||||
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||
*/
|
||||
function broadcastProximity(sortedIndices) {
|
||||
let advertisingOptions = {
|
||||
interval: PROXIMITY_PACKET_INTERVAL_MILLISECONDS,
|
||||
showName: false,
|
||||
manufacturer: DIRACT_MANUFACTURER_ID
|
||||
};
|
||||
|
||||
advertisingOptions.manufacturerData = compileProximityData(sortedIndices);
|
||||
NRF.setAdvertising({}, advertisingOptions); // Start advertising
|
||||
setTimeout(observe, BROADCAST_PERIOD_MILLISECONDS); // ...until period end
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiate broadcaster mode advertising digest packets.
|
||||
* @param {TypedArray} sortedIndices The sorted digest table indices.
|
||||
* @param {Number} pageNumber The page number to broadcast.
|
||||
*/
|
||||
function broadcastDigest(sortedIndices, pageNumber) {
|
||||
let isLastPage = (pageNumber === (numberOfDigestPages - 1));
|
||||
let advertisingOptions = {
|
||||
interval: DIGEST_PACKET_INTERVAL_MILLISECONDS,
|
||||
showName: false,
|
||||
manufacturer: DIRACT_MANUFACTURER_ID
|
||||
};
|
||||
|
||||
advertisingOptions.manufacturerData = compileDigestData(sortedIndices,
|
||||
pageNumber);
|
||||
NRF.setAdvertising({}, advertisingOptions); // Start advertising
|
||||
|
||||
if(isLastPage) {
|
||||
setTimeout(observe, BROADCAST_DIGEST_PAGE_MILLISECONDS);
|
||||
if(getTime() > DIGEST_TIME_CYCLE_THRESHOLD) {
|
||||
lastResetTime = Math.round(getTime());
|
||||
resetDigest();
|
||||
}
|
||||
if(BLINK_ON_DIGEST) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setTimeout(broadcastDigest, BROADCAST_DIGEST_PAGE_MILLISECONDS,
|
||||
sortedIndices, ++pageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given device discovered on scan and process further if
|
||||
* Eddystone-UID or DirAct.
|
||||
* @param {BluetoothDevice} device The discovered device.
|
||||
*/
|
||||
function handleDiscoveredDevice(device) {
|
||||
let isEddystone = (device.hasOwnProperty('services') &&
|
||||
device.hasOwnProperty('serviceData') &&
|
||||
(device.services[0] === EDDYSTONE_UUID));
|
||||
let isManufacturer = (device.hasOwnProperty('manufacturer') &&
|
||||
device.manufacturer === DIRACT_MANUFACTURER_ID);
|
||||
|
||||
if(isEddystone) {
|
||||
let isEddystoneUID = (device.serviceData[EDDYSTONE_UUID][0] ===
|
||||
EDDYSTONE_UID_FRAME);
|
||||
if(isEddystoneUID) {
|
||||
handleEddystoneUidDevice(device.serviceData[EDDYSTONE_UUID], device.rssi);
|
||||
}
|
||||
}
|
||||
else if(isManufacturer) {
|
||||
let isDirAct = ((device.manufacturerData[0] === DIRACT_PROXIMITY_FRAME) ||
|
||||
(device.manufacturerData[0] === DIRACT_DIGEST_FRAME));
|
||||
if(isDirAct) {
|
||||
handleDirActDevice(device.manufacturerData, device.rssi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given Eddystone-UID device, adding to the devices in range if
|
||||
* it meets the filter criteria.
|
||||
* @param {Array} serviceData The Eddystone service data.
|
||||
* @param {Number} rssi The received signal strength.
|
||||
*/
|
||||
function handleEddystoneUidDevice(serviceData, rssi) {
|
||||
for(let cByte = 0; cByte < EDDYSTONE_NAMESPACE_LENGTH; cByte++) {
|
||||
let namespaceIndex = EDDYSTONE_NAMESPACE_OFFSET + cByte;
|
||||
if(serviceData[namespaceIndex] !== NAMESPACE_FILTER_ID[cByte]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let instanceId = 0;
|
||||
let bitShift = (DIRACT_INSTANCE_LENGTH - 1) * BITS_PER_BYTE;
|
||||
|
||||
for(let cByte = 0; cByte < DIRACT_INSTANCE_LENGTH; cByte++) {
|
||||
let instanceByte = serviceData[EDDYSTONE_INSTANCE_OFFSET + cByte];
|
||||
instanceId += instanceByte << bitShift;
|
||||
bitShift -= BITS_PER_BYTE;
|
||||
}
|
||||
|
||||
let unsignedInstanceId = new Uint32Array([instanceId])[0];
|
||||
|
||||
if(EXCITER_INSTANCE_IDS.indexOf(unsignedInstanceId) >= 0) {
|
||||
isExciterPresent = true;
|
||||
}
|
||||
else if(RESETTER_INSTANCE_IDS.indexOf(unsignedInstanceId) >= 0) {
|
||||
isResetterPresent = true;
|
||||
}
|
||||
else {
|
||||
updateProximityTable(instanceId, rssi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given DirAct device, adding to the devices in range if
|
||||
* it meets the filter criteria.
|
||||
* @param {Array} manufacturerData The DirAct manufacturer data.
|
||||
* @param {Number} rssi The received signal strength.
|
||||
*/
|
||||
function handleDirActDevice(manufacturerData, rssi) {
|
||||
let instanceId = 0;
|
||||
let bitShift = (DIRACT_INSTANCE_LENGTH - 1) * BITS_PER_BYTE;
|
||||
|
||||
for(let cByte = DIRACT_INSTANCE_OFFSET;
|
||||
cByte < DIRACT_INSTANCE_OFFSET + DIRACT_INSTANCE_LENGTH; cByte++) {
|
||||
let instanceByte = manufacturerData[cByte];
|
||||
instanceId += instanceByte << bitShift;
|
||||
bitShift -= BITS_PER_BYTE;
|
||||
}
|
||||
|
||||
updateProximityTable(instanceId, rssi);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the proximity table with the given instance's RSSI. If the instance
|
||||
* already exists, combine RSSI values in a weighted average.
|
||||
* @param {String} instanceId The DirAct 4-byte instance id as a 32-bit integer.
|
||||
* @param {Number} rssi The received signal strength.
|
||||
*/
|
||||
function updateProximityTable(instanceId, rssi) {
|
||||
let instanceIndex = proximityInstances.indexOf(instanceId);
|
||||
let isNewInstance = (instanceIndex < 0);
|
||||
|
||||
if(isNewInstance) {
|
||||
let nextIndex = proximityInstances.indexOf(DUMMY_INSTANCE_ID);
|
||||
if(nextIndex >= 0) {
|
||||
proximityInstances[nextIndex] = instanceId;
|
||||
proximityRssis[nextIndex] = rssi;
|
||||
}
|
||||
}
|
||||
else {
|
||||
proximityRssis[instanceIndex] = (proximityRssis[instanceIndex] + rssi) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the digest table based on the proximity table and its sorted indices.
|
||||
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||
*/
|
||||
function updateDigestTable(sortedIndices) {
|
||||
for(let cInstance = 0; cInstance < PROXIMITY_TABLE_SIZE; cInstance++) {
|
||||
let proximityIndex = sortedIndices[cInstance];
|
||||
|
||||
if(proximityRssis[proximityIndex] >= PROXIMITY_RSSI_THRESHOLD) {
|
||||
let instanceId = proximityInstances[proximityIndex];
|
||||
let instanceIndex = digestInstances.indexOf(instanceId);
|
||||
let isNewInstance = (instanceIndex < 0);
|
||||
|
||||
if(isNewInstance) {
|
||||
let nextIndex = digestInstances.indexOf(DUMMY_INSTANCE_ID);
|
||||
if(nextIndex >= 0) {
|
||||
digestInstances[nextIndex] = instanceId;
|
||||
digestCounts[nextIndex] = 1;
|
||||
}
|
||||
}
|
||||
else if(digestCounts[instanceIndex] < 65535) {
|
||||
digestCounts[instanceIndex]++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cInstance = PROXIMITY_TABLE_SIZE; // Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compile the digest from the digest table.
|
||||
*/
|
||||
function compileDigest() {
|
||||
let numberOfEntries = digestCounts.findIndex(count => count === 0);
|
||||
let currentTime = Math.round(getTime());
|
||||
let elapsedTime = currentTime - lastResetTime;
|
||||
if(numberOfEntries < 0) {
|
||||
numberOfEntries = DIGEST_TABLE_SIZE;
|
||||
}
|
||||
digestTime[0] = (elapsedTime >> 16) & 0xff;
|
||||
digestTime[1] = (elapsedTime >> 8) & 0xff;
|
||||
digestTime[2] = elapsedTime & 0xff;
|
||||
numberOfDigestPages = Math.max(1, Math.min(8, Math.ceil(numberOfEntries/3)));
|
||||
lastDigestTime = currentTime;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Clear the digest table.
|
||||
*/
|
||||
function resetDigest() {
|
||||
digestInstances.fill(DUMMY_INSTANCE_ID);
|
||||
digestCounts.fill(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the DirAct proximity data.
|
||||
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||
*/
|
||||
function compileProximityData(sortedIndices) {
|
||||
let data = [
|
||||
DIRACT_PROXIMITY_FRAME, DIRACT_DEFAULT_COUNT_LENGTH,
|
||||
INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3],
|
||||
sensorData[0], sensorData[1], sensorData[2]
|
||||
];
|
||||
let isNewProximityDetected = false;
|
||||
|
||||
for(let cInstance = 0; cInstance < MAX_NUMBER_STRONGEST; cInstance++) {
|
||||
let index = sortedIndices[cInstance];
|
||||
|
||||
if(proximityRssis[index] >= PROXIMITY_RSSI_THRESHOLD) {
|
||||
let instanceId = proximityInstances[index];
|
||||
data.push((instanceId >> 24) & 0xff, (instanceId >> 16) & 0xff,
|
||||
(instanceId >> 8) & 0xff, instanceId & 0xff,
|
||||
encodeRssi(proximityRssis[index]));
|
||||
if(proximityRssis[index] >= PROXIMITY_LED_RSSI_THRESHOLD) {
|
||||
isNewProximityDetected = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cInstance = PROXIMITY_TABLE_SIZE; // Break
|
||||
}
|
||||
}
|
||||
|
||||
cyclicCount = (cyclicCount + 1) % 8;
|
||||
|
||||
data[1] = (cyclicCount << 5) + (data.length - 2);
|
||||
|
||||
if(isProximityDetected && !isNewProximityDetected && BLINK_ON_DISTANCING) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
else if(isNewProximityDetected && BLINK_ON_PROXIMITY) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
isProximityDetected = isNewProximityDetected;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the DirAct digest data.
|
||||
* @param {TypedArray} sortedIndices The sorted digest table indices.
|
||||
* @param {Number} digestPage The page of the digest to compile.
|
||||
*/
|
||||
function compileDigestData(sortedIndices, digestPage) {
|
||||
let isLastPage = (digestPage === (numberOfDigestPages - 1));
|
||||
|
||||
let digestStatus = digestTime[0] & 0x7f;
|
||||
if(isLastPage) {
|
||||
digestStatus |= 0x80;
|
||||
}
|
||||
|
||||
let data = [
|
||||
DIRACT_DIGEST_FRAME, DIRACT_DEFAULT_COUNT_LENGTH,
|
||||
INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3],
|
||||
digestStatus, digestTime[1], digestTime[2]
|
||||
];
|
||||
let pageIndex = digestPage * 3;
|
||||
|
||||
for(let cInstance = pageIndex; cInstance < (pageIndex + 3); cInstance++) {
|
||||
let index = sortedIndices[cInstance];
|
||||
|
||||
if(digestCounts[index] > 0) {
|
||||
let instanceId = digestInstances[index];
|
||||
let encodedCount = digestCounts[index];
|
||||
if(encodedCount > 127) {
|
||||
encodedCount = 0x80 | (Math.min((encodedCount >> 8), 0x7f) & 0x7f);
|
||||
}
|
||||
data.push((instanceId >> 24) & 0xff, (instanceId >> 16) & 0xff,
|
||||
(instanceId >> 8) & 0xff, instanceId & 0xff,
|
||||
encodedCount);
|
||||
}
|
||||
else {
|
||||
cInstance = pageIndex + 3; // Break
|
||||
}
|
||||
}
|
||||
|
||||
data[1] = (digestPage << 5) + (data.length - 2);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode the given RSSI.
|
||||
* @param {Number} rssi The given RSSI.
|
||||
* @return {Number} The encoded RSSI.
|
||||
*/
|
||||
function encodeRssi(rssi) {
|
||||
rssi = Math.round(rssi);
|
||||
|
||||
if(rssi >= MAX_RSSI_TO_ENCODE) {
|
||||
return 0x3f;
|
||||
}
|
||||
if(rssi <= MIN_RSSI_TO_ENCODE) {
|
||||
return 0x00;
|
||||
}
|
||||
return rssi - MIN_RSSI_TO_ENCODE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode the battery percentage.
|
||||
* @return {Number} The battery percentage.
|
||||
*/
|
||||
function encodeBatteryPercentage() {
|
||||
let voltage = NRF.getBattery();
|
||||
|
||||
if(voltage <= MIN_BATTERY_VOLTAGE) {
|
||||
return 0x00;
|
||||
}
|
||||
if(voltage >= MAX_BATTERY_VOLTAGE) {
|
||||
return 0x3f;
|
||||
}
|
||||
|
||||
return Math.round(0x3f * (voltage - MIN_BATTERY_VOLTAGE) /
|
||||
(MAX_BATTERY_VOLTAGE - MIN_BATTERY_VOLTAGE));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode the acceleration.
|
||||
* @return {Array} The encoded acceleration [ x, y, z ].
|
||||
*/
|
||||
function encodeAcceleration() {
|
||||
let encodedAcceleration = { x: INVALID_ACCELERATION_CODE,
|
||||
y: INVALID_ACCELERATION_CODE,
|
||||
z: INVALID_ACCELERATION_CODE };
|
||||
|
||||
let acceleration = { x: Bangle.getAccel().x,
|
||||
y: Bangle.getAccel().y,
|
||||
z: Bangle.getAccel().z };
|
||||
|
||||
for(let axis in acceleration) {
|
||||
let magnitude = acceleration[axis];
|
||||
let encodedMagnitude = Math.min(MAX_ACCELERATION_MAGNITUDE,
|
||||
Math.round(MAX_ACCELERATION_MAGNITUDE *
|
||||
(Math.abs(magnitude) /
|
||||
MAX_ACCELERATION_TO_ENCODE)));
|
||||
if(magnitude < 0) {
|
||||
encodedMagnitude = 0x3f - encodedMagnitude;
|
||||
}
|
||||
encodedAcceleration[axis] = encodedMagnitude;
|
||||
}
|
||||
|
||||
return encodedAcceleration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the sensor data (battery & acceleration) for the advertising packet.
|
||||
*/
|
||||
function updateSensorData() {
|
||||
|
||||
// Update the battery measurement each time the cyclic count resets
|
||||
if(cyclicCount === 0) {
|
||||
encodedBattery = encodeBatteryPercentage();
|
||||
}
|
||||
|
||||
encodedAcceleration = encodeAcceleration();
|
||||
|
||||
sensorData[0] = ((encodedAcceleration.x << 2) & 0xfc) |
|
||||
((encodedAcceleration.y >> 4) & 0x3f);
|
||||
sensorData[1] = ((encodedAcceleration.y << 4) & 0xf0) |
|
||||
((encodedAcceleration.z >> 2) & 0x0f);
|
||||
sensorData[2] = ((encodedAcceleration.z << 6) & 0xc0) |
|
||||
(encodedBattery & 0x3f);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the sorted order of the indices of the given array.
|
||||
* @return {Uint8Array} The array of indices sorted in descending order.
|
||||
*/
|
||||
function getSortedIndices(unsortedArray) {
|
||||
let sortedIndices = new Uint8Array(unsortedArray.length);
|
||||
|
||||
sortedIndices.forEach((value, index) => sortedIndices[index] = index);
|
||||
sortedIndices.sort((a, b) => unsortedArray[b] - unsortedArray[a]);
|
||||
|
||||
return sortedIndices;
|
||||
}
|
||||
|
||||
|
||||
// On start: begin DirAct operation and display the menu
|
||||
g.clear();
|
||||
E.showMenu(menu);
|
||||
observe();
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -11,4 +11,4 @@ The year itself begins on the December solstice. Because that always happens, th
|
|||
|
||||
The epoch (year numbering) begins in the last year when the perihelion coincided with the June solstice, near the beginning of the Holocene era. That astronomical basis makes the calendar free from politics, religion, or geography.
|
||||
|
||||
While the year number remains cardinal, BTN5 toggles between cardinal and ordinal for the rest of the calendar segments. BTN4 adds or removes a quickly changing digit to or from the clock.
|
||||
While the year number remains cardinal, tapping on the right side of the watch face toggles between cardinal and ordinal for the rest of the calendar segments. Tapping on the left adds or removes a quickly changing digit to or from the clock.
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
// Positioning values for graphics buffers
|
||||
const g_height = 80; // total graphics height
|
||||
const g_x_off = 0; // position from left was 16, then 8 here
|
||||
const g_y_off = (184 - g_height)/2; // vertical center for graphics region was 240
|
||||
const g_width = 240 - 2 * g_x_off; // total graphics width
|
||||
const g_height_d = 28; // height of date region was 32
|
||||
const g_y_off_d = 0; // y position of date region within graphics region
|
||||
const spacing = 0; // space between date and time in graphics region
|
||||
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
|
||||
const g_height_t = 44; // height of time region was 48
|
||||
|
||||
// Other vars
|
||||
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
|
||||
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
|
||||
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
|
||||
const timeColour = "#ffffff";
|
||||
const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#8383ff","#ff00ff","#ff0080"]; //blue was 0000ff
|
||||
const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line ft w 32, 32-g, step 20
|
||||
const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line ft w 32, 62-g, step 20
|
||||
const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line ft w 48, 64-g, step 30
|
||||
const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30
|
||||
const baseYear = 11584;
|
||||
const baseDate = Date(2020,11,21); // month values run from 0 to 11
|
||||
let accum = new Date(baseDate.getTime());
|
||||
let sequence = [];
|
||||
let timeActiveUntil;
|
||||
let addTimeDigit = false;
|
||||
let dateFormat = false;
|
||||
let lastX = 999999999;
|
||||
let res = {};
|
||||
//var last_time_log = 0;
|
||||
|
||||
var drawtime_timeout;
|
||||
|
||||
// Date and time graphics buffers
|
||||
var dateColour = "#ffffff"; // override later
|
||||
var timeColour2 = timeColour;
|
||||
var g_d = Graphics.createArrayBuffer(g_width,g_height_d,1,{'msb':true});
|
||||
var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true});
|
||||
// Set screen mode and function to write graphics buffers
|
||||
//Bangle.setLCDMode();
|
||||
g.clear(); // start with blank screen
|
||||
g.flip = function()
|
||||
{
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(dateColour);
|
||||
g.drawImage(
|
||||
{
|
||||
width:g_width,
|
||||
height:g_height_d,
|
||||
buffer:g_d.buffer
|
||||
}, g_x_off, g_y_off + g_y_off_d);
|
||||
g.setColor(timeColour2);
|
||||
g.drawImage(
|
||||
{
|
||||
width:g_width,
|
||||
height:g_height_t,
|
||||
buffer:g_t.buffer
|
||||
}, g_x_off, g_y_off + g_y_off_t);
|
||||
};
|
||||
|
||||
setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); //was BTN1
|
||||
setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); //was BTN2
|
||||
//setWatch(function(){ modeWeather(); }, BTN3, {repeat:true});
|
||||
//setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true});
|
||||
//setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true});
|
||||
|
||||
Bangle.on('touch', function(button, xy) { //from Gordon Williams
|
||||
if (button==1) toggleTimeDigits();
|
||||
if (button==2) toggleDateFormat();
|
||||
});
|
||||
|
||||
function buildSequence(targ){
|
||||
for(let i=0;i<targ.length;++i){
|
||||
sequence.push(new Date(accum.getTime()));
|
||||
accum.setDate(accum.getDate()+targ[i]);
|
||||
}
|
||||
}
|
||||
buildSequence(B2);
|
||||
buildSequence(B2);
|
||||
buildSequence(A1);
|
||||
buildSequence(B1);
|
||||
buildSequence(B2);
|
||||
buildSequence(B2);
|
||||
buildSequence(A1);
|
||||
buildSequence(B1);
|
||||
buildSequence(B2);
|
||||
buildSequence(B2);
|
||||
buildSequence(A1);
|
||||
buildSequence(B1);
|
||||
buildSequence(B2);
|
||||
|
||||
function getDate(dt){
|
||||
let index = sequence.findIndex(n => n > dt)-1;
|
||||
let year = baseYear+parseInt(index/12);
|
||||
let month = index % 12;
|
||||
let day = parseInt((dt-sequence[index])/86400000);
|
||||
let colour = dateColours[day % 6];
|
||||
if(day==30){ colour=dateColours[6]; }
|
||||
return({"year":year,"month":month,"day":day,"colour":colour});
|
||||
}
|
||||
function toggleTimeDigits(){
|
||||
addTimeDigit = !addTimeDigit;
|
||||
modeTime();
|
||||
}
|
||||
function toggleDateFormat(){
|
||||
dateFormat = !dateFormat;
|
||||
modeTime();
|
||||
}
|
||||
function formatDate(res,dateFormat){
|
||||
let yyyy = res.year.toString(12);
|
||||
calenDef = calen10;
|
||||
if(!dateFormat){ //ordinal format
|
||||
let mm = ("0"+(res.month+1).toString(12)).substr(-2);
|
||||
let dd = ("0"+(res.day+1).toString(12)).substr(-2);
|
||||
if(res.day==30){
|
||||
calenDef = calen7;
|
||||
let m = ((res.month+1).toString(12)).substr(-2);
|
||||
return(yyyy+"-"+"S"+m); // ordinal format
|
||||
}
|
||||
return(yyyy+"-"+mm+"-"+dd);
|
||||
}
|
||||
let m = res.month.toString(12); // cardinal format
|
||||
let w = parseInt(res.day/6);
|
||||
let d = res.day%6;
|
||||
//return(yyyy+"-"+res.month+"-"+w+"-"+d);
|
||||
return(yyyy+"-"+m+"-"+w+"-"+d);
|
||||
}
|
||||
|
||||
function writeDozTime(text,def){
|
||||
let pts = def.pts;
|
||||
let x=def.pt0[0];
|
||||
let y=def.pt0[1];
|
||||
g_t.clear();
|
||||
g_t.setFont("Vector",def.size);
|
||||
for(let i in text){
|
||||
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s are new
|
||||
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s are new
|
||||
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
|
||||
x = x+def.step[0];
|
||||
y = y+def.step[1];
|
||||
}
|
||||
}
|
||||
function writeDozDate(text,def,colour){
|
||||
|
||||
dateColour = colour;
|
||||
let pts = def.pts;
|
||||
let x=def.pt0[0];
|
||||
let y=def.pt0[1];
|
||||
g_d.clear();
|
||||
g_d.setFont("Vector",def.size);
|
||||
for(let i in text){
|
||||
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s new
|
||||
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s new
|
||||
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
|
||||
x = x+def.step[0];
|
||||
y = y+def.step[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Functions for time mode
|
||||
function drawTime()
|
||||
{
|
||||
let dt = new Date();
|
||||
let date = "";
|
||||
let timeDef;
|
||||
let x = 0;
|
||||
dt.setDate(dt.getDate());
|
||||
if(addTimeDigit){
|
||||
x =
|
||||
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
|
||||
let msg = "00000"+Math.floor(x).toString(12);
|
||||
let time = msg.substr(-5,3)+"."+msg.substr(-2);
|
||||
let wait = 347*(1-(x%1));
|
||||
timeDef = time6;
|
||||
} else {
|
||||
x =
|
||||
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
|
||||
let msg = "0000"+Math.floor(x).toString(12);
|
||||
let time = msg.substr(-4,3)+"."+msg.substr(-1);
|
||||
let wait = 4167*(1-(x%1));
|
||||
timeDef = time5;
|
||||
}
|
||||
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
|
||||
date = formatDate(res,dateFormat);
|
||||
if(dt<timeActiveUntil)
|
||||
{
|
||||
// Write to background buffers, then display on screen
|
||||
writeDozDate(date,calenDef,res.colour);
|
||||
writeDozTime(time,timeDef);
|
||||
g.flip();
|
||||
// Ready next interval
|
||||
drawtime_timeout = setTimeout(drawTime,wait);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear screen
|
||||
g_d.clear();
|
||||
g_t.clear();
|
||||
g.flip();
|
||||
|
||||
}
|
||||
lastX = x;
|
||||
}
|
||||
function modeTime()
|
||||
{
|
||||
timeActiveUntil = new Date();
|
||||
timeActiveUntil.setDate(timeActiveUntil.getDate());
|
||||
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+86400);
|
||||
if (typeof drawtime_timeout !== 'undefined')
|
||||
{
|
||||
clearTimeout(drawtime_timeout);
|
||||
}
|
||||
drawTime();
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Functions for weather mode - TODO
|
||||
// function drawWeather() {}
|
||||
// function modeWeather() {}
|
||||
|
||||
// Start time on twist
|
||||
Bangle.on('twist',function() {
|
||||
modeTime();
|
||||
});
|
||||
|
||||
// Time fix with GPS
|
||||
function fixTime() {
|
||||
Bangle.on("GPS",function cb(g) {
|
||||
Bangle.setGPSPower(0,"time");
|
||||
Bangle.removeListener("GPS",cb);
|
||||
if (!g.time || (g.time.getFullYear()<2000) ||
|
||||
(g.time.getFullYear()>2200)) {
|
||||
} else {
|
||||
// We have a GPS time. Set time
|
||||
setTime(g.time.getTime()/1000);
|
||||
}
|
||||
});
|
||||
Bangle.setGPSPower(1,"time");
|
||||
setTimeout(fixTime, 10*60*1000); // every 10 minutes
|
||||
}
|
||||
// Start time fixing with GPS on next 10 minute interval
|
||||
setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000);
|
|
@ -1,8 +1,18 @@
|
|||
# Desktop style App Launcher
|
||||
|
||||
Bangle 1:
|
||||
|
||||

|
||||
|
||||
In the picture above, the Settings app is selected.
|
||||
|
||||
Bangle 2:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Controls- Bangle
|
||||
|
||||
**BTN1** - move backward through app icons on a page
|
||||
|
@ -21,4 +31,4 @@ In the picture above, the Settings app is selected.
|
|||
|
||||
**Swipe Left** - move to next page of app icons
|
||||
|
||||
**Swipe Right** - move to previous page of app icons
|
||||
**Swipe Right** - move to previous page of app icons
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: first release
|
|
@ -0,0 +1,20 @@
|
|||
# Flipper
|
||||
|
||||

|
||||
|
||||
*A utility to switch from the dark to the light theme and vice versa*
|
||||
|
||||
* If the current theme is dark it will switch to the light theme
|
||||
* If the current theme is light it will switch to the dark theme
|
||||
* Combine with the awesome pattern launcher and it saves loads of time
|
||||
|
||||
|
||||
## Demo Video
|
||||
|
||||
There's no screenshot but there is a [demo video.](https://espruino.microco.sm/api/v1/files/9caa1afef7e4cce1d9b518af2dd271f1a57c5ecc.mp4)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
* Nothing left to add
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
|
@ -0,0 +1,39 @@
|
|||
const storage = require('Storage');
|
||||
let settings = storage.readJSON('setting.json', 1);
|
||||
|
||||
function cl(x) { return g.setColor(x).getColor(); }
|
||||
|
||||
function upd(th) {
|
||||
g.theme = th;
|
||||
settings.theme = th;
|
||||
storage.write('setting.json', settings);
|
||||
delete g.reset;
|
||||
g._reset = g.reset;
|
||||
g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); };
|
||||
g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); };
|
||||
g.clear(1);
|
||||
}
|
||||
|
||||
function flipTheme() {
|
||||
if (!g.theme.dark) {
|
||||
upd({
|
||||
fg:cl("#fff"), bg:cl("#000"),
|
||||
fg2:cl("#0ff"), bg2:cl("#000"),
|
||||
fgH:cl("#fff"), bgH:cl("#00f"),
|
||||
dark:true
|
||||
});
|
||||
} else {
|
||||
upd({
|
||||
fg:cl("#000"), bg:cl("#fff"),
|
||||
fg2:cl("#000"), bg2:cl("#cff"),
|
||||
fgH:cl("#000"), bgH:cl("#0ff"),
|
||||
dark:false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
flipTheme();
|
||||
setTimeout(load, 20);
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4X/AAO/mMUzs975K+ggLKysUAYNVqoLFitUoAKBqtQBYkJBIQABqwLEgQLEqtABggJDqkVBaoNCBZQwEgILWgoJENYsVBIcVBYpDEgpSIBYMBKQg6CuogCBY1UgoLCXAQLDqAsDBYhSBqEJHAoLDoEBcQ4LBEwILIMooLdIg4LaVoyaGERLcFao4LIdRAACYYUQBY5RKAH4Ar"))
|
After Width: | Height: | Size: 644 B |
|
@ -2,3 +2,5 @@
|
|||
0.02: Add support for ZIPs
|
||||
Find and download ZIPs direct from the Espruino website
|
||||
Take 'beta' tag off
|
||||
0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later
|
||||
Add CRC checks for common bootloaders that we know don't work
|
||||
|
|
|
@ -60,6 +60,7 @@ function onInit(device) {
|
|||
document.getElementById("fw-unknown").style = "display:none";
|
||||
document.getElementById("fw-ok").style = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkForFileOnServer() {
|
||||
|
@ -264,6 +265,8 @@ function createJS_app(binary, startAddress, endAddress) {
|
|||
bin32[3] = VERSION; // VERSION! Use this to test ourselves
|
||||
console.log("CRC 0x"+bin32[2].toString(16));
|
||||
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
|
||||
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 2v10.219 needs update"); load();}\n`;
|
||||
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("BOOTLOADER 2v10.236 needs update"); load();}\n`;
|
||||
hexJS += '\x10var s = require("Storage");\n';
|
||||
hexJS += '\x10s.erase(".firmware");\n';
|
||||
var CHUNKSIZE = 2048;
|
||||
|
@ -291,20 +294,14 @@ function createJS_bootloader(binary, startAddress, endAddress) {
|
|||
var chunk = btoa(new Uint8Array(binary.buffer, binary.byteOffset+i, l));
|
||||
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
|
||||
}
|
||||
// hexJS += `\x10(function() {
|
||||
// if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC!";
|
||||
// var f = require("Flash");
|
||||
// for (var i=${startAddress};i<${endAddress};i+=4096) f.erasePage(i);
|
||||
// f.write(_fw,${startAddress});
|
||||
// E.reboot();
|
||||
// })();\n`;
|
||||
hexJS += `\x10if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
|
||||
hexJS += '\x10var f = require("Flash");\n';
|
||||
hexJS += `\x10(function() { if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
|
||||
hexJS += 'E.showMessage("Flashing Bootloader...")\n';
|
||||
hexJS += 'E.setFlags({unsafeFlash:1})\n';
|
||||
hexJS += 'var f = require("Flash");\n';
|
||||
for (var i=startAddress;i<endAddress;i+=4096)
|
||||
hexJS += '\x10f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `\x10f.write(_fw,${startAddress});\n`;
|
||||
// hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
|
||||
// hexJS += '\x10setTimeout(()=>E.reboot(), 2000);\n';
|
||||
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `f.write(_fw,${startAddress});\n`;
|
||||
hexJS += `})()\n`;
|
||||
}
|
||||
|
||||
function fileLoaded() {
|
||||
|
|
|
@ -2,3 +2,6 @@
|
|||
0.03: Show number of satellites while waiting for fix
|
||||
0.04: Add Maidenhead readout of GPS location
|
||||
0.05: Refactor to use 'layout' library for multi-device support
|
||||
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)
|
||||
|
|
|
@ -16,13 +16,20 @@ var lastFix = {
|
|||
time: 0,
|
||||
satellites: 0
|
||||
};
|
||||
var nofix = 0;
|
||||
var SATinView = 0;
|
||||
var nofBD = 0;
|
||||
var nofGP = 0;
|
||||
var listenerGPSraw = 1;
|
||||
|
||||
function formatTime(now) {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
var time = fd[4].substr(0, 5);
|
||||
var date = [fd[0], fd[1], fd[2]].join(" ");
|
||||
return time + " - " + date;
|
||||
if (now == undefined) {
|
||||
return "no GPS time available";
|
||||
} else {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
var time = fd[4].substr(0, 5);
|
||||
var date = [fd[0], fd[1], fd[2]].join(" ");
|
||||
return time + " - " + date;
|
||||
}
|
||||
}
|
||||
function getMaidenHead(param1,param2){
|
||||
var lat=-100.0;
|
||||
|
@ -77,9 +84,9 @@ function onGPS(fix) {
|
|||
{type:"txt", font:"6x8", label:"Waiting for GPS" },
|
||||
{type:"h", c: [
|
||||
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
|
||||
{type:"txt", font:"6x8", pad:3, label:"Satellites" }
|
||||
{type:"txt", font:"6x8", pad:3, label:"Satellites used" }
|
||||
]},
|
||||
{type:"txt", font:"6x8", label:"", id:"progress" }
|
||||
{type:"txt", font:"6x8", label:"", fillx:true, id:"progress" }
|
||||
]},{lazy:true});
|
||||
}
|
||||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
|
@ -87,7 +94,10 @@ function onGPS(fix) {
|
|||
}
|
||||
lastFix = fix;
|
||||
if (fix.fix) {
|
||||
nofix = 0;
|
||||
if (listenerGPSraw == 1) {
|
||||
Bangle.removeListener('GPS-raw', onGPSraw);
|
||||
listenerGPSraw = 0;
|
||||
}
|
||||
var locale = require("locale");
|
||||
var satellites = fix.satellites;
|
||||
var maidenhead = getMaidenHead(fix.lat,fix.lon);
|
||||
|
@ -99,13 +109,34 @@ function onGPS(fix) {
|
|||
layout.sat.label = "Satellites: "+satellites;
|
||||
layout.maidenhead.label = "Maidenhead: "+maidenhead;
|
||||
} else {
|
||||
if (listenerGPSraw == 0) {
|
||||
Bangle.on('GPS-raw', onGPSraw);
|
||||
listenerGPSraw = 1;
|
||||
}
|
||||
layout.sat.label = fix.satellites;
|
||||
nofix = (nofix+1) % 4;
|
||||
layout.progress.label = ".".repeat(nofix) + " ".repeat(4-nofix);
|
||||
layout.progress.label = "in view: " + SATinView;
|
||||
}
|
||||
layout.render();
|
||||
}
|
||||
|
||||
function onGPSraw(nmea) {
|
||||
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
||||
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
|
||||
SATinView = nofBD + nofGP;
|
||||
}
|
||||
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.on('GPS', onGPS);
|
||||
Bangle.on('GPS-raw', onGPSraw);
|
||||
|
||||
function exitApp() {
|
||||
load();
|
||||
}
|
||||
|
||||
setWatch(_=>exitApp(), BTN1);
|
||||
if (global.BTN2) {
|
||||
setWatch(_=>exitApp(), BTN2);
|
||||
setWatch(_=>exitApp(), BTN3);
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: First version
|
||||
0.02: Enchanced contrast of icon image
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA="))
|
||||
require("heatshrink").decompress(atob("mEw4UA///iADCn+EqoAWqAuJgoLcn/8BZENGwNwBY/VBYNXBY0DJ4fABYoiCEggLDmtX1Wq6tcBYvVrQLB0owCBYdVtQLB1NVBYg6BBQIABHgQLCgIuCGAVABYcNqwtBGIOVJAILFyoCCBY5eBBdo7IgIIB1t6BYJfENZaDB9QKB1aDFBYKbEBYizBrwLB2qnFdwSmCX401cYdUBZTjGfYgHCBZB2BBYhUBAARSBBYhICAAIGCBYkVBQJSCBYpICIwQLFHgQ6CBYo8CHQQLFHgQFDBYsVQIQLHgo6DBY0BHQYLGgY6DBYwAFBbCjDACY"))
|
||||
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: base code
|
||||
0.02: saved settings when switching color scheme
|
||||
0.01: Base code
|
||||
0.02: Saved settings when switching color scheme
|
||||
0.03: Added Button 3 opening messages (if app is installed)
|
|
@ -5,6 +5,7 @@ A High-contrast, black-on-white or white-on-black clock displaying huge pixel di
|
|||
## Usage
|
||||
|
||||
* BTN 1 switches between the two modes : black-on-white or white-on-black
|
||||
* BTN 3 opens the messages (if installed, and there are new messages)
|
||||
* That's it!
|
||||
|
||||
## Issues and Requests
|
||||
|
|
|
@ -129,6 +129,7 @@ function updateTime()
|
|||
g.setFontAlign(0, -1, 0);
|
||||
g.drawString(fmtDate(d,mo,y,hour), 120, 120);
|
||||
}
|
||||
drawMessages();
|
||||
}
|
||||
|
||||
function drawDigits(x, value)
|
||||
|
@ -222,6 +223,55 @@ function flipColors()
|
|||
setColorScheme(0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// MESSAGE HANDLING()
|
||||
//
|
||||
|
||||
let messages_installed = require("Storage").read("messages.app.js") != undefined;
|
||||
|
||||
function handleMessages()
|
||||
{
|
||||
if(messages_installed && hasMessages() > 0)
|
||||
{
|
||||
E.showMessage("Loading Messages...");
|
||||
load("messages.app.js");
|
||||
}
|
||||
}
|
||||
|
||||
function hasMessages()
|
||||
{
|
||||
if(!messages_installed)
|
||||
return false;
|
||||
|
||||
var messages = require("Storage").readJSON("messages.json",1)||[];
|
||||
if (messages.some(m=>m.new))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
let msg = atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==");
|
||||
let had_messages = false;
|
||||
|
||||
function drawMessages()
|
||||
{
|
||||
if(!had_messages && hasMessages()) {
|
||||
g.setColor(255,255,255);
|
||||
g.drawImage(msg, 184, 212);
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, -1, 0);
|
||||
g.drawString(">", 224, 216);
|
||||
had_messages = true;
|
||||
}
|
||||
else if (had_messages && !hasMessages())
|
||||
{
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(180, 210, 240, 240);
|
||||
had_messages = false;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// MAIN FUNCTION()
|
||||
|
@ -238,6 +288,7 @@ setInterval(updateTime, interval);
|
|||
// Handle Button Press
|
||||
setWatch(flipColors, BTN1, true);
|
||||
setWatch(Bangle.showLauncher, BTN2, false);
|
||||
setWatch(handleMessages, BTN3, true);
|
||||
|
||||
// Handle redraw on LCD on / fullscreen notifications dismissed
|
||||
Bangle.on('lcdPower', (on) => { if(on) redraw(); });
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
0.02: Remove messages on disconnect
|
||||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Added common bundleId's
|
||||
0.05: Added more bundleId's (app-id's which can be used to
|
||||
determine a friendly app name in the notifications)
|
||||
0.05: Added more bundleId's (app-id's which can be used to determine a friendly app name in the notifications)
|
||||
0.06: Fix (not) popupping up old messages
|
||||
0.07: Added more details from music (instead of Undefined)
|
||||
Added more app identifiers
|
||||
|
||||
0.07: Added more details from music (instead of Undefined), added more app identifiers
|
||||
0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements
|
||||
|
|
|
@ -66,6 +66,7 @@ E.on('notify',msg=>{
|
|||
"com.apple.mobilecal": "Calendar",
|
||||
"com.apple.mobilemail": "Mail",
|
||||
"com.apple.mobilephone": "Phone",
|
||||
"com.apple.mobileslideshow": "Pictures",
|
||||
"com.apple.MobileSMS": "SMS Message",
|
||||
"com.apple.Passbook": "iOS Wallet",
|
||||
"com.apple.podcasts": "Podcasts",
|
||||
|
@ -83,6 +84,7 @@ E.on('notify',msg=>{
|
|||
"com.ifttt.ifttt" : "IFTTT",
|
||||
"com.jumbo.app" : "Jumbo",
|
||||
"com.linkedin.LinkedIn" : "LinkedIn",
|
||||
"com.marktplaats.iphone": "Marktplaats",
|
||||
"com.microsoft.Office.Outlook" : "Outlook Mail",
|
||||
"com.nestlabs.jasper.release" : "Nest",
|
||||
"com.netflix.Netflix" : "Netflix",
|
||||
|
@ -90,6 +92,7 @@ E.on('notify',msg=>{
|
|||
"com.skype.skype": "Skype",
|
||||
"com.skype.SkypeForiPad": "Skype",
|
||||
"com.spotify.client": "Spotify",
|
||||
"com.storytel.iphone": "Storytel",
|
||||
"com.strava.stravaride": "Strava",
|
||||
"com.tinyspeck.chatlyio": "Slack",
|
||||
"com.toyopagroup.picaboo": "Snapchat",
|
||||
|
@ -98,6 +101,8 @@ E.on('notify',msg=>{
|
|||
"com.vilcsak.bitcoin2": "Coinbase",
|
||||
"com.wordfeud.free": "WordFeud",
|
||||
"com.zhiliaoapp.musically": "TikTok",
|
||||
"io.robbie.HomeAssistant": "Home Assistant",
|
||||
"net.weks.prowl": "Prowl",
|
||||
"net.whatsapp.WhatsApp": "WhatsApp",
|
||||
"nl.ah.Appie": "Albert Heijn",
|
||||
"nl.postnl.TrackNTrace": "PostNL",
|
||||
|
@ -118,7 +123,7 @@ E.on('notify',msg=>{
|
|||
new : msg.new,
|
||||
title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer),
|
||||
subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer),
|
||||
body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer)
|
||||
body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) || "Cannot display"
|
||||
});
|
||||
// TODO: posaction/negaction?
|
||||
});
|
||||
|
|
|
@ -4,4 +4,6 @@
|
|||
0.04: Inluded LCARS Logo.
|
||||
0.05: Additional icons for (1) charging and (2) bat < 30%.
|
||||
0.06: Fix - Alarm disabled, if clock was closed.
|
||||
0.07: Added settings to adjust data that is shown for each row.
|
||||
0.07: Added settings to adjust data that is shown for each row.
|
||||
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
||||
0.09: Tab anywhere to open the launcher.
|
|
@ -1,18 +1,35 @@
|
|||
# LCARS clock
|
||||
|
||||
A simple LCARS inspired clock.
|
||||
Note: To display the steps, its necessary to install
|
||||
the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget).
|
||||
Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown.
|
||||
To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps)
|
||||
|
||||
## Features
|
||||
* LCARS Style watch face
|
||||
* Shows satate (charging, out of battery etc.)
|
||||
* SHows data that can be configured (steps, HRM, temperature etc.)
|
||||
* Swipe left/right to activate an alarm
|
||||
* LCARS Style watch face.
|
||||
* Full screen mode - widgets are still loaded.
|
||||
* Supports multiple screens with different data.
|
||||
* Tab anywhere to open the launcher.
|
||||
* [Screen 1] Date + Time + Lock status.
|
||||
* [Screen 1] Shows randomly images of real planets.
|
||||
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
|
||||
* [Screen 1] Swipe up/down to activate an alarm.
|
||||
* [Screen 1] Shows 3 customizable datapoints on the first screen.
|
||||
* [Screen 1] The lower orange line indicates the battery level.
|
||||
* [Screen 2] Display graphs for steps + hrm on the second screen.
|
||||
* [Screen 2] Switch between day/month via swipe up/down.
|
||||
|
||||
|
||||
## Multiple screens support
|
||||
Access different screens via swipe left/ right
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Icons
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a>, <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
||||
|
||||
|
||||
## Creator
|
||||
Made by [David Peer](https://github.com/peerdavid)
|
||||
## Contributors
|
||||
- Creator: [David Peer](https://github.com/peerdavid).
|
||||
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||
|
|
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 795 B |
After Width: | Height: | Size: 791 B |
Before Width: | Height: | Size: 8.5 KiB |
|
@ -15,85 +15,97 @@ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
|||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
/*
|
||||
* Colors to use
|
||||
*/
|
||||
let cBlue = "#0094FF";
|
||||
let cOrange = "#FF9900";
|
||||
let cPurple = "#FF00DC";
|
||||
let cWhite = "#FFFFFF";
|
||||
let cBlack = "#000000";
|
||||
let cGrey = "#9E9E9E";
|
||||
|
||||
/*
|
||||
* Global lcars variables
|
||||
*/
|
||||
let lcarsViewPos = 0;
|
||||
let drag;
|
||||
let hrmValue = 0;
|
||||
var connected = NRF.getSecurityStatus().connected;
|
||||
var plotWeek = false;
|
||||
|
||||
/*
|
||||
* Requirements and globals
|
||||
*/
|
||||
const locale = require('locale');
|
||||
|
||||
var backgroundImage = {
|
||||
width : 176, height : 151, bpp : 3,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("AAdx48cATsAg4daIAX3799ATv2wEFDrUAgNHQDyDghaAeQcJKG86D4gRKGgAA4jxKFuBB5iaDF6BB5ZwyD6QAYCC4CD/Qf6Dzg/gQf8H/iD/n//wCD9gP///wQfpBKQf6D4h5BB/yD8jl/IIIABjiD5n4/DAAWAQe8B//8QYfHj//PAaDzHwICCAAP4gYCBQep6DIIYFBRgKD1j/+gB9BQYYKBn/gQen/+BBFQAUH/iDzGoZBHJoOAQeRBDj5BHj6PB0WKlACDJQIAofYZBFBAZBBAGMHPQZB8QYZAEIIcDIOiDI/hB3QZBBFjlx44CDuBBpg4DCIJEfIIPnz15AQeAQeH8gIDBGoJBCnnz54CDZ1UHPQMHIIUAIIKD3II6MBQYQCCQeI1B+BBC/BKCBASGCQeK5B/xBC4BKEn/gAoKDyj//45BFj/xZYSDzgF/IAP+JQrLCQecAgKDBF4cHQYKJDQecAn6EBAAiJEQeZBB/jICAAMcvwMDQevgQwR0CIIiDzgP/BA1/4CD3nAHGhyD3ABqD0ABiD/Qf4ADjiD/gEnQYuQQf6D7gaDFzxB5gFzQYnz4BB5hyDFATfkEoIdagEBQYoCcgEHDrReBhKDhwEBQbYABjiD/AH4A/AH4AGiFx48cATsAg4daIIWSpMkATuQEbkAgJfbQckJQDyDhZxQA1gRKFpBA4gEQQYtwIPMSQYtAIPKADQfqADAQRA5Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4A/AH4A/AH4A/AFkcuPHAQdAIPOSpMkAQaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4A/AH4A/AH4A/AGUcuPHAQdwIPOSpMkAQaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AciSDFoCD/QfcCQYtIIPMAQYoC6gEJQYgC6gEBQf7HCQf4ABiiD9"))
|
||||
}
|
||||
var bgLeft = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))
|
||||
};
|
||||
|
||||
var bgRight = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA="))
|
||||
};
|
||||
|
||||
var iconEarth = {
|
||||
text: "EARTH",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconSaturn = {
|
||||
text: "SATURN",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA"))
|
||||
}
|
||||
};
|
||||
|
||||
var iconMoon = {
|
||||
text: "MOON",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AH4AQjlx44CCCZsg8eOkHDwAQKEYgmPhEgEQM48AOIgMHEYoCB4ATI8UAmH/x04JoRuJsImHuBKLn37EwZuIgEQOI8cEpXj/yYBhE8+YNGgkYoJxITBUPnAaC///nC+FjBuIOJZEB8YeCh/8AoYACoMEEAnEjhQDPQJKJ/DCDAoi5DoLdHAoMQgLjFWYPOnngh02IwXzwDjEgPGEYS8BI4MBYoSVG4fP/nghkAgZrDkngJQqSG4gvBg4sBQgkImHihEAWwP8ZBMBEYl5/+cSoVAGQIUFh04weJn///0gj/OEw5KEz45BzhuCTYQAEgePB4IACAoJuBnAQEa4XHjxKB//xFgWHJQsCRgMDEonipwjENwUBDQNx8+evvn/hTDLw3igE+EgZxB8UOXIvEJQUfEYOfv53DEQkgga5BJQvzx84cAj+CDoNh8/eEYJKDuCSEcocnEon+/7xEgFBIIcfB4Mf/IICXI2DgDdBAAn758gCIq5Dv4zBvJuIOIfjEgvP/ARHgwdCB4P3AoTdFAAk4EYk8SQgAFTALaDSQwAGh08//vnDmBABYmEEZYAzA=="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconMars = {
|
||||
text: "MARS",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA=="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconSatellite = {
|
||||
text: "GPS ON",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("pMkyQC/ATGXhIRPyNl0gmPjlwCJ9ly1aCJ1c+fHJR1Hy1ZJR1I+fPnlx6QRLpe+/JKBr5KMuYjBJQMdCJce/fvJQW0CJUlEYQCBSpvvJQbXJjl0NwnzNxGQwEOnHhgF78+WqQyIrFx48cAQXz4ShJgAABh0+8cP//9LJEhg4jDuP3//0LhGQgYlBgeAn///5cIy8MuAmDCIP/9I4HkmCEYMOgHfCQWkCI0cuBuDgF/CIP+CI1Ny1IkeAgHANwIAB/QRFrj7BhkxEwQRC/4RFpbXDgSVBg4RCSorXDI4MJAQMfCIP8cwImDn37fwN58+kwHgLgSVFub7CI4NyBAJKDLgkuEYX78+evKtCLg0jEYRKC58JMoRcFkwjDJQTFDl65EkojEAQMdcwn/+gFC3YjEJQLXEpYRDWwQmEdI6SHAQO0CJUkx4jDF4gCIJQgRMXIjCEARIjCCJ2XEYPKCJqJBJQIROcAUpCJ0kybaDARtdCKAC2kAA="))
|
||||
}
|
||||
|
||||
var iconAlarm = {
|
||||
text: "TIMER",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpICEp//BAwCJn/+CJ8k//5CKAABCJs8uPH//x48EI5YjCAARNKEYUcv//jgFBExEnEYoAC+QmHIgIgC/gpCuPBCI2fIgU4AQXjA4P8CIuTEYZKBAolwHApXBEAWP//jxwpBAALaFDoYCIiQmDDIP4EAT+CEwnJEwYjLAQLaFEYomDKALmDNwoCIOIZuD8AkFgCYDHAQjMAQTdDNwOAEg0Dx0/cYeREZtxQYOTHgJuHOIvkXJy8DNwIACJQ8Ah4NDAAfxEZARHOIIkHg4jQAQb1CQ4KVJgEOnDIBSoIjNAQPBcAaVJcAKVBcDGOcD7OBMQM48BuH8f//JKCnhKNggRBkmfTQJxBEwhuD/gRCyVHJRlyCIVJXgYmB8ZQBAoIKBXIQmCOIt/NxAUCOIImCIgIpCBAJuDAQZEE/huIAQWTDgImBTYQGC8gRFcYpKFCI8kDwQAFCJBfBEAX/+IjBiQRIEw4jJAQc8v//NYwCIOgJrIJpA1OcwbaFAQWQA="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconCharging = {
|
||||
text: "CHARGE",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 5,
|
||||
buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A"))
|
||||
}
|
||||
};
|
||||
|
||||
var iconNoBattery = {
|
||||
text: "NO BAT",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA"))
|
||||
}
|
||||
};
|
||||
|
||||
// Font to use:
|
||||
// <link href="https://fonts.googleapis.com/css2?family=Antonio:wght@400;700&display=swap" rel="stylesheet">
|
||||
Graphics.prototype.setFontAntonioSmall = function(scale) {
|
||||
// Actual height 18 (17 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16));
|
||||
}
|
||||
Graphics.prototype.setFontAntonioMedium = function(scale) {
|
||||
// Actual height 20 (19 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAA//mP/5gAAAAAAAAAAAAA/gAMAAAAAA/gAPAAAEIIBP+H/8D+IYBP+H/8D+IABCAAwIAfnwP8+PHh448eP3+B4fAAAAAAAH/AD/4AwGAMBgD/4Af8GAAPgAPgAfgAfAAfAA+AAOP/AH/4BgGAYBgH/4A/8AAAAAAAAAQAA/B+f4/+GMPhjv/4/h8Dg/gAcYwAAPwADgAAAAAAAAB//8///sAAaAACAAAMAAb//+f//AAAAAAAbAAGwAA4AA/wADgABsAAbAAAAAAAgAAMAAPwAD8AAMAADAAAAAAAAAAHAAB/AAOAAAAAAAAMAADAAAwAAMAACAAAAAAAAAABgAAYAAAAAAAAA4AD+AP+A/4A/gAOAAAAAAAAAH//j//8wADMAAz//8f/+AAAAAAAMAADAABgAA//+P//gAAAAAAAAAAAAAfgfP4fzAfswfDP/gx/gMAAAHgPj4D8wMDMHAz//8f3+AAEAAAAADwAH8APzA/AwP//j//4AAwAAAD/Hw/x+MwBjOAYz/+Mf/AAAAAAAH//j//8wYDMGAz9/8fP+AAcDAAAwAAMAfjB/4z/wP+AD4AAwAAAAOB/f4///MHAzBwM///H9/gAAAAAAH/Pj/78wGDMBgz//8f/+AAAAAAADhwA4cAAAAAAAAAAAAAADh/A4fgAAAAOAAHwABsAA7gAccAGDAAAAANgADYAA2AANgADYAA2AAAAAAAABgwAccADuAAbAAHwAA4AAAAHwAD8c4/POMHAD/wAfwAAAAAAAAD/wD//B4B4Y/HMf8zMBMyATMwczP+M4BzHwcgf+AA+AAAAAAD4A/+P/8D+DA/4wH/+AB/4AAeAAAAAAA//+P//jBgYwYGP//j//4PH4AAAAAAAf/+P//zgAcwADP4fz+P4Ph8AAAAAAA//+P//jAAYwAGPADj//4P/4AAAAAAA//+P//jBgYwYGMGBgAAAAAAP//j//4wYAMGADBgAAAAAAAA//w///PAHzAQM4MHP7/x+/8AAAAAAD//4//+AGAABgAAYAP//j//4AAAAAAAAAA//+P//gAAAAAAAAAAAHwAB+AABgAAY//+P//AAAAAAAAAAD//4//+APgAf+Afj8PgPjAAYAAAAAAD//4//+AABgAAYAAGAAAAAAA//+P//j/gAD/wAB/gAP4B/4P/AD//4//+AAAAAAAAAAP//j//4P4AAfwAA/g//+P//gAAAAAAAAAA//g//+PAHjAAY4AOP//h//wAAAAAAD//4//+MDADAwA4cAP/AB/gAAAAAAAA//g//+PAHjAAc4APv//5//yAAAAAAD//4//+MGADBgA48AP//h+f4AAAAAAB+Pw/z+MOBjBwY/P+Hx/AAHgwAAMAAD//4//+MAADAAAAAAP//D//4AAOAABgAA4//+P//AAAAwAAP8AD//AA/+AAfgP/4//gPwAAAAA+AAP/4Af/4AD+A//j/wA/wAD/+AA/4B/+P/+D+AAAAAMADj8P4P/4A/4B//w+A+MABgAAA4AAPwAB/gAB/+A//j/gA+AAMAAAAAYwB+MH/jf+Y/8GPwBjAAAAAAP//7//+wABsAAYAAAAAAPAAD/gAH/gAD/gAD4AACAAADAAGwABv//7//+AAAA=="), 32, atob("BQUHCAgVCQQFBQkHBQcFBwgICAgICAgICAgFBQcHBwgPCQkJCQcHCQoFCQkHDQoJCQkJCAYJCQ0ICAcGBwY="), 20+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontAntonioLarge = function(scale) {
|
||||
// Actual height 34 (34 - 1)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAADwAAAAAeAAAAADwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAD+AAAAH/wAAAP/+AAAf/+AAA//8AAB//4AAD//wAAD//gAAAf/AAAAD+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAB////gA/////AP////8D/////wfAAAA+DwAAADweAAAAeDwAAADwf////+D/////wP////8Af///+AAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAOAAAAADwAAAAAeAAAAAHgAAAAB/////wf////+D/////wf////+D/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAPwH/4AP+B//AH/wf/4D/+D4AB/9weAAf4ODwAP8BweAP/AOD///gBwP//wAOA//4ABwB/4AAOAAAAAAAAAAAAAAAAAAAAB8AA/gA/gAH/AP8AA/8D/gAH/wfAHAA+DwA4ADweAHgAeDwB8ADwf7/+H+D/////gP/9//8A//H/+AA/AH/AAAAAAAAAAAAAAAAAABwAAAAD+AAAAD/wAAAH/+AAAH/5wAAH/wOAAP/gBwAP/gAOAD/////wf////+D/////wf////+AAAABwAAAAAOAAAAABwAAAAAAAAAAAAAAAAAAeAD//4D/Af//Af8D//4D/wf//Af+DwPAADweB4AAeDwPAADweB///+DwP///weA///8DwD//+AAAA/8AAAAAAAAAAAAAAAAAAAAAA////AA/////AP////8D/////wfgPAB+DwB4ADweAOAAeDwBwADwf+PAA+D/x///wP+H//8A/wf//AAAA//gAAAAAAAAAAAAADgAAAAAeAAAAADwAAAAAeAAAD+DwAAP/weAA//+DwA///weB///8Dx//8AAf//wAAD//gAAAf/AAAAD/AAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAD/wf/wB//v//AP////8D/////weAPwAeDwA8ADwcAHAAeDwB8ADwf////+D/////wP/9//8A//H//AA/AD/AAAAAAAAAAAAAAAAAAAAAD//gfAA///D/AP//8f8D///j/weAA8A+DwADgDweAAcAeDwAHgDwf////+B/////gP////8Af///+AAP//4AAAAAAAAAAAAAAAAAAAAAAD4AfAAAfAD4AAD4AfAAAfAD4AAD4AfAAAAAAAAAAAAAA=="), 46, atob("Cg4QEBAQEBAQEBAQCQ=="), 39+(scale<<8)+(1<<16));
|
||||
}
|
||||
// Actual height 39 (39 - 1)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAPgAAAAAB8AAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAD8AAAAAH/gAAAAP/8AAAAf//gAAA///AAAB//+AAAD//8AAAH//4AAAP//wAAAB//gAAAAP/AAAAAB+AAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///AAAf////8AP/////4B//////Af/////8D8AAAAfgeAAAAA8DwAAAAHgeAAAAA8D//////gf/////8B//////AP/////wAf////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAHgAAAAAA8AAAAAAPgAAAAAB4AAAAAAf/////gP/////8B//////gP/////8B//////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAD/+AAP8A//wAP/gP/+AH/8D//wD//gfgAA//8DwAAf+HgeAAP/A8DwAH/gHgfgP/wA8D///4AHgP//+AA8A///AAHgB//AAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AA/gAD/AAH/gA/4AA/+AP/AAH/4D/4AA//gfgA4AB8DwAPAAHgeAB4AA8DwAPgAHgfAD+AB8D//////gP/////4B//5//+AD/+H//gAH/AH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAP/AAAAAP/4AAAAP//AAAAP/x4AAAf/wPAAAf/gB4AAf/AAPAAP/AAB4AB//////gP/////8B//////gP/////8AAAAAPAAAAAAB4AAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//wD/AB///Af+AP//4D/4B///Af/gP//4B/8B4D4AAPgPAeAAA8B4DwAAHgPAfAAB8B4D////gPAf///4B4B////APAD///gAAAD//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAP////4AH/////wB//////Af/////8D8APAA/geADwAB8DwAeAAHgeADwAA8D4AeAAPgf/j+AH8B/8f///gP/h///4Af8H//+AAPgP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAPAAAAAAB4AAAABgPAAAA/8B4AAB//gPAAD//8B4AH///gPAH///8B4P//+AAPH//wAAB///gAAAP//AAAAB/+AAAAAP+AAAAAB+AAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4A/+AAf/w//+AP//v//4B//////Af/////8D4AfwAPgeAB8AA8DwAHAAHgeAB8AA8D4Af4APgf/////8B//////AP//v//4A//4//8AA/4A/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAD//+D/gB///4f+AP///j/4D///8f/gfAAHgB8DwAA8AHgeAAHgA8DwAA8AHgfgAHgB8D//////gP/////4A/////+AD/////gAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAfgAAB+AD8AAAPwAfgAAB+AD8AAAPwAfgAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DBATExMTExMTExMTCw=="), 45+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Draw watch face
|
||||
|
@ -108,117 +120,303 @@ function queueDraw() {
|
|||
}
|
||||
|
||||
|
||||
function printData(key, y){
|
||||
function printData(key, y, c){
|
||||
g.setFontAlign(-1,-1,0);
|
||||
var text = "ERR";
|
||||
var value = "NOT FOUND";
|
||||
|
||||
if(key == "Battery"){
|
||||
var bat = E.getBattery();
|
||||
g.drawString("BAT:", 30, y);
|
||||
g.drawString(bat+ "%", 68, y);
|
||||
text = "BAT";
|
||||
value = E.getBattery() + "%";
|
||||
|
||||
} else if(key == "Steps"){
|
||||
var steps = getSteps();
|
||||
g.drawString("STEP:", 30, y);
|
||||
g.drawString(steps, 68, y);
|
||||
text = "STEP";
|
||||
value = getSteps();
|
||||
|
||||
} else if(key == "Temp."){
|
||||
var temperature = Math.floor(E.getTemperature());
|
||||
g.drawString("TEMP:", 30, y);
|
||||
g.drawString(temperature + "C", 69, y);
|
||||
text = "TEMP";
|
||||
value = Math.floor(E.getTemperature()) + "C";
|
||||
|
||||
} else if(key == "HRM"){
|
||||
g.drawString("HRM:", 30, y);
|
||||
g.drawString(hrmValue, 69, y);
|
||||
text = "HRM";
|
||||
value = hrmValue;
|
||||
|
||||
} else if (key == "VREF"){
|
||||
text = "VREF";
|
||||
value = E.getAnalogVRef().toFixed(2) + "V";
|
||||
|
||||
}
|
||||
|
||||
g.setColor(c);
|
||||
g.fillRect(79, y-2, 87 ,y+18);
|
||||
|
||||
g.setFontAlign(1,-1,0);
|
||||
g.drawString(value, 131, y);
|
||||
|
||||
g.setColor(c);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.fillRect(133, y-2, 165 ,y+18);
|
||||
g.fillCircle(161, y+8, 10);
|
||||
g.setColor(cBlack);
|
||||
g.drawString(text, 135, y);
|
||||
}
|
||||
|
||||
function drawHorizontalBgLine(color, x1, x2, y, h){
|
||||
g.setColor(color);
|
||||
|
||||
for(var i=0; i<h; i++){
|
||||
g.drawLine(x1, y+i, x2,y+i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawLock(){
|
||||
if(lcarsViewPos != 0){
|
||||
return;
|
||||
}
|
||||
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cOrange);
|
||||
g.clearRect(120, 10, g.getWidth(), 75);
|
||||
g.drawString("LCARS", 128, 13);
|
||||
if(connected){
|
||||
g.drawString("CONN", 128, 33);
|
||||
} else {
|
||||
g.drawString("NOT FOUND", 30, y);
|
||||
g.drawString("NOCON", 128, 33);
|
||||
}
|
||||
if(Bangle.isLocked()){
|
||||
g.setColor(cPurple);
|
||||
g.drawString("LOCK", 128, 53);
|
||||
}
|
||||
}
|
||||
|
||||
function drawState(){
|
||||
if(lcarsViewPos != 0){
|
||||
return;
|
||||
}
|
||||
|
||||
g.clearRect(20, 93, 75, 170);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setFontAntonioMedium();
|
||||
|
||||
if(!isAlarmEnabled()){
|
||||
var bat = E.getBattery();
|
||||
var current = new Date();
|
||||
var hours = current.getHours();
|
||||
var iconImg =
|
||||
Bangle.isCharging() ? iconCharging :
|
||||
bat < 30 ? iconNoBattery :
|
||||
Bangle.isGPSOn() ? iconSatellite :
|
||||
hours % 4 == 0 ? iconSaturn :
|
||||
hours % 4 == 1 ? iconMars :
|
||||
hours % 4 == 2 ? iconMoon :
|
||||
iconEarth;
|
||||
g.drawImage(iconImg, 24, 118);
|
||||
g.setColor(cWhite);
|
||||
g.drawString("STATUS", 24+25, 108);
|
||||
} else {
|
||||
// Alarm within symbol
|
||||
g.setColor(cOrange);
|
||||
g.drawString("ALARM", 24+25, 108);
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(getAlarmMinutes(), 24+25, 108+35);
|
||||
}
|
||||
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
}
|
||||
|
||||
|
||||
function drawPosition0(){
|
||||
// Draw background image
|
||||
g.drawImage(bgLeft, 0, 0);
|
||||
drawHorizontalBgLine(cBlue, 25, 120, 0, 4);
|
||||
drawHorizontalBgLine(cBlue, 130, 176, 0, 4);
|
||||
drawHorizontalBgLine(cPurple, 20, 70, 80, 4);
|
||||
drawHorizontalBgLine(cPurple, 80, 176, 80, 4);
|
||||
drawHorizontalBgLine(cOrange, 35, 110, 87, 4);
|
||||
drawHorizontalBgLine(cOrange, 120, 176, 87, 4);
|
||||
|
||||
// The last line is a battery indicator too
|
||||
var bat = E.getBattery() / 100.0;
|
||||
var batX2 = parseInt((172 - 35) * bat + 35);
|
||||
drawHorizontalBgLine(cOrange, 35, batX2, 171, 5);
|
||||
drawHorizontalBgLine(cGrey, batX2+10, 172, 171, 5);
|
||||
|
||||
// Draw logo
|
||||
drawLock();
|
||||
|
||||
// Write time
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.setColor(cWhite);
|
||||
var currentDate = new Date();
|
||||
var timeStr = locale.time(currentDate,1);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(timeStr, 29, 10);
|
||||
|
||||
// Write date
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioMedium();
|
||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||
dayStr += " " + currentDate.getDate();
|
||||
dayStr += " " + currentDate.getFullYear();
|
||||
g.drawString(dayStr, 32, 56);
|
||||
|
||||
// Draw data
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.setColor(cWhite);
|
||||
printData(settings.dataRow1, 97, cOrange);
|
||||
printData(settings.dataRow2, 122, cPurple);
|
||||
printData(settings.dataRow3, 147, cBlue);
|
||||
|
||||
// Draw state
|
||||
drawState();
|
||||
}
|
||||
|
||||
function drawPosition1(){
|
||||
// Draw background image
|
||||
g.drawImage(bgRight, 149, 0);
|
||||
drawHorizontalBgLine(cBlue, 0, 140, 0, 4);
|
||||
drawHorizontalBgLine(cPurple, 0, 80, 80, 4);
|
||||
drawHorizontalBgLine(cPurple, 90, 150, 80, 4);
|
||||
drawHorizontalBgLine(cOrange, 0, 50, 87, 4);
|
||||
drawHorizontalBgLine(cOrange, 60, 140, 87, 4);
|
||||
drawHorizontalBgLine(cOrange, 0, 150, 171, 5);
|
||||
|
||||
// Draw steps bars
|
||||
g.setColor(cWhite);
|
||||
let health;
|
||||
|
||||
try {
|
||||
health = require("health");
|
||||
} catch(ex) {
|
||||
g.setFontAntonioMedium();
|
||||
g.drawString("MODULE HEALTH", 20, 110);
|
||||
g.drawString("REQUIRED.", 20, 130);
|
||||
g.drawString("MODULE HEALTH", 20, 20);
|
||||
g.drawString("REQUIRED.", 20, 40);
|
||||
return;
|
||||
}
|
||||
|
||||
// Plot HRM graph
|
||||
if(plotWeek){
|
||||
var data = new Uint16Array(32);
|
||||
var cnt = new Uint8Array(32);
|
||||
health.readDailySummaries(new Date(), h=>{
|
||||
data[h.day]+=h.bpm;
|
||||
if (h.bpm) cnt[h.day]++;
|
||||
});
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 5,
|
||||
gridy : 100,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 25
|
||||
});
|
||||
|
||||
// Plot step graph
|
||||
var data = new Uint16Array(32);
|
||||
health.readDailySummaries(new Date(), h=>data[h.day]+=h.steps/1000);
|
||||
var gridY = parseInt(Math.max.apply(Math, data)/2);
|
||||
gridY = gridY <= 0 ? 1 : gridY;
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 5,
|
||||
gridy : gridY,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 115
|
||||
});
|
||||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("WEEK HRM", 154, 27);
|
||||
g.drawString("WEEK STEPS [K]", 154, 115);
|
||||
|
||||
// Plot day
|
||||
} else {
|
||||
var data = new Uint16Array(24);
|
||||
var cnt = new Uint8Array(24);
|
||||
health.readDay(new Date(), h=>{
|
||||
data[h.hr]+=h.bpm;
|
||||
if (h.bpm) cnt[h.hr]++;
|
||||
});
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 4,
|
||||
gridy : 100,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 25
|
||||
});
|
||||
|
||||
// Plot step graph
|
||||
var data = new Uint16Array(24);
|
||||
health.readDay(new Date(), h=>data[h.hr]+=h.steps);
|
||||
var gridY = parseInt(Math.max.apply(Math, data)/1000)*1000;
|
||||
gridY = gridY <= 0 ? 1000 : gridY;
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 4,
|
||||
gridy : gridY,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 115
|
||||
});
|
||||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("DAY HRM", 154, 27);
|
||||
g.drawString("DAY STEPS", 154, 115);
|
||||
}
|
||||
}
|
||||
|
||||
function draw(){
|
||||
|
||||
// First handle alarm to show this correctly afterwards
|
||||
handleAlarm();
|
||||
|
||||
// Next draw the watch face
|
||||
g.reset();
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
// Draw background image
|
||||
g.drawImage(backgroundImage, 0, 24);
|
||||
|
||||
// Draw symbol
|
||||
var bat = E.getBattery();
|
||||
var timeInMinutes = getCurrentTimeInMinutes();
|
||||
|
||||
var iconImg =
|
||||
isAlarmEnabled() ? iconAlarm :
|
||||
Bangle.isCharging() ? iconCharging :
|
||||
bat < 30 ? iconNoBattery :
|
||||
Bangle.isGPSOn() ? iconSatellite :
|
||||
timeInMinutes % 4 == 0 ? iconSaturn :
|
||||
timeInMinutes % 4 == 1 ? iconMars :
|
||||
timeInMinutes % 4 == 2 ? iconMoon :
|
||||
iconEarth;
|
||||
g.drawImage(iconImg, 115, 115);
|
||||
|
||||
// Alarm within symbol
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setFontAntonioSmall();
|
||||
g.drawString(iconImg.text, 115+25, 105);
|
||||
if(isAlarmEnabled() > 0){
|
||||
g.drawString(getAlarmMinutes(), 115+25, 115+25);
|
||||
// Draw current lcars position
|
||||
if(lcarsViewPos == 0){
|
||||
drawPosition0();
|
||||
} else if (lcarsViewPos == 1) {
|
||||
drawPosition1();
|
||||
}
|
||||
|
||||
// Write time
|
||||
var currentDate = new Date();
|
||||
var timeStr = locale.time(currentDate,1);
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(timeStr, 60, 55);
|
||||
|
||||
// Write date
|
||||
g.setFontAlign(-1,-1, 0);
|
||||
g.setFontAntonioSmall();
|
||||
|
||||
var dayName = locale.dow(currentDate, true).toUpperCase();
|
||||
var day = currentDate.getDate();
|
||||
g.drawString(day, 100, 35);
|
||||
g.drawString(dayName, 100, 55);
|
||||
|
||||
// Draw battery
|
||||
printData(settings.dataRow1, 98);
|
||||
printData(settings.dataRow2, 121);
|
||||
printData(settings.dataRow3, 144);
|
||||
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Step counter via widget
|
||||
*/
|
||||
function getSteps() {
|
||||
if (stepsWidget() !== undefined)
|
||||
return stepsWidget().getSteps();
|
||||
return "???";
|
||||
}
|
||||
|
||||
function stepsWidget() {
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom;
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom;
|
||||
var steps = 0;
|
||||
try {
|
||||
health = require("health");
|
||||
} catch(ex) {
|
||||
return steps;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
health.readDay(new Date(), h=>steps+=h.steps);
|
||||
return steps;
|
||||
}
|
||||
|
||||
/*
|
||||
* HRM Listener
|
||||
*/
|
||||
Bangle.on('HRM', function (hrm) {
|
||||
hrmValue = hrm.bpm;
|
||||
});
|
||||
|
||||
/*
|
||||
* Handle alarm
|
||||
|
@ -228,7 +426,7 @@ function getCurrentTimeInMinutes(){
|
|||
}
|
||||
|
||||
function isAlarmEnabled(){
|
||||
return settings.alarm > 0;
|
||||
return settings.alarm >= 0;
|
||||
}
|
||||
|
||||
function getAlarmMinutes(){
|
||||
|
@ -253,65 +451,134 @@ function handleAlarm(){
|
|||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1));
|
||||
|
||||
// Update alarm state to disabled
|
||||
settings.alarm = -1;
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
|
||||
.then(() => {
|
||||
// Update alarm state to disabled
|
||||
settings.alarm = -1;
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Swipe to set an alarm
|
||||
*/
|
||||
Bangle.on('swipe',function(dir) {
|
||||
// Increase alarm
|
||||
if(dir == -1){
|
||||
if(isAlarmEnabled()){
|
||||
settings.alarm += 5;
|
||||
} else {
|
||||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrease alarm
|
||||
if(dir == +1){
|
||||
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
||||
settings.alarm -= 5;
|
||||
} else {
|
||||
settings.alarm = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
draw();
|
||||
|
||||
// Update alarm state
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Stop updates when LCD is off, restart when on
|
||||
* Listeners
|
||||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
// Whenever we connect to Gadgetbridge, reading data from
|
||||
// health failed. Therefore, we update and read data from
|
||||
// health iff the connection state did not change.
|
||||
if(connected == NRF.getSecurityStatus().connected) {
|
||||
draw();
|
||||
} else {
|
||||
connected = NRF.getSecurityStatus().connected
|
||||
drawLock();
|
||||
}
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
connected = NRF.getSecurityStatus().connected
|
||||
});
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
drawLock();
|
||||
});
|
||||
|
||||
Bangle.on('charging',function(charging) {
|
||||
drawState();
|
||||
});
|
||||
|
||||
Bangle.on('HRM', function (hrm) {
|
||||
hrmValue = hrm.bpm;
|
||||
});
|
||||
|
||||
|
||||
function increaseAlarm(){
|
||||
if(isAlarmEnabled()){
|
||||
settings.alarm += 5;
|
||||
} else {
|
||||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||
}
|
||||
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
|
||||
function decreaseAlarm(){
|
||||
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
||||
settings.alarm -= 5;
|
||||
} else {
|
||||
settings.alarm = -1;
|
||||
}
|
||||
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
|
||||
// Thanks to the app "gbmusic" for this code to detect swipes in all 4 directions.
|
||||
Bangle.on("drag", e => {
|
||||
if (!drag) { // start dragging
|
||||
drag = {x: e.x, y: e.y};
|
||||
} else if (!e.b) { // released
|
||||
const dx = e.x-drag.x, dy = e.y-drag.y;
|
||||
drag = null;
|
||||
|
||||
// Horizontal swipe
|
||||
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||
if(dx > 0){
|
||||
lcarsViewPos = 0;
|
||||
} else {
|
||||
lcarsViewPos = 1;
|
||||
}
|
||||
|
||||
// Vertical swipe
|
||||
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||
if(lcarsViewPos == 0){
|
||||
if(dy > 0){
|
||||
decreaseAlarm();
|
||||
} else {
|
||||
increaseAlarm();
|
||||
}
|
||||
|
||||
// Only update the state and return to
|
||||
// avoid a full draw as this is much faster.
|
||||
drawState();
|
||||
return;
|
||||
}
|
||||
|
||||
if(lcarsViewPos == 1){
|
||||
plotWeek = dy < 0 ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on("touch", e => {
|
||||
Bangle.showLauncher();
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Lets start widgets, listen for btn etc.
|
||||
*/
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets - needed by draw
|
||||
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.
|
||||
*/
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
draw();
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
Bangle.drawWidgets();
|
||||
// Bangle.drawWidgets();
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
|
||||
require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -18,14 +18,14 @@
|
|||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
var data_options = ['Battery', 'Steps', 'Temp.', "HRM"];
|
||||
var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF"];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'LCARS Clock' },
|
||||
'< Back': back,
|
||||
'Row 1': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow1),
|
||||
min: 0, max: 3,
|
||||
min: 0, max: 4,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow1 = data_options[v];
|
||||
|
@ -34,7 +34,7 @@
|
|||
},
|
||||
'Row 2': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow2),
|
||||
min: 0, max: 3,
|
||||
min: 0, max: 4,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow2 = data_options[v];
|
||||
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
'Row 3': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow3),
|
||||
min: 0, max: 3,
|
||||
min: 0, max: 4,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow3 = data_options[v];
|
||||
|
|