Merge branch 'master' into master
409
apps.json
|
@ -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.05",
|
||||
"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.15",
|
||||
"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",
|
||||
|
@ -1148,7 +1148,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",
|
||||
|
@ -1501,7 +1501,7 @@
|
|||
{
|
||||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
|
@ -1749,8 +1749,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}
|
||||
|
@ -2027,7 +2028,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",
|
||||
|
@ -2184,7 +2185,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"}],
|
||||
|
@ -2429,7 +2430,7 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Simple calendar",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
|
@ -2970,11 +2971,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"}],
|
||||
|
@ -3532,7 +3533,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",
|
||||
|
@ -4064,7 +4065,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",
|
||||
|
@ -4210,13 +4211,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": [
|
||||
|
@ -4382,7 +4383,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"}],
|
||||
|
@ -4487,7 +4488,7 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
|
@ -4621,7 +4622,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",
|
||||
|
@ -4652,7 +4653,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": [
|
||||
|
@ -4697,6 +4698,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}
|
||||
|
@ -4708,7 +4711,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",
|
||||
|
@ -4725,7 +4728,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",
|
||||
|
@ -4733,7 +4736,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"},
|
||||
|
@ -4767,7 +4770,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",
|
||||
|
@ -4778,7 +4781,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"}],
|
||||
|
@ -4838,6 +4841,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",
|
||||
|
@ -4861,7 +4883,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"}],
|
||||
|
@ -4875,11 +4897,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",
|
||||
|
@ -4931,10 +4967,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": [
|
||||
|
@ -4963,8 +5001,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",
|
||||
|
@ -4972,10 +5010,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",
|
||||
|
@ -5029,9 +5071,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",
|
||||
|
@ -5047,6 +5090,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",
|
||||
|
@ -5080,5 +5139,273 @@
|
|||
{"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.01",
|
||||
"description": "Replace Bangle.js 1's built in menu function.",
|
||||
"icon": "icon.png",
|
||||
"type": "boot",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS"],
|
||||
"screenshots": [{"url":"pro-menu-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"promenu.boot.js","url":"boot.js"},
|
||||
{"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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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 +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) {
|
||||
|
@ -68,13 +64,26 @@ function draw() {
|
|||
|
||||
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;
|
||||
|
@ -24,6 +24,8 @@ window.onload = function() {
|
|||
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] = []; }
|
||||
|
@ -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}
|
||||
]
|
||||
}
|
|
@ -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,4 @@
|
|||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
var log = function() {};//print
|
||||
var settings = {};
|
||||
var device;
|
||||
var gatt;
|
||||
var service;
|
||||
var characteristic;
|
||||
|
||||
//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
|
||||
});
|
||||
});
|
||||
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 |
|
@ -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
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
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: Added number of satellites in view and fixed crash with GPS time
|
||||
|
|
|
@ -16,13 +16,19 @@ var lastFix = {
|
|||
time: 0,
|
||||
satellites: 0
|
||||
};
|
||||
var nofix = 0;
|
||||
var SATinView = 0;
|
||||
var nofBD = 0;
|
||||
var nofGP = 0;
|
||||
|
||||
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 +83,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 +93,6 @@ function onGPS(fix) {
|
|||
}
|
||||
lastFix = fix;
|
||||
if (fix.fix) {
|
||||
nofix = 0;
|
||||
var locale = require("locale");
|
||||
var satellites = fix.satellites;
|
||||
var maidenhead = getMaidenHead(fix.lat,fix.lon);
|
||||
|
@ -100,12 +105,21 @@ function onGPS(fix) {
|
|||
layout.maidenhead.label = "Maidenhead: "+maidenhead;
|
||||
} else {
|
||||
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(3,6) == "GSV") {
|
||||
// console.log(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);
|
||||
|
|
|
@ -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?
|
||||
});
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
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.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
|
@ -1,18 +1,33 @@
|
|||
# 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.
|
||||
* [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 month graphs for steps + hrm on the second screen.
|
||||
|
||||
|
||||
## 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,96 @@ 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";
|
||||
|
||||
/*
|
||||
* 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 +119,299 @@ 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(133, y-2, 165 ,y+18);
|
||||
g.fillCircle(161, y+8, 10);
|
||||
g.setColor(cBlack);
|
||||
g.drawString(text, 135, y);
|
||||
|
||||
g.setColor(c);
|
||||
g.setFontAlign(1,-1,0);
|
||||
g.drawString(value, 130, 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, 77, 170);
|
||||
g.setColor(cWhite);
|
||||
var bat = E.getBattery();
|
||||
var current = new Date();
|
||||
var hours = current.getHours();
|
||||
|
||||
if(!isAlarmEnabled()){
|
||||
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, 29, 104);
|
||||
} else {
|
||||
// Alarm within symbol
|
||||
g.setFontAntonioMedium();
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor(cOrange);
|
||||
g.drawString("ALARM", 29+25, 107);
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(getAlarmMinutes(), 29+25, 107+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(cPurple, 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, 28, 10);
|
||||
|
||||
// Write date
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioMedium();
|
||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||
dayStr += " " + currentDate.getDate();
|
||||
dayStr += " " + currentDate.getFullYear();
|
||||
g.drawString(dayStr, 29, 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 +421,7 @@ function getCurrentTimeInMinutes(){
|
|||
}
|
||||
|
||||
function isAlarmEnabled(){
|
||||
return settings.alarm > 0;
|
||||
return settings.alarm >= 0;
|
||||
}
|
||||
|
||||
function getAlarmMinutes(){
|
||||
|
@ -253,65 +446,130 @@ 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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* 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();
|
||||
|
|
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];
|
||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
|
@ -457,12 +457,12 @@ var locales = {
|
|||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00
|
||||
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020
|
||||
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
|
||||
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
|
||||
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
|
||||
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
|
||||
abday: "dom,lun,mar,mer,gio,ven,sab",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì,sabato",
|
||||
trans: { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }
|
||||
},
|
||||
"it_IT": {
|
||||
|
@ -475,12 +475,12 @@ var locales = {
|
|||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00
|
||||
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020
|
||||
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
|
||||
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
|
||||
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
|
||||
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
|
||||
abday: "dom,lun,mar,mer,gio,ven,sab",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì,sabato",
|
||||
trans: { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }
|
||||
},
|
||||
"wae_CH": {
|
||||
|
@ -589,7 +589,7 @@ var locales = {
|
|||
month: "leden,únor,březen,duben,květen,červen,červenec,srpen,září,říjen,listopad,prosinec",
|
||||
abday: "ne,po,út,st,čt,pá,so",
|
||||
day: "neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota",
|
||||
trans: { yes: "tak", Yes: "Tak", no: "nie", No: "Nie", ok: "ok", on: "na", off: "poza" }
|
||||
trans: { yes: "ano", Yes: "Ano", no: "ne", No: "Ne", ok: "ok", on: "zap", off: "vyp" }
|
||||
},
|
||||
"sl_SI": {
|
||||
lang: "sl_SI",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Created app
|
||||
0.02: Use Bangle.setUI for button/launcher handling
|
||||
0.03: Allow widgets to detect this is a clock
|
||||
|
|
|
@ -304,15 +304,14 @@ Bangle.on('lcdPower',function(on) {
|
|||
});
|
||||
|
||||
g.clear();
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// Update time once a second
|
||||
timeInterval = setInterval(showTime, 1000);
|
||||
showTime();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Start stopwatch when BTN3 is pressed
|
||||
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"});
|
||||
B3 = 1; // BTN3 is bound to start the stopwatch
|
||||
|
|
|
@ -21,3 +21,6 @@
|
|||
Add 'Delete All' option to message options
|
||||
Now update correctly when 'require("messages").clearAll()' is called
|
||||
0.14: Hide widget when all unread notifications are dismissed from phone
|
||||
0.15: Don't buzz when Quiet Mode is active
|
||||
0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147)
|
||||
0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font
|
||||
|
|
|
@ -52,7 +52,7 @@ var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
|
|||
if (!Array.isArray(MESSAGES)) MESSAGES=[];
|
||||
var onMessagesModified = function(msg) {
|
||||
// TODO: if new, show this new one
|
||||
if (msg && msg.new) {
|
||||
if (msg && msg.new && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) {
|
||||
if (WIDGETS["messages"]) WIDGETS["messages"].buzz();
|
||||
else Bangle.buzz();
|
||||
}
|
||||
|
@ -243,7 +243,8 @@ function showMessage(msgid) {
|
|||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1});
|
||||
}});
|
||||
}
|
||||
lines = g.wrapString(msg.body, g.getWidth()-10);
|
||||
var bodyFont = fontMedium;
|
||||
lines = g.setFont(bodyFont).wrapString(msg.body, g.getWidth()-10);
|
||||
var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n");
|
||||
layout = new Layout({ type:"v", c: [
|
||||
{type:"h", fillx:1, bgCol:colBg, c: [
|
||||
|
@ -256,7 +257,7 @@ function showMessage(msgid) {
|
|||
title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
|
||||
]},
|
||||
]},
|
||||
{type:"txt", font:fontMedium, label:body, fillx:1, filly:1, pad:2 },
|
||||
{type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2 },
|
||||
{type:"h",fillx:1, c: buttons}
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
|
|
|
@ -43,7 +43,8 @@ exports.pushMessage = function(event) {
|
|||
// otherwise load messages/show widget
|
||||
var loadMessages = Bangle.CLOCK || event.important;
|
||||
// first, buzz
|
||||
if (loadMessages && global.WIDGETS && WIDGETS.messages)
|
||||
var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
|
||||
if (!quiet && loadMessages && global.WIDGETS && WIDGETS.messages)
|
||||
WIDGETS.messages.buzz();
|
||||
// after a delay load the app, to ensure we have all the messages
|
||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||
|
@ -51,7 +52,7 @@ exports.pushMessage = function(event) {
|
|||
exports.messageTimeout = undefined;
|
||||
// if we're in a clock or it's important, go straight to messages app
|
||||
if (loadMessages) return load("messages.app.js");
|
||||
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
if (!quiet && (!global.WIDGETS || !WIDGETS.messages)) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
WIDGETS.messages.show();
|
||||
}, 500);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
||||
WIDGETS["messages"]={area:"tl", width:0, iconwidth:23,
|
||||
draw:function() {
|
||||
Bangle.removeListener('touch', this.touch);
|
||||
if (!this.width) return;
|
||||
var c = (Date.now()-this.t)/1000;
|
||||
g.reset().clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
||||
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth);
|
||||
g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y);
|
||||
//if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
|
||||
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
||||
|
@ -17,7 +18,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
|||
WIDGETS["messages"].t=Date.now(); // first time
|
||||
WIDGETS["messages"].l=Date.now()-10000; // last buzz
|
||||
if (quiet) WIDGETS["messages"].t -= 500000; // if quiet, set last time in the past so there is no buzzing
|
||||
WIDGETS["messages"].width=64;
|
||||
WIDGETS["messages"].width=this.iconwidth;
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLCDPower(1);// turns screen on
|
||||
},hide:function() {
|
||||
|
@ -26,6 +27,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
|||
WIDGETS["messages"].width=0;
|
||||
Bangle.drawWidgets();
|
||||
},buzz:function() {
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode
|
||||
let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
|
||||
function b() {
|
||||
var c = v[0];
|
||||
|
@ -36,7 +38,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
|||
b();
|
||||
},touch:function(b,c) {
|
||||
var w=WIDGETS["messages"];
|
||||
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+23) return;
|
||||
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+w.iconwidth) return;
|
||||
load("messages.app.js");
|
||||
}};
|
||||
/* We might have returned here if we were in the Messages app for a
|
||||
|
|
|
@ -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,15 @@
|
|||
# Minimal Analog Clock #
|
||||
|
||||
This app displays the perhaps most basic analog clock one can think of - just
|
||||
some clock hands and no clock 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("mEwgJC/AEBhCjgCBgeAgF8AoXggHwCIXwgfADAX8h4TBAAM+jwkDj/4AocPDwIACgdgBYgoCAAMEuB+/AH4="))
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,230 @@
|
|||
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 HourHandLength = outerRadius * 0.5;
|
||||
let HourHandWidth = 2*5, halfHourHandWidth = HourHandWidth/2;
|
||||
|
||||
let MinuteHandLength = outerRadius * 0.7;
|
||||
let MinuteHandWidth = 2*3, halfMinuteHandWidth = MinuteHandWidth/2;
|
||||
|
||||
let SecondHandLength = outerRadius * 0.9;
|
||||
let SecondHandOffset = halfHourHandWidth + 10;
|
||||
|
||||
let outerBoltRadius = halfHourHandWidth + 2, innerBoltRadius = outerBoltRadius - 4;
|
||||
let HandOffset = outerBoltRadius + 4;
|
||||
|
||||
let twoPi = 2*Math.PI, deg2rad = Math.PI/180;
|
||||
let Pi = Math.PI;
|
||||
let halfPi = Math.PI/2;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
|
||||
let sine = [0, sin(30*deg2rad), sin(60*deg2rad), 1];
|
||||
|
||||
let HandPolygon = [
|
||||
-sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3],
|
||||
sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0],
|
||||
sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3],
|
||||
-sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0],
|
||||
];
|
||||
|
||||
let HourHandPolygon = new Array(HandPolygon.length);
|
||||
for (let i = 0, l = HandPolygon.length; i < l; i+=2) {
|
||||
HourHandPolygon[i] = halfHourHandWidth*HandPolygon[i];
|
||||
HourHandPolygon[i+1] = halfHourHandWidth*HandPolygon[i+1];
|
||||
if (i < l/2) { HourHandPolygon[i+1] -= HourHandLength; }
|
||||
if (i > l/2) { HourHandPolygon[i+1] += HandOffset; }
|
||||
}
|
||||
let MinuteHandPolygon = new Array(HandPolygon.length);
|
||||
for (let i = 0, l = HandPolygon.length; i < l; i+=2) {
|
||||
MinuteHandPolygon[i] = halfMinuteHandWidth*HandPolygon[i];
|
||||
MinuteHandPolygon[i+1] = halfMinuteHandWidth*HandPolygon[i+1];
|
||||
if (i < l/2) { MinuteHandPolygon[i+1] -= MinuteHandLength; }
|
||||
if (i > l/2) { MinuteHandPolygon[i+1] += HandOffset; }
|
||||
}
|
||||
|
||||
/**** transforme polygon ****/
|
||||
|
||||
let transformedPolygon = new Array(HandPolygon.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
|
||||
);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(CenterX,CenterY, outerBoltRadius);
|
||||
|
||||
g.setColor(g.theme.bg);
|
||||
g.drawCircle(CenterX,CenterY, outerBoltRadius);
|
||||
g.fillCircle(CenterX,CenterY, innerBoltRadius);
|
||||
}
|
||||
|
||||
/**** refreshDisplay ****/
|
||||
|
||||
let Timer;
|
||||
function refreshDisplay () {
|
||||
g.clear(true); // also loads current theme
|
||||
|
||||
Bangle.drawWidgets();
|
||||
|
||||
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');
|
|
@ -1 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Enhanced icon, make it bolder
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///t/7j/P3/vB4cBqtVoAbHBQIABBQ0FBYdQBYsVBYdUERIkGHIQADHoguEGAwuEGAwKFBZg8DHQw8EBYNf/1Vq3/8oLDIwNf/Wpv//0oLG9Wq3/qBYJUCBYuqBaBqBBYW+BepHEBbybCBYP+BYSnErYLDyoLFAANq/r8Ga5T7MBZZUBAAhSCfhA6DBZhIGBQg8FHQg8GHQgwGFwowFBQwwDFwwLMlS7Bqta1AKEn2q1K1C1WgBYf/1WqBYIDB1QKCgYLC0taBYoXB/QICBY0//7vBAAQ8EEgIABCwwME9QVEA"))
|
||||
require("heatshrink").decompress(atob("mEw4UA///gH4AYPO/QPDgNVqtADY/1BYNfBQ0PBQIAB+ALFmoLDrgLF6oLDq4KEgYKDBYPABYcNBYlVuAuIGAwuEAANUBYYKFHgg6Bq4ZCr4DBHgQLBvWq2te1WlBYZGBBYOr1Wq1qSDBYNqBIILDKgQLLgoLHqBqDBfJHLBZBrOgKPCBYiPCU4NaBYe1WYrABBQLCCfgYGCrwVBa4kAirvKNgIAErgLDKgIAEKQQ8EAAY6DBZhIDIww8GHQg8GHQgwGFwowEFwx5EOog8GHQ0AlWpBYNq1AKFWIILBAYOgBYbICytWAgQKCgTgDcwYXGAAgvGAAY8EEgYWGBgoVEA=="))
|
||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -7,3 +7,4 @@
|
|||
0.07: Added info line that cycles on BTN1/BTN3 (or vitual buttons on a bangle 2)
|
||||
0.08: Added dependancy on MyLocation
|
||||
0.09: Added dependancy on Pedometer Widget
|
||||
0.10: Added Weather line, fixed issues on a Bangle 1, update every minute
|
||||
|
|
|
@ -1,45 +1,83 @@
|
|||
# Pastel Clock
|
||||
|
||||
*a configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times*
|
||||
*a configurable clock with custom fonts, background and optional weather icons. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times*
|
||||
|
||||
* Designed specifically for Bangle 1 and Bangle 2
|
||||
* A choice of 7 different custom fonts
|
||||
* Supports the Light and Dark themes
|
||||
* Has a settings menu, change font, enable/disable the grid
|
||||
* Has a settings menu, change font, enable/disable the grid, weather icons
|
||||
* On Bangle 1 use BTN1,BTN3 to cycle through the info display (Date, ID, Batt %, Ram % etc)
|
||||
* On Bangle 2 touch the top right/top left to cycle through the info display (Date, ID, Batt %, Ram % etc)
|
||||
* The information display will cycle on each screen update
|
||||
* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location
|
||||
* Uses pedometer widget to get latest step count
|
||||
* Use the weather widget to get weather status
|
||||
* Dependant apps are installed when Pastel installs
|
||||
* The screen is updated every minute to save battery power
|
||||
* The weather display will display temperature and wind speed on alternate screen refreshes
|
||||
|
||||
I came up with the name Pastel due to the shade of the grid background.
|
||||
|
||||
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/)
|
||||
|
||||
## Lato
|
||||
|
||||
## Weather Support
|
||||
|
||||
|
||||

