diff --git a/apps/orloj/ChangeLog b/apps/orloj/ChangeLog new file mode 100644 index 000000000..263d4078d --- /dev/null +++ b/apps/orloj/ChangeLog @@ -0,0 +1 @@ +0.01: attempt to import diff --git a/apps/orloj/README.md b/apps/orloj/README.md new file mode 100644 index 000000000..4da3f6a98 --- /dev/null +++ b/apps/orloj/README.md @@ -0,0 +1,25 @@ +# Orloj ![](app.png) + +Astronomical clock. + +Written by: [Pavel Machek](https://github.com/pavelmachek) + +The plan is to have an (analog) astronomical clock with a lot of +information on single dial. + +It continuously displays information that can be obtained "cheaply", +that is current time, sunset/sunrise times, battery status and +altitude. One-second updates with useful compass can be activated by +tapping bottom right corner. + +Display is split in three rings. Outside ring is for time-based data +with base of one week, and for non time-based data. Black dot +indicates day of week. Green foot indicates number of steps taken, red +battery symbol indicates remaining charge, black thermometer symbol +represents temperature, and black ruler symbol indicates +altitude. Number in bottom left corner is day of month. + +In the middle ring, hour-based data are displayed. Black dot indicates +current hour, yellow symbols indicate sunset and sunrise, and black +symbols indicate moonset and moonrise. + diff --git a/apps/orloj/app-icon.js b/apps/orloj/app-icon.js new file mode 100644 index 000000000..4663c8266 --- /dev/null +++ b/apps/orloj/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkB/4A+gAXWh4YWh4BCC6vwBxoIJGBYXXHhAUCJBYXXHhAXaVBgkHC7JGMEpDvQC48ACxoXHCx5WPC8BvPC8BgwgAAFC65nQ+AvWAFSjYaiyERUQ7QYGKS4EOyguUC7h4VFoIXUIgbBWAH4A/AH4APA==")) diff --git a/apps/orloj/app.js b/apps/orloj/app.js new file mode 100644 index 000000000..8dd1cd571 --- /dev/null +++ b/apps/orloj/app.js @@ -0,0 +1,407 @@ +const SunCalc = require("suncalc"); // from modules folder + +// ################################################################################ + +let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2; +let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2; +let outerRadius = Math.min(CenterX,CenterY) * 0.9; + +const lat = 50.1; +const lon = 14.45; + +const h = g.getHeight(); +const w = g.getWidth(); +const sm = 15; +let settings, location, mode = 0; +var altitude, temperature; + +var img_north = Graphics.createImage(` + X + XXX + XXX + X XXX + X XXX + X XXXX + X XXXX +X XXXXX +X XXXXX +XXXXXXXXX +`); + +var img_sunrise = Graphics.createImage(` + XXX + XXXXX +XXXXXXXXX +`); + +var img_moonrise = Graphics.createImage(` + XXX + XX X +XXXXXXXXX +`); + +var img_altitude = Graphics.createImage(` +X X +X X X +XXXXXXXXX +X X X +X X +`); + +var img_temperature = Graphics.createImage(` + XX +XXXXXXXX +X XX +XXXXXXXX + XX +`); + +var img_battery = Graphics.createImage(` +XXXXXXXX +XXX X +XXXX XX +XXXXX X +XXXXXXXX +`); + +var img_step = Graphics.createImage(` + XXX + XX XXXXX +XXX XXXXX +XXX XXXXX + XX XXXX +`); + +var img_sun = Graphics.createImage(` +X X + XXX + XXXXXXX +XXXXXXXXX +XXXXXXXXX +XXXXXXXXX + XXXXXXX + XXX +X X +`); + +var img_moon = Graphics.createImage(` + XXX + XX XXX +X XXXX +X XXX +X XXX +X XXX +X XXXX + X XXX + XXX +`); + +let use_compass = 0; + +function draw() { + drawBorders(); + queueDraw(); +} + +function radA(p) { return p*(Math.PI*2); } +function radD(d) { return d*(h/2); } + +function radX(p, d) { + let a = radA(p); + return h/2 + Math.sin(a)*radD(d); +} + +function radY(p, d) { + let a = radA(p); + return w/2 - Math.cos(a)*radD(d); +} + +function fracHour(d) { + let hour = d.getHours(); + let min = d.getMinutes(); + hour = hour + min/60; + if (hour > 12) + hour -= 12; + return hour; +} + + 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 () { + g.setColor(g.theme.fg); + g.setFont('Vector', 22); + + g.setFontAlign(0,-1); + g.drawString('12', CenterX,CenterY-outerRadius); + + g.setFontAlign(1,0); + g.drawString('3', CenterX+outerRadius,CenterY); + + g.setFontAlign(0,1); + g.drawString('6', CenterX,CenterY+outerRadius); + + g.setFontAlign(-1,0); + g.drawString('9', CenterX-outerRadius,CenterY); + } + +/**** 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 + ); + + g.setFont('Vector', 22); + + g.setFontAlign(-1, 1); + g.drawString(now.getDate(), CenterX-outerRadius,CenterY+outerRadius); + + } + +function drawTimeIcon(time, icon, options) { + let h = fracHour(time); + let x = radX(h/12, 0.7); + let y = radY(h/12, 0.7); + g.drawImage(icon, x,y, options); +} + +function drawOutsideIcon(h, icon, options) { + let x = radX(h, 0.95); + let y = radY(h, 0.95); + g.drawImage(icon, x,y, options); +} + +function drawBorders() { + g.reset(); + g.setColor(0); + g.fillRect(Bangle.appRect); + + g.setColor(-1); + g.fillCircle(w/2, h/2, h/2 - 2); + if (0) { + g.fillCircle(sm+1, sm+1, sm); + g.fillCircle(sm+1, h-sm-1, sm); + g.fillCircle(w-sm-1, h-sm-1, sm); + g.fillCircle(h-sm-1, sm+1, sm); + } + g.setColor(0, 1, 0); + g.drawCircle(h/2, w/2, radD(0.7)); + g.drawCircle(h/2, w/2, radD(0.5)); + + outerRadius = radD(0.7); + drawClockHands(); + + let d = new Date(); + let hour = fracHour(d); + let min = d.getMinutes(); + let day = d.getDay(); + day = day + hour/24; + { + let x = radX(hour/12, 0.7); + let y = radY(hour/12, 0.7); + g.setColor(0, 0, 0); + g.fillCircle(x,y, 5); + } + { + let x = radX(min/60, 0.5); + let y = radY(min/60, 0.5); + g.setColor(0, 0, 0); + g.drawLine(h/2, w/2, x, y); + } + { + let x = radX(hour/12, 0.3); + let y = radY(hour/12, 0.3); + g.setColor(0, 0, 0); + g.drawLine(h/2, w/2, x, y); + } + { + let km = 0.001 * 0.719 * Bangle.getHealthStatus("day").steps; + let x = radX(km/12 + 0, 0.95); + let y = radY(km/12 + 0, 0.95); + g.setColor(0, 0.7, 0); + g.drawImage(img_step, x,y, { scale: 2, rotate: Math.PI*0.0 } ); + } + { + let bat = E.getBattery(); + let x = radX(bat/100, 0.95); + let y = radY(bat/100, 0.95); + g.setColor(0.7, 0, 0); + g.drawImage(img_battery, x,y, { scale: 2, rotate: Math.PI*0.0 } ); + } + { + d = new Date(); + sun = SunCalc.getTimes(d, lat, lon); + g.setColor(0.5, 0.5, 0); + print("sun", sun); + drawTimeIcon(sun.sunset, img_sunrise, { rotate: Math.PI, scale: 2 }); + drawTimeIcon(sun.sunrise, img_sunrise, { scale: 2 }); + g.setColor(0, 0, 0); + moon = SunCalc.getMoonTimes(d, lat, lon); + print("moon", moon); + drawTimeIcon(moon.set, img_moonrise, { rotate: Math.PI, scale: 2 }); + drawTimeIcon(moon.rise, img_sunrise, { scale: 2 }); + pos = SunCalc.getPosition(d, lat, lon); + print("sun:", pos); + if (pos.altitude > -0.1) { + g.setColor(0.5, 0.5, 0); + az = pos.azimuth; + drawOutsideIcon(az / (2*Math.PI), img_sun, { scale: 2 }); + } + pos = SunCalc.getMoonPosition(d, lat, lon); + print("moon:", pos); + if (pos.altitude > -0.05) { + g.setColor(0, 0, 0); + az = pos.azimuth; + drawOutsideIcon(az / (2*Math.PI), img_moon, { scale: 2 }); + } + } + { + Bangle.getPressure().then((x) => + { altitude = x.altitude; temperature = x.temperature; }, + print); + print(altitude, temperature); + drawOutsideIcon(altitude / 120, img_altitude, { scale: 2 }); + drawOutsideIcon(temperature / 12, img_temperature, { scale: 2 }); + } + if (use_compass) { + let obj = Bangle.getCompass(); + if (obj) { + let h = 360-obj.heading; + let x = radX(h/360, 0.7); + let y = radY(h/360, 0.7); + g.setColor(0, 0, 1); + g.drawImage(img_north, x,y, {scale:2}); + } + } + { + let x = radX(day/7, 0.95); + let y = radY(day/7, 0.95); + g.setColor(0, 0, 0); + g.fillCircle(x,y, 5); + } +} + +function drawEmpty() { + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); +} + +Bangle.on('touch', function(button, xy) { + var x = xy.x; + var y = xy.y; + if (y > h) y = h; + if (y < 0) y = 0; + if (x > w) x = w; + if (x < 0) x = 0; +}); + +// if we get a step then we are not idle +Bangle.on('step', s => { +}); + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + next = 60000; + if (use_compass) next = 250; + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, next - (Date.now() % next)); +} + +// 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=> { + if (btn<0) use_compass = 0; + if (btn>0) use_compass = 1; + Bangle.setCompassPower(use_compass, 'orloj'); + draw(); +}); + +if (use_compass) + Bangle.setCompassPower(true, 'orloj'); +g.clear(); +draw(); + diff --git a/apps/orloj/app.png b/apps/orloj/app.png new file mode 100644 index 000000000..fced2ce5e Binary files /dev/null and b/apps/orloj/app.png differ diff --git a/apps/orloj/metadata.json b/apps/orloj/metadata.json new file mode 100644 index 000000000..00c091e35 --- /dev/null +++ b/apps/orloj/metadata.json @@ -0,0 +1,13 @@ +{ "id": "orloj", + "name": "Orloj", + "version":"0.01", + "description": "Astronomical clock", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "tags": "", + "storage": [ + {"name":"orloj.app.js","url":"app.js"}, + {"name":"orloj.img","url":"app-icon.js","evaluate":true} + ] +}