orloj: rewrite from "andark", mark this as 0.10

master
Pavel Machek 2024-11-11 20:27:39 +01:00
parent eec3d89b45
commit ddadf2c682
4 changed files with 251 additions and 395 deletions

View File

@ -1,3 +1,4 @@
0.01: attempt to import 0.01: attempt to import
0.02: Minor code improvements 0.02: Minor code improvements
0.03: Minor code improvements 0.03: Minor code improvements
0.10: Restart, sttart from "andark" adding astronomical features to it

View File

@ -5,21 +5,4 @@ Astronomical clock.
Written by: [Pavel Machek](https://github.com/pavelmachek) Written by: [Pavel Machek](https://github.com/pavelmachek)
The plan is to have an (analog) astronomical clock with a lot of The plan is to have an (analog) astronomical clock with a lot of
information on single dial. information on single dial. Thanks a lot to "Dark Analog Clock".
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.

View File

@ -1,405 +1,277 @@
const SunCalc = require("suncalc"); // from modules folder /* sun version 0.0.3 */
let sun = {
SunCalc: null,
lat: 50,
lon: 14,
rise: 0, /* Unix time of sunrise/sunset */
set: 0,
init: function() {
try {
this.SunCalc = require("suncalc"); // from modules folder
} catch (e) {
print("Require error", e);
}
print("Have suncalc: ", this.SunCalc);
},
sunPos: function() {
let d = new Date();
if (!this.SunCalc) {
let sun = {};
sun.azimuth = 175;
sun.altitude = 15;
return sun;
}
let sun = this.SunCalc.getPosition(d, this.lat, this.lon);
print(sun.azimuth, sun.altitude);
return sun;
},
sunTime: function() {
let d = new Date();
if (!this.SunCalc) {
let sun = {};
sun.sunrise = d;
sun.sunset = d;
return sun;
}
let sun = this.SunCalc.getTimes(d, this.lat, this.lon);
return sun;
},
adj: function (x) {
if (x < 0)
return x + 24*60*60;
return x;
},
toSunrise: function () {
return this.adj(this.rise - getTime());
},
toSunset: function () {
return this.adj(this.set - getTime());
},
update: function () {
let t = this.sunTime();
this.rise = t.sunrise.getTime() / 1000;
this.set = t.sunset.getTime() / 1000;
},
// < 0 : next is sunrise, in abs(ret) seconds
// > 0
getNext: function () {
let rise = this.toSunrise();
let set = this.toSunset();
if (rise < set) {
return -rise;
}
return set;
// set = set / 60;
// return s + (set / 60).toFixed(0) + ":" + (set % 60).toFixed(0);
},
};
// ################################################################################ sun.init();
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2; const defaultSettings = {
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2; loadWidgets : false,
let outerRadius = Math.min(CenterX,CenterY) * 0.9; textAboveHands : false,
shortHrHand : true
};
const white = 0;
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
const lat = 50.1; const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
const lon = 14.45;
const h = g.getHeight(); const zahlpos=(function() {
const w = g.getWidth(); let z=[];
const sm = 15; let sk=1;
var altitude, temperature; 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;}
z.push([sk,xsk,ysk]);
sk+=1;
}
return z;
})();
var img_north = Graphics.createImage(` let unlock = false;
X
XXX
XXX
X XXX
X XXX
X XXXX
X XXXX
X XXXXX
X XXXXX
XXXXXXXXX
`);
var img_sunrise = Graphics.createImage(` function zeiger(len,dia,tim){
XXX const x=c.x+ Math.cos(tim)*len/2,
XXXXX y=c.y + Math.sin(tim)*len/2,
XXXXXXXXX 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;
}
var img_moonrise = Graphics.createImage(` function drawHands(d) {
XXX let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
XX X g.setColor(white,white,white);
XXXXXXXXX
`);
var img_altitude = Graphics.createImage(` if(h>12){
X X h=h-12;
X X X }
XXXXXXXXX //calculates the position of the minute, second and hour hand
X X X h=2*Math.PI/12*(h+m/60)-Math.PI/2;
X X //more accurate
`); //m=2*Math.PI/60*(m+s/60)-Math.PI/2;
m=2*Math.PI/60*(m)-Math.PI/2;
var img_temperature = Graphics.createImage(` s=2*Math.PI/60*s-Math.PI/2;
XX //g.setColor(1,0,0);
XXXXXXXX const hz = zeiger(settings.shortHrHand?88:100,5,h);
X XX g.fillPoly(hz,true);
XXXXXXXX //g.setColor(1,1,1);
XX 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);
}
var img_battery = Graphics.createImage(` function setColor() {
XXXXXXXX g.setBgColor(!white,!white,!white);
XXX X g.setColor(white,white,white);
XXXX XX }
XXXXX X
XXXXXXXX
`);
var img_step = Graphics.createImage(` function drawText(d) {
XXX g.setFont("Vector",20);
XX XXXXX let dateStr = require("locale").date(d);
XXX XXXXX //g.drawString(dateStr, c.x, c.y+20, true);
XXX XXXXX let bat = E.getBattery();
XX XXXX let batStr = Math.round(bat/5)*5+"%";
`); if (Bangle.isCharging()) {
g.setBgColor(1,0,0);
}
if (bat < 30)
g.drawString(batStr, c.x, c.y+40, true);
}
var img_sun = Graphics.createImage(` function drawNumbers(d) {
X X let hour = d.getHours();
XXX if (d.getMinutes() > 30) {
XXXXXXX hour += 1;
XXXXXXXXX }
XXXXXXXXX let day = d.getDate();
XXXXXXXXX if (day > 12) {
XXXXXXX day = day % 10;
XXX if (!day)
X X day = 10;
`); }
//draws the numbers on the screen
var img_moon = Graphics.createImage(` for(let i = 0;i<12;i++){
XXX let on = false;
XX XXX let j = i+1;
X XXXX g.setFont("Vector",20);
X XXX if (j == day) {
X XXX on = true;
X XXX g.setFont("Vector",29);
X XXXX }
X XXX if ((j % 12) == (hour % 12))
XXX on = true;
`); setColor();
if (!on)
let use_compass = 0; g.setColor(white/2, !white, white);
if (1 || on)
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
}
}
function draw(){ function draw(){
drawBorders(); // draw black rectangle in the middle to clear screen from scale and hands
queueDraw(); g.setColor(!white,!white,!white);
} g.fillRect(10,10,2*c.x-10,2*c.x-10);
// prepare for drawing the text
function radA(p) { return p*(Math.PI*2); } g.setFontAlign(0,0);
function radD(d) { return d*(h/2); } // do drawing
const d=new Date();
function radX(p, d) { drawScale(d); // FIXME: it is enough to do once in 12 hours or so
let a = radA(p); drawNumbers(d);
return h/2 + Math.sin(a)*radD(d); if (settings.textAboveHands) {
} drawHands(d); drawText(d);
} else {
function radY(p, d) { drawText(d); drawHands(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 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 ****/ /* 0..12 -> angle suitable for drawScale */
function conv(m) { return -15 + (m / 12) * 60; }
function drawClockHands () { /* datetime -> 0..12 float */
let now = new Date(); function hour12(d) {
let h = d.getHours() + d.getMinutes() / 60;
let Hours = now.getHours() % 12; if (h > 12)
let Minutes = now.getMinutes(); h = h - 12;
let Seconds = now.getSeconds(); return h;
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) { //draws the scale once the app is started
let h = fracHour(time); function drawScale(d){
let x = radX(h/12, 0.7); // clear the screen
let y = radY(h/12, 0.7); g.setBgColor(!white,!white,!white);
g.drawImage(icon, x,y, options); g.clear();
} // Display month as a wider mark
let m = conv(d.getMonth() + 1);
print(m);
function drawOutsideIcon(h, icon, options) { let pos = sun.sunPos().azimuth;
let x = radX(h, 0.95); pos = conv(12*(pos/360));
let y = radY(h, 0.95);
g.drawImage(icon, x,y, options);
}
function drawBorders() { let t = sun.sunTime();
g.reset(); // FIXME
g.setColor(0); let set = conv(hour12(t.sunset));
g.fillRect(Bangle.appRect); let dark = conv(hour12(t.sunset) + 0.25);
print(set, dark, pos);
g.setColor(-1); // draw the ticks of the scale
g.fillCircle(w/2, h/2, h/2 - 2); for(let i=-14;i<47;i++){
if (0) { const win=i*2*Math.PI/60;
g.fillCircle(sm+1, sm+1, sm); let d=2;
g.fillCircle(sm+1, h-sm-1, sm); if(i%5==0){d=5;}
g.fillCircle(w-sm-1, h-sm-1, sm); if(i==m){d=10;}
g.fillCircle(h-sm-1, sm+1, sm); if (i>=pos && i<=(pos+2))
} g.setColor(!white,!white,white/2);
g.setColor(0, 1, 0); else if (i>=set && i<=dark)
g.drawCircle(h/2, w/2, radD(0.7)); g.setColor(white/2,!white,white/2);
g.drawCircle(h/2, w/2, radD(0.5)); else
g.setColor(white,white,white);
outerRadius = radD(0.7); g.fillPoly(zeiger(300,d,win),true);
drawClockHands(); g.setColor(!white,!white,!white);
g.fillRect(10,10,2*c.x-10,2*c.x-10);
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();
const 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);
const 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 });
let 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() { //// main running sequence ////
g.reset();
g.setColor(g.theme.bg);
g.fillRect(Bangle.appRect);
}
Bangle.on('touch', function(button, xy) { // Show launcher when middle button pressed, and widgets that we're clock
var x = xy.x; Bangle.setUI("clock");
var y = xy.y; // Load widgets if needed, and make them show swipeable
if (y > h) y = h; if (settings.loadWidgets) {
if (y < 0) y = 0; Bangle.loadWidgets();
if (x > w) x = w; require("widget_utils").swipeOn();
if (x < 0) x = 0; } else if (global.WIDGETS) require("widget_utils").hide();
}); // Clear the screen once, at startup
drawScale(new Date());
// 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(); draw();
}, next - (Date.now() % next));
} let secondInterval = setInterval(draw, 1000);
// Stop updates when LCD is off, restart when on // Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{ Bangle.on('lcdPower',on=>{
if (secondInterval) clearInterval(secondInterval);
secondInterval = undefined;
if (on) { if (on) {
draw(); // draw immediately, queue redraw secondInterval = setInterval(draw, 1000);
} else { // stop draw timer draw(); // draw immediately
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
} }
}); });
Bangle.on('lock',on=>{
Bangle.setUI("clockupdown", btn=> { unlock = !on;
if (btn<0) use_compass = 0; if (secondInterval) clearInterval(secondInterval);
if (btn>0) use_compass = 1; secondInterval = setInterval(draw, unlock ? 1000 : 60000);
Bangle.setCompassPower(use_compass, 'orloj'); draw(); // draw immediately
draw();
}); });
Bangle.on('charging',on=>{draw();});
if (use_compass)
Bangle.setCompassPower(true, 'orloj');
g.clear();
draw();

View File

@ -1,6 +1,6 @@
{ "id": "orloj", { "id": "orloj",
"name": "Orloj", "name": "Orloj",
"version": "0.03", "version": "0.10",
"description": "Astronomical clock", "description": "Astronomical clock",
"icon": "app.png", "icon": "app.png",
"readme": "README.md", "readme": "README.md",