|
||||
|
||||
Pastel installs the weather app and weather widget. You may want to
|
||||
hide the weather widget display, this can be done through the weather
|
||||
widget settings. You should first get the weather app working. If
|
||||
the weather App is not working, then it is not going to work for
|
||||
Pastel.
|
||||
|
||||
The following weather icons are supported.
|
||||
|
||||

|
||||
|
||||
Mostly cloudy, Sunny, Mostly Sunny, Snow, Rain.
|
||||
|
||||
The triangle icon shows there is a problem connecting to GadgetBridge and the weather service.
|
||||
You should follow the setup and trouble shooting guide for the Weather App.
|
||||
|
||||
If you find the weather / gadgetbridge service unreliable you can
|
||||
disable weather updates to pastel through the settings app.
|
||||
|
||||
|
||||
## Fonts
|
||||
|
||||
### Lato
|
||||

|
||||
|
||||
|
||||
## Architect
|
||||
### Architect
|
||||

|
||||
|
||||
|
||||
## Gochihand
|
||||
### Gochihand
|
||||

|
||||
|
||||
|
||||
## Monoton
|
||||
### Monoton
|
||||

|
||||
|
||||
|
||||
## Elite
|
||||
### Elite
|
||||

|
||||
|
||||
|
||||
## Cabin Sketch
|
||||
### Cabin Sketch
|
||||

