1
0
Fork 0

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(`
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 drawHands(d) {
function radD(d) { return d*(h/2); } let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
g.setColor(white,white,white);
function radX(p, d) { if(h>12){
let a = radA(p); h=h-12;
return h/2 + Math.sin(a)*radD(d); }
//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.setColor(1,0,0);
const hz = zeiger(settings.shortHrHand?88: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);
} }
function radY(p, d) { function setColor() {
let a = radA(p); g.setBgColor(!white,!white,!white);
return w/2 - Math.cos(a)*radD(d); g.setColor(white,white,white);
} }
function fracHour(d) { function drawText(d) {
g.setFont("Vector",20);
let dateStr = require("locale").date(d);
//g.drawString(dateStr, c.x, c.y+20, true);
let bat = E.getBattery();
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);
}
function drawNumbers(d) {
let hour = d.getHours(); let hour = d.getHours();
let min = d.getMinutes(); if (d.getMinutes() > 30) {
hour = hour + min/60; hour += 1;
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);
} }
let day = d.getDate();
/**** transforme polygon ****/ if (day > 12) {
day = day % 10;
let transformedPolygon = new Array(HourHandPolygon.length); if (!day)
day = 10;
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;
}
} }
//draws the numbers on the screen
/**** draw clock hands ****/ for(let i = 0;i<12;i++){
let on = false;
function drawClockHands () { let j = i+1;
let now = new Date(); g.setFont("Vector",20);
if (j == day) {
let Hours = now.getHours() % 12; on = true;
let Minutes = now.getMinutes(); g.setFont("Vector",29);
let Seconds = now.getSeconds(); }
if ((j % 12) == (hour % 12))
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi; on = true;
let MinutesAngle = (Minutes/60) * twoPi - Pi; setColor();
let SecondsAngle = (Seconds/60) * twoPi - Pi; if (!on)
g.setColor(white/2, !white, white);
g.setColor(g.theme.fg); if (1 || on)
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
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();
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() { function draw(){
g.reset(); // draw black rectangle in the middle to clear screen from scale and hands
g.setColor(g.theme.bg); g.setColor(!white,!white,!white);
g.fillRect(Bangle.appRect); g.fillRect(10,10,2*c.x-10,2*c.x-10);
// prepare for drawing the text
g.setFontAlign(0,0);
// do drawing
const d=new Date();
drawScale(d); // FIXME: it is enough to do once in 12 hours or so
drawNumbers(d);
if (settings.textAboveHands) {
drawHands(d); drawText(d);
} else {
drawText(d); drawHands(d);
}
} }
Bangle.on('touch', function(button, xy) { /* 0..12 -> angle suitable for drawScale */
var x = xy.x; function conv(m) { return -15 + (m / 12) * 60; }
var y = xy.y; /* datetime -> 0..12 float */
if (y > h) y = h; function hour12(d) {
if (y < 0) y = 0; let h = d.getHours() + d.getMinutes() / 60;
if (x > w) x = w; if (h > 12)
if (x < 0) x = 0; h = h - 12;
}); return h;
// 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));
} }
//draws the scale once the app is started
function drawScale(d){
// clear the screen
g.setBgColor(!white,!white,!white);
g.clear();
// Display month as a wider mark
let m = conv(d.getMonth() + 1);
print(m);
let pos = sun.sunPos().azimuth;
pos = conv(12*(pos/360));
let t = sun.sunTime();
// FIXME
let set = conv(hour12(t.sunset));
let dark = conv(hour12(t.sunset) + 0.25);
print(set, dark, pos);
// draw the ticks of the scale
for(let i=-14;i<47;i++){
const win=i*2*Math.PI/60;
let d=2;
if(i%5==0){d=5;}
if(i==m){d=10;}
if (i>=pos && i<=(pos+2))
g.setColor(!white,!white,white/2);
else if (i>=set && i<=dark)
g.setColor(white/2,!white,white/2);
else
g.setColor(white,white,white);
g.fillPoly(zeiger(300,d,win),true);
g.setColor(!white,!white,!white);
g.fillRect(10,10,2*c.x-10,2*c.x-10);
}
}
//// main running sequence ////
// Show launcher when middle button pressed, and widgets that we're clock
Bangle.setUI("clock");
// Load widgets if needed, and make them show swipeable
if (settings.loadWidgets) {
Bangle.loadWidgets();
require("widget_utils").swipeOn();
} else if (global.WIDGETS) require("widget_utils").hide();
// Clear the screen once, at startup
drawScale(new Date());
draw();
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",