diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index d34e59db3..64df26c55 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -9,3 +9,4 @@ Show humidity as weather circle data 0.06: Allow settings empty circles Support to choose between humidity and wind speed for weather circle progress + Support to show time and progress until next sunrise or sunset diff --git a/apps/circlesclock/README.md b/apps/circlesclock/README.md index b5a15bab0..4d54bce00 100644 --- a/apps/circlesclock/README.md +++ b/apps/circlesclock/README.md @@ -13,13 +13,13 @@ It can show the following information (this can be configured): * Humidity or wind speed as circle progress * Temperature inside circle * Condition as icon below circle + * Time and progress until next sunrise or sunset ## Screenshots ![Screenshot dark theme](screenshot-dark.png) ![Screenshot light theme](screenshot-light.png) # TODO -* Add sunrise and sunset * Display moon instead of sun during night on weather circle ## Creator diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 82a1e671f..fe373e87b 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,6 +1,7 @@ const locale = require("locale"); const heatshrink = require("heatshrink"); const storage = require("Storage"); +const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA=")); const shoesIconGreen = heatshrink.decompress(atob("h0OwYJGhIEDgVIAgUEyQKDkmACgcggVACIeQAYMSgIRCgmApIbDiQUDAAkBkAFDGoYAD")); @@ -18,6 +19,9 @@ const weatherSnowy = heatshrink.decompress(atob("iEQwYROn/8AocH8AECuAFBh0Agf+CIN const weatherFoggy = heatshrink.decompress(atob("iEQwYROn/8AgUB/EfwAFBh/AgfwgED/wIBuEABwd/4EcDQgFDgE4Fosf///8f//A/Lj/xCQIRNA=")); const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AoX8gE4AoQpBnAdBF4IRBDQMH/kOHgY7DAo4AOA==")); +const sunSetDown = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wLDg1ggfACoo")); +const sunSetUp = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wRFgfAg1gBIY")); + let settings; function loadSettings() { @@ -127,8 +131,11 @@ function drawCircle(index) { case "weather": drawWeather(w); break; + case "sunprogress": + drawSunProgress(w); + break; case "empty": - // we do nothing here + // we draw nothing here return; } } @@ -295,6 +302,49 @@ function drawWeather(w) { } } + +function drawSunProgress(w) { + if (!w) w = getCirclePosition("sunprogress"); + const percent = getSunProgress(); + + drawCircleBackground(w); + + drawGauge(w, h3, percent, colorYellow); + + drawInnerCircleAndTriangle(w); + + let icon = powerIcon; + let color = colorFg; + if (percent < 1) { // it is before sunset + color = colorFg; + icon = sunSetUp; + } else { + color = colorGrey; + icon = sunSetDown; + } + + const times = getSunData(); + if (times != undefined) { + const sunRise = Math.round(times.sunrise.getTime() / 1000); + const sunSet = Math.round(times.sunset.getTime() / 1000); + const now = Math.round(new Date().getTime() / 1000); + let text; + if (now > sunRise && now < sunSet) { + text = formatSeconds(sunSet - now); + } else { + // approx sunrise tomorrow: + const upcomingSunRise = sunRise + 60 * 60 * 24; + text = formatSeconds(upcomingSunRise - now); + } + } + + writeCircleText(w, text); + + g.drawImage(icon, w - 6, h3 + radiusOuter - 6); + +} + + /* * Choose weather icon to display based on weather conditition code * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 @@ -344,6 +394,58 @@ function getWeatherIconByCode(code) { return undefined; } + +function formatSeconds(s) { + if (s > 60 * 60) { // hours + return Math.round(s / (60 * 60)) + "h"; + } + if (s > 60) { // minutes + return Math.round(s / (60)) + "m"; + } + return s + "s"; +} + +/* + * Read location from myLocation app + */ +function getLocation() { + return storage.readJSON("mylocation.json", 1) || { + "lat": 51.5072, + "lon": 8.1276, + "location": "London" + }; +} + +function getSunData() { + const location = getLocation(); + if (location != undefined && location.lat != undefined) { + // get today's sunlight times for lat/lon + return SunCalc.getTimes(new Date(), location.lat, location.lon); + } + return undefined; +} + +/* + * Calculated progress of the sun between sunrise and sunset in percent + * + * Taken from rebble app and modified + */ +function getSunProgress() { + const times = getSunData(); + const sunRise = Math.round(times.sunrise.getTime() / 1000); + const sunSet = Math.round(times.sunset.getTime() / 1000); + const now = Math.round(new Date().getTime() / 1000); + + if (now > sunRise && now < sunSet) { + // during day, progress until sunSet + return (now - sunRise) / (sunSet - sunRise); + } else { + // during night, progress until approx sunrise tomorrow: + const upcomingSunRise = sunRise + 60 * 60 * 24; + return ((upcomingSunRise - now) / (upcomingSunRise - sunSet)); + } +} + /* * Draws the background and the grey circle */ diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index c03f02847..cde4f9283 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -7,8 +7,8 @@ storage.write(SETTINGS_FILE, settings); } - const valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather", "empty"]; - const namesCircleTypes = ["steps", "distance", "heart", "battery", "weather", "empty"]; + const valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "empty"]; + const namesCircleTypes = ["steps", "distance", "heart", "battery", "weather", "sun progress", "empty"]; const weatherData = ["humidity", "wind", "empty"]; @@ -88,19 +88,19 @@ }, 'left': { value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0, - min: 0, max: 5, + min: 0, max: 6, format: v => namesCircleTypes[v], onchange: x => save('circle1', valuesCircleTypes[x]), }, 'middle': { value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2, - min: 0, max: 5, + min: 0, max: 6, format: v => namesCircleTypes[v], onchange: x => save('circle2', valuesCircleTypes[x]), }, 'right': { value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3, - min: 0, max: 5, + min: 0, max: 6, format: v => namesCircleTypes[v], onchange: x => save('circle3', valuesCircleTypes[x]), }