|
||||
|
||||
|
||||
## Orbitron
|
||||
### Orbitron
|
||||

|
||||
|
||||
### The Grid
|
||||
|
||||
Setting the grid on provides a graph paper style background to the App.
|
||||
The grid is not supported on a Bangle 1 due to flicker issues.
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
require("f_latosmall").add(Graphics);
|
||||
const storage = require('Storage');
|
||||
const locale = require("locale");
|
||||
const SETTINGS_FILE = "pastel.json";
|
||||
const LOCATION_FILE = "mylocation.json";
|
||||
let settings;
|
||||
let location;
|
||||
|
||||
// cloud, sun, partSun, snow, rain, storm, error
|
||||
// create 1 bit, max contrast, brightness set to 85
|
||||
var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA=="));
|
||||
var sunIcon = require("heatshrink").decompress(atob("kEggILIgOAAZkDAYPAgeBwPAgIFBBgPhw4TBp/yAYMcnADBnEcAYMwhgDBsEGgE/AYP8AYYLDCYgbDEYYrD8fHIwI7CIYZLDL54AHA=="));
|
||||
var sunPartIcon = require("heatshrink").decompress(atob("kEggIHEmADJjEwsEAjkw8EAh0B4EAg35wEAgP+CYMDwv8AYMDBAP2g8HgH+g0DBYMMgPwAYX8gOMEwMG3kAg8OvgSBjg2BgcYGQIcBAY5CBg0Av//HAM///4MYgNBEIMOCoUMDoUAnBwGkEA"));
|
||||
var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAjADCj+AgOAj/gAYMIuEHwEAjEPAYQVChk4AYQhCAYcYBYQTDnEPgEB+EH///IAQACE4IAB8EICIPghwDB4EeBYNAjgDBg8EAYQYCg4bCgZuFA=="));
|
||||
var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA=="));
|
||||
var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A=="));
|
||||
|
||||
|
||||
function loadSettings() {
|
||||
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||
settings.grid = settings.grid||false;
|
||||
|
@ -93,7 +105,49 @@ function prevInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
var mm_prev = "xx";
|
||||
|
||||
/**
|
||||
Choose weather icon to display based on condition.
|
||||
Based on function from the Bangle weather app so it should handle all of the conditions
|
||||
sent from gadget bridge.
|
||||
*/
|
||||
function chooseIcon(condition) {
|
||||
condition = condition.toLowerCase();
|
||||
if (condition.includes("thunderstorm")) return stormIcon;
|
||||
if (condition.includes("freezing")||condition.includes("snow")||
|
||||
condition.includes("sleet")) {
|
||||
return snowIcon;
|
||||
}
|
||||
if (condition.includes("drizzle")||
|
||||
condition.includes("shower")) {
|
||||
return rainIcon;
|
||||
}
|
||||
if (condition.includes("rain")) return rainIcon;
|
||||
if (condition.includes("clear")) return sunIcon;
|
||||
if (condition.includes("few clouds")) return partSunIcon;
|
||||
if (condition.includes("scattered clouds")) return cloudIcon;
|
||||
if (condition.includes("clouds")) return cloudIcon;
|
||||
if (condition.includes("mist") ||
|
||||
condition.includes("smoke") ||
|
||||
condition.includes("haze") ||
|
||||
condition.includes("sand") ||
|
||||
condition.includes("dust") ||
|
||||
condition.includes("fog") ||
|
||||
condition.includes("ash") ||
|
||||
condition.includes("squalls") ||
|
||||
condition.includes("tornado")) {
|
||||
return cloudIcon;
|
||||
}
|
||||
return cloudIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
Get weather stored in json file by weather app.
|
||||
*/
|
||||
function getWeather() {
|
||||
let jsonWeather = storage.readJSON('weather.json');
|
||||
return jsonWeather;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
|
@ -115,19 +169,27 @@ function draw() {
|
|||
var x = (g.getWidth()/2);
|
||||
var y = (g.getHeight()/3);
|
||||
|
||||
g.reset();
|
||||
var weatherJson = getWeather();
|
||||
var w_temp;
|
||||
var w_icon;
|
||||
var w_wind;
|
||||
|
||||
if (process.env.HWVERSION == 1) {
|
||||
// avoid flicker on a bangle 1 by comparing with previous minute
|
||||
if (mm_prev != mm) {
|
||||
mm_prev = mm;
|
||||
g.clearRect(0, 30, w, h - 24);
|
||||
}
|
||||
if (settings.weather && weatherJson && weatherJson.weather) {
|
||||
var currentWeather = weatherJson.weather;
|
||||
const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||
w_temp = temp[1] + " " + temp[2];
|
||||
w_icon = chooseIcon(currentWeather.txt);
|
||||
const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
w_wind = wind[1] + " " + wind[2] + " " + (currentWeather.wrose||'').toUpperCase();
|
||||
} else {
|
||||
// on a b2 safe to just clear anyway as there is no flicker
|
||||
g.clearRect(0, 30, w, h - 24);
|
||||
w_temp = "Err";
|
||||
w_wind = "???";
|
||||
w_icon = errIcon;
|
||||
}
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0, 30, w, h - 24);
|
||||
|
||||
// draw a grid like graph paper
|
||||
if (settings.grid && process.env.HWVERSION !=1) {
|
||||
g.setColor("#0f0");
|
||||
|
@ -139,6 +201,18 @@ function draw() {
|
|||
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
// draw weather line
|
||||
if (settings.weather) {
|
||||
g.drawImage(w_icon, (w/2) - 40, 24);
|
||||
g.setFontLatoSmall();
|
||||
g.setFontAlign(-1,0); // left aligned
|
||||
if (drawCount % 2 == 0)
|
||||
g.drawString(w_temp, (w/2) + 6, 24 + ((y - 24)/2));
|
||||
else
|
||||
g.drawString( (w_wind.split(' ').slice(0, 2).join(' ')), (w/2) + 6, 24 + ((y - 24)/2));
|
||||
// display first 2 words of the wind string eg '4 mph'
|
||||
}
|
||||
|
||||
if (settings.font == "Architect")
|
||||
g.setFontArchitect();
|
||||
else if (settings.font == "GochiHand")
|
||||
|
@ -161,36 +235,39 @@ function draw() {
|
|||
|
||||
// for the colon
|
||||
g.setFontAlign(0,-1); // centre aligned
|
||||
|
||||
if (d.getSeconds()&1) {
|
||||
g.drawString(":", x,y);
|
||||
} else {
|
||||
// on bangle 1, we are not using clearRect(), hide : by printing over it in reverse color
|
||||
if (process.env.HWVERSION == 1) {
|
||||
g.setColor(g.theme.bg);
|
||||
g.drawString(":", x,y);
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
}
|
||||
|
||||
g.drawString(":", x,y);
|
||||
g.setFontLatoSmall();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString((infoData[infoMode].calc()), w/2, h - 24 - 24);
|
||||
|
||||
if (drawCount % 3600 == 0)
|
||||
// recalc sunrise / sunset every hour
|
||||
if (drawCount % 60 == 0)
|
||||
updateSunRiseSunSet(new Date(), location.lat, location.lon);
|
||||
drawCount++;
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Only update when display turns on
|
||||
if (process.env.BOARD!="SMAQ3") // hack for Q3 which is always-on
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (secondInterval)
|
||||
clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on)
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw();
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
nextInfo();
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clockupdown", btn=> {
|
||||
|
@ -204,8 +281,6 @@ loadFonts();
|
|||
loadLocation();
|
||||
|
||||
g.clear();
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
draw();
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// initialize with default settings...
|
||||
let s = {
|
||||
'grid': false,
|
||||
'weather': false,
|
||||
'font': "Lato"
|
||||
}
|
||||
|
||||
|
@ -39,8 +40,16 @@
|
|||
value: s.grid,
|
||||
format: () => (s.grid ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.grid = !s.grid
|
||||
save()
|
||||
s.grid = !s.grid;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Show Weather': {
|
||||
value: s.weather,
|
||||
format: () => (s.weather ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.weather = !s.weather;
|
||||
save();
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.5 KiB |