1
0
Fork 0

solar clock: First Commit

master
adrian w kirk 2021-07-17 21:04:04 +01:00
parent 29f34ad567
commit 03716d5b80
18 changed files with 1115 additions and 0 deletions

View File

@ -265,6 +265,31 @@
{"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
]
},
{ "id": "solarclock",
"name": "Solar Clock",
"icon": "solar_clock.png",
"version":"0.01",
"description": "The solar clock will use your current or chosen location to work out the the current sun phase of the day",
"tags": "clock",
"type":"clock",
"allow_emulator":false,
"readme": "README.md",
"storage": [
{"name":"solarclock.app.js","url":"solar_clock.js"},
{"name":"solarclock.img","url":"solar_clock-icon.js","evaluate":true},
{"name":"solar_colors.js","url":"solar_colors.js"},
{"name":"solar_controller.js","url":"solar_controller.js"},
{"name":"solar_date_utils.js","url":"solar_date_utils.js"},
{"name":"solar_graphic_utils.js","url":"solar_graphic_utils.js"},
{"name":"solar_location.js","url":"solar_location.js"},
{"name":"solar_math_utils.js","url":"solar_math_utils.js"},
{"name":"solar_loc.Iceland.json","url":"solar_loc.Iceland.json"},
{"name":"solar_loc.Kauai.json","url":"solar_loc.Kauai.json"},
{"name":"solar_loc.Tokyo.json","url":"solar_loc.Tokyo.json"},
{"name":"solar_loc.local.json","url":"solar_loc.local.json"}
{"name":"solar_locations.json","url":"solar_locations.json"}
]
},
{ "id": "sweepclock",
"name": "Sweep Clock",
"icon": "sweepclock.png",

View File

@ -0,0 +1 @@
0.01: Initial Release

View File

@ -0,0 +1 @@
# Solar Clock

BIN
apps/solarclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("lEowkA/4AGmYIHABHzmVCCaE0kUin4TPmUimQTQ+UzmcvJ6EjCaP/kYABCaEymYTl+Q7SMgITTmQTQPAK0RMgITm+QTS+ciPCcikQpPY4MjmYTO+czmcyHh4TCmcvJ54nCPCBjBJx4oECc8zJ6ATTn48RE4YTTHh4SDH4ImRFBwTGFBgTGFBgSGFBYmHUgITRmcyFBASFAoUjE5PzkQLBHJxiDAQP/GxAA=="))

View File

@ -0,0 +1,466 @@
const DateUtils = require("solar_date_utils.js");
const Math2 = require("solar_math_utils.js");
const GraphicUtils = require("solar_graphic_utils.js");
const Colors = require("solar_colors.js");
const LocationUtils = require("solar_location.js");
var screen_info = {
screen_width : g.getWidth(),
screen_start_x : 0,
screen_centre_x: g.getWidth()/2,
screen_height : (g.getHeight()-100),
screen_start_y : 100,
screen_centre_y : 90 + (g.getHeight()-100)/2,
screen_bg_color : Colors.BLACK,
sun_radius: 8,
sun_x : null,
sun_y : null,
sunrise_y : null,
}
const img_width=40;
const img_height=30;
var img_buffer = Graphics.createArrayBuffer(img_width,img_height,8);
var img = {width:img_width,height:img_height,bpp:8,transparent:0,buffer:img_buffer.buffer};
var img_info = {
x: null,
y: null,
img: img,
img_buffer: img_buffer
}
const COSINE_COLOUR= Colors.GREY;
const HORIZON_COLOUR = Colors.GREY;
const SolarController = require("solar_controller.js");
var controller = new SolarController();
var curr_mode = null;
var last_sun_draw_time = null;
var draw_full_cosine = true;
function draw_sun(now, day_info) {
var now_fraction = (now.getTime() - day_info.day_start.getTime())/DateUtils.DAY_MILLIS;
var now_x = now_fraction * screen_info.screen_width;
if(screen_info.sun_x != null && Math.abs(now_x- screen_info.sun_x) < 1){
console.log("no sun movement");
return false;
}
// now calculate thew new sun coordinates
var now_radians = Math2.TWO_PI *(now_x - screen_info.screen_centre_x)/screen_info.screen_width;
var now_y = screen_info.screen_centre_y - (screen_info.screen_height * Math.cos(now_radians) / 2);
if(Math.abs(now_x - screen_info.sun_x) > 5){
clear_sun();
}
// update the screen info with the new sun info
screen_info.sun_x = now_x;
screen_info.sun_y = now_y;
last_sun_draw_time = now;
if(draw_full_cosine){
//console.log("drawing full cosine");
GraphicUtils.draw_cosine(screen_info.screen_start_x,
screen_info.screen_width,
COSINE_COLOUR,
screen_info);
draw_full_cosine = false;
}
if(curr_mode == null) {
GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info);
}
// decide on the new sun drawing mode and draw
curr_mode = controller.mode(now,day_info,screen_info);
img_info.img_buffer.clear();
img_info.img_buffer.setColor(screen_info.screen_bg_color[0],
screen_info.screen_bg_color[1],
screen_info.screen_bg_color[2],
);
img_info.img_buffer.fillRect(0,0,img_width, img_height);
img_info.x = screen_info.sun_x - img_info.img.width/2;
img_info.y = screen_info.sun_y - img_info.img.height/2;
var cosine_dist = screen_info.sun_radius/Math.sqrt(2);
GraphicUtils.draw_cosine(img_info.x,
screen_info.sun_x - cosine_dist,
COSINE_COLOUR,
screen_info,
img_info);
GraphicUtils.draw_cosine(screen_info.sun_x + cosine_dist,
screen_info.sun_x + img_width,
COSINE_COLOUR,
screen_info,
img_info);
curr_mode.draw(now,day_info,screen_info,img_info);
var sunrise_dist = Math.abs(screen_info.sunrise_y-screen_info.sun_y);
if( sunrise_dist <= img_height) {
GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info,img_info);
} else if(sunrise_dist <= img_height*2.5) {
GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info);
}
// we draw a blank where the image is going to be drawn to clear out the area
g.setColor(screen_info.screen_bg_color[0],screen_info.screen_bg_color[1],screen_info.screen_bg_color[2]);
g.fillRect(img_info.x,img_info.y-2,img_info.x+img_width,img_info.y + img_height + 2);
g.drawImage(img,img_info.x,img_info.y);
// paint the cosine curve back to the normal color where it just came from
GraphicUtils.draw_cosine(img_info.x - 3,
img_info.x,
COSINE_COLOUR,
screen_info);
GraphicUtils.draw_cosine(img_info.x + img_width,
img_info.x + img_width + 3,
COSINE_COLOUR,
screen_info);
return true;
}
function clear_sun(){
g.setColor(screen_info.screen_bg_color[0],screen_info.screen_bg_color[1],screen_info.screen_bg_color[2]);
g.fillRect(img_info.x,img_info.y,img_info.x+img_width,img_info.y + img_width);
GraphicUtils.draw_cosine(img_info.x - 4,
img_info.x + img_width + 4,
COSINE_COLOUR,
screen_info);
GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info);
screen_info.sun_x = null;
screen_info.sun_y = null;
}
var last_time = null;
var last_offset = null;
var last_date = null;
const time_color = Colors.WHITE;
const date_color = Colors.YELLOW;
const DATE_Y_COORD = 35;
const DATE_X_COORD = 10;
const TIME_X_COORD = 140;
const TIME_Y_COORD = 35;
const OFFSET_Y_COORD = 70;
const LOCATION_Y_COORD = 55;
function write_date(now){
var new_date = require('locale').dow(now,1) + " " + Math2.format00(now.getDate());
//console.log("writing date:" + new_date)
g.setFont("Vector",15);
g.setFontAlign(-1,-1,0);
if(last_date != null){
if(new_date == last_date){
return;
}
g.setColor(screen_info.screen_bg_color[0],
screen_info.screen_bg_color[1],
screen_info.screen_bg_color[2]);
g.drawString(last_date, DATE_X_COORD,DATE_Y_COORD);
}
g.setColor(date_color[0],date_color[1],date_color[2]);
g.drawString(new_date, DATE_X_COORD,DATE_Y_COORD);
last_date = new_date;
}
var last_status_msg = ""
var last_gps_coords_msg_n = "";
var last_gps_coords_msg_e = "";
const GPS_MSG_X_COORD = 70;
const GPS_MSG_Y = 220;
const GPS_MSG_COORDS_Y_E = 80;
const GPS_MSG_COORDS_Y_N = 90;
function write_GPS_status(){
var gps_coords = location.getCoordinates();
var gps_coords_msg_n;
var gps_coords_msg_e;
if(gps_coords != null){
gps_coords_msg_n = "N: " + gps_coords[0];
gps_coords_msg_n = gps_coords_msg_n.substr(0,Math.min(gps_coords_msg_n.length - 1,10));
gps_coords_msg_e = "E: " + gps_coords[1];
gps_coords_msg_e = gps_coords_msg_e.substr(0,Math.min(gps_coords_msg_e.length - 1,10));
} else {
gps_coords_msg_n = "";
gps_coords_msg_e = "";
}
var status_msg = "";
if(location.isGPSLocation()) {
if(gps_coords == null) {
if (location.getGPSPower() > 0) {
status_msg = "Finding GPS Position";
} else {
status_msg = "ERROR GPS Position not found";
}
} else {
if (location.getGPSPower() > 0) {
status_msg = "Updating GPS Position";
}
}
}
g.setFont("Vector",11);
g.setFontAlign(-1,-1,0);
if(last_status_msg != status_msg) {
g.setColor(screen_info.screen_bg_color[0],
screen_info.screen_bg_color[1],
screen_info.screen_bg_color[2]);
g.drawString(last_status_msg, GPS_MSG_X_COORD, GPS_MSG_Y);
g.setColor(Colors.YELLOW[0],Colors.YELLOW[1],Colors.YELLOW[2]);
g.drawString(status_msg, GPS_MSG_X_COORD, GPS_MSG_Y);
last_status_msg = status_msg;
}
if(last_gps_coords_msg_e != gps_coords_msg_e) {
g.setColor(screen_info.screen_bg_color[0],
screen_info.screen_bg_color[1],
screen_info.screen_bg_color[2]);
g.drawString(last_gps_coords_msg_e, DATE_X_COORD, GPS_MSG_COORDS_Y_E);
g.drawString(last_gps_coords_msg_n, DATE_X_COORD, GPS_MSG_COORDS_Y_N);
g.setColor(Colors.WHITE[0],Colors.WHITE[1],Colors.WHITE[2]);
g.drawString(gps_coords_msg_e, DATE_X_COORD, GPS_MSG_COORDS_Y_E);
g.drawString(gps_coords_msg_n, DATE_X_COORD, GPS_MSG_COORDS_Y_N);
last_gps_coords_msg_e = gps_coords_msg_e;
last_gps_coords_msg_n = gps_coords_msg_n;
}
}
function write_time(now){
var new_time = format_time(now);
g.setFont("Vector",35);
g.setFontAlign(-1,-1,0);
if(last_time != null){
g.setColor(screen_info.screen_bg_color[0],screen_info.screen_bg_color[1],screen_info.screen_bg_color[2]);
g.drawString(last_time, TIME_X_COORD,TIME_Y_COORD);
}
g.setColor(time_color[0],time_color[1],time_color[2]);
g.drawString(new_time, TIME_X_COORD,TIME_Y_COORD);
last_time = new_time;
}
function format_time(now){
var time = new Date(now.getTime() - time_offset);
var hours = time.getHours() % 12;
if(hours < 1){
hours = 12;
}
return Math2.format00(hours) + ":" + Math2.format00(time.getMinutes());
}
function write_offset(){
var new_offset = format_offset();
g.setFont("Vector",15);
g.setFontAlign(-1,-1,0);
if(last_offset != null){
g.setColor(screen_info.screen_bg_color[0],screen_info.screen_bg_color[1],screen_info.screen_bg_color[2]);
g.drawString(last_offset, TIME_X_COORD,OFFSET_Y_COORD);
}
g.setColor(time_color[0],time_color[1],time_color[2]);
g.drawString(new_offset, TIME_X_COORD,OFFSET_Y_COORD);
last_offset = new_offset;
}
function format_offset(){
if(time_offset == 0)
return "";
var hours_offset = Math.abs(time_offset) / DateUtils.HOUR_MILLIS;
var mins_offset = Math.abs(time_offset) / DateUtils.MIN_MILLIS;
var mins_offset_from_hour = mins_offset % 60;
//console.log("mins offset=" + mins_offset + " mins_offset_from_hour=" + mins_offset_from_hour);
var sign = "+";
if(time_offset < 0)
sign = "-";
return sign + Math2.format00(hours_offset) + ":" + Math2.format00(mins_offset_from_hour);
}
let time_offset = 0;
let last_draw_time = null;
var day_info = null;
var location = LocationUtils.load_locations();
var last_location_name = null;
function write_location_name() {
var new_location_name = location.getName();
g.setFont("Vector", 20);
g.setFontAlign(-1, -1, 0);
if (last_location_name != null) {
g.setColor(screen_info.screen_bg_color[0], screen_info.screen_bg_color[1], screen_info.screen_bg_color[2]);
g.drawString(last_location_name, DATE_X_COORD, LOCATION_Y_COORD);
}
g.setColor(time_color[0], time_color[1], time_color[2]);
if (new_location_name != "local") {
g.drawString(new_location_name, DATE_X_COORD, LOCATION_Y_COORD);
}
last_location_name = new_location_name;
}
location.addUpdateListener(
(loc)=>{
console.log("location update:" + JSON.stringify(loc));
clear_sun();
GraphicUtils.draw_sunrise_line(screen_info.screen_bg_color, day_info, screen_info);
day_info = null;
screen_info.sunrise_y = null;
curr_mode = null;
draw_clock();
}
);
function dayInfo(now) {
if (day_info == null || now > day_info.day_end) {
var coords = location.getCoordinates();
if(coords != null) {
day_info = DateUtils.sunrise_sunset(now, coords[0], coords[1], location.getUTCOffset());
//console.log("day info:" + JSON.stringify(day_info));
} else {
day_info = null;
}
}
return day_info;
}
function time_now() {
var timezone_offset_hours = location.getUTCOffset();
if(timezone_offset_hours != null) {
var local_offset_hours = -new Date().getTimezoneOffset()/60;
var timezone_offset_millis =
(timezone_offset_hours - local_offset_hours) * DateUtils.HOUR_MILLIS;
return new Date(Date.now() + time_offset + timezone_offset_millis);
} else {
return new Date(Date.now() + time_offset);
}
}
function draw_clock(){
var start_time = Date.now();
var now = time_now();
var day_info = dayInfo(now);
if(day_info != null) {
draw_sun(now, day_info);
}
write_time(now);
write_date(now);
write_offset();
write_location_name();
write_GPS_status();
last_draw_time = now;
log_memory_used();
var time_taken = Date.now() - start_time;
console.log("drawing clock:" + now.toISOString() + " time taken:" + time_taken );
}
function log_memory_used() {
var memory = process.memory();
console.log("memory used:" + memory.usage +
" total:" + memory.total + "->" +
" ->" + memory.usage/memory.total
);
}
function button1pressed(){
console.log("button 1 pressed");
time_offset = 0;
clear_sun();
day_info = null;
draw_clock();
}
function button3pressed(){
console.log("button 3 pressed");
time_offset = 0;
location.nextLocation();
}
function button4pressed(){
time_offset -= DateUtils.HOUR_MILLIS/4;
draw_clock();
setTimeout(()=>{
if(BTN4.read()){
button4pressed();
}
},
50
)
}
function button5pressed(){
time_offset += DateUtils.HOUR_MILLIS/4;
draw_clock();
setTimeout(()=>{
if(BTN5.read()){
button5pressed();
}
},
50
)
}
// The interval reference for updating the clock
let interval_ref = null;
function clear_timers(){
if(interval_ref != null) {
clearInterval(interval_ref);
interval_ref = null;
}
}
function start_timers(){
var date = new Date();
var secs = date.getSeconds();
var nextMinuteStart = 60 - secs;
setTimeout(schedule_draw_clock,nextMinuteStart * 1000);
draw_clock();
}
function schedule_draw_clock(){
clear_timers();
if (Bangle.isLCDOn()) {
interval_ref = setInterval(() => {
if (!Bangle.isLCDOn()) {
console.log("draw clock callback - skipped redraw");
} else {
draw_clock();
}
}, DateUtils.MIN_MILLIS
);
draw_clock();
} else {
console.log("scheduleDrawClock - skipped not visible");
}
}
Bangle.on('lcdPower', (on) => {
if (on) {
console.log("lcdPower: on");
draw_clock();
start_timers();
} else {
console.log("lcdPower: off");
clear_timers();
}
});
Bangle.on('faceUp',function(up){
if (up && !Bangle.isLCDOn()) {
clear_timers();
Bangle.setLCDPower(true);
}
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
start_timers();
function button2pressed(){
controller = null;
location.shutdown();
location = null;
Bangle.showLauncher();
}
setWatch(button2pressed, BTN2,{repeat:false,edge:"falling"});
setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"});
setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"});
setWatch(button4pressed, BTN4,{repeat:true,edge:"rising"});
setWatch(button5pressed, BTN5,{repeat:true,edge:"rising"});

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,14 @@
const _BLACK= [0.0,0.0,0.0];
const _GREY= [0.5,0.5,0.5];
const _WHITE= [1.0,1.0,1.0];
const _YELLOW= [1.0,1.0,0.0];
const _RED= [1.0,0.0,0.0];
const Colors = {
BLACK: _BLACK,
GREY: _GREY,
WHITE: _WHITE,
YELLOW: _YELLOW,
RED: _RED
};
module.exports = Colors;

View File

@ -0,0 +1,247 @@
const Math2 = require("solar_math_utils.js");
const DateUtils = require("solar_date_utils.js");
const GraphicUtils = require("solar_graphic_utils.js");
const Colors = require("solar_colors.js");
const CORONA_GAP = 2;
const CORONA_MIN_LENGTH = 2;
const CORONA_LINES = 12;
const CORONA_RADIUS = 14;
const SUNSET_START_HEIGHT = 10;
const SUNSET_COLOUR = Colors.RED;
const SUNRISE_COLOUR = [1,0.6,0];
const MIDDAY_COLOUR = [1,1,0.7];
const NIGHT_COLOUR = Colors.BLACK;
function daytime_sun_color(now,day_info){
var now_fraction_of_day =DateUtils.now_fraction_of_day(now,day_info);
if(now > day_info.sunset_date){
return SUNSET_COLOUR;
} else if(now < day_info.sunrise_date){
return SUNRISE_COLOUR;
} else if(now < day_info.solar_noon) {
var sunrise_fraction = (day_info.sunrise_date - day_info.day_start) / DateUtils.DAY_MILLIS;
var rise_to_midday_fraction = (now_fraction_of_day - sunrise_fraction) / (0.5 - sunrise_fraction);
return Math2.interpolate(SUNRISE_COLOUR, MIDDAY_COLOUR, rise_to_midday_fraction);
} else {
var sunset_fraction = (day_info.day_end - day_info.sunset_date) / DateUtils.DAY_MILLIS;
var midday_to_sunset_fraction = (now_fraction_of_day - 0.5)/(0.5 - sunset_fraction);
//console.log("sunset_fraction=" + sunset_fraction + " midday_to_sunset_fraction=" + midday_to_sunset_fraction)
return Math2.interpolate(MIDDAY_COLOUR,SUNSET_COLOUR,midday_to_sunset_fraction);
}
}
function draw_night_sun(sun_x,sun_y,sun_radius,img_info){
var draw_info = GraphicUtils.draw_info(img_info);
draw_info.buff.setColor(Colors.WHITE[0],Colors.WHITE[1],Colors.WHITE[2]);
draw_info.buff.fillCircle(sun_x - draw_info.offset_x,
sun_y - draw_info.offset_y,
sun_radius);
draw_info.buff.setColor(NIGHT_COLOUR[0],NIGHT_COLOUR[1],NIGHT_COLOUR[2]);
draw_info.buff.fillCircle(sun_x - draw_info.offset_x,
sun_y - draw_info.offset_y,
sun_radius-1);
}
function draw_partial_sun(time, day_info, screen_info,img_info){
var sun_height = screen_info.sunrise_y - screen_info.sun_y;
if(sun_height > screen_info.sun_radius){
var sun_color = daytime_sun_color(time,day_info);
var draw_info = GraphicUtils.draw_info(img_info);
draw_info.buff.setColor(sun_color[0],sun_color[1],sun_color[2]);
draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x,
screen_info.sun_y - draw_info.offset_y,
screen_info.sun_radius
);
} else if(sun_height < -screen_info.sun_radius){
draw_night_sun(screen_info.sun_x,screen_info.sun_y,screen_info.sun_radius, img_info);
} else {
var draw_info = GraphicUtils.draw_info(img_info);
draw_info.buff.setColor(NIGHT_COLOUR[0],NIGHT_COLOUR[1],NIGHT_COLOUR[2]);
draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x,
screen_info.sun_y - draw_info.offset_y,
screen_info.sun_radius-1);
var sun_color = daytime_sun_color(time,day_info);
draw_info.buff.setColor(sun_color[0],sun_color[1],sun_color[2]);
draw_info.buff.drawCircle(screen_info.sun_x - draw_info.offset_x,
screen_info.sun_y - draw_info.offset_y,
screen_info.sun_radius);
GraphicUtils.fill_circle_partial_y(screen_info.sun_x,
screen_info.sun_y,
screen_info.sun_radius,
screen_info.sun_y - screen_info.sun_radius,
screen_info.sunrise_y,
img_info
);
}
}
function draw_random_background(screen_info,
img_info,
rgb_init,
rgb_step
){
var draw_info = GraphicUtils.draw_info(img_info);
var rgb = rgb_init;
var sky_to = Math.min(screen_info.sunrise_y-1, img_info.y + img_info.img.height - 3);
for(var sky_y=img_info.y+3;sky_y<sky_to ; sky_y++){
for(var i=0;i<rgb.length;i++){
if(rgb_step[i]>0)
rgb[i] = Math2.random_walk(rgb[i],rgb_step[i],1,0);
}
draw_info.buff.setColor(rgb[0],rgb[1],rgb[2]);
draw_info.buff.moveTo(screen_info.sun_x +
Math.random()*img_info.img.width/8 -
0.4*img_info.img.width -
draw_info.offset_x,
sky_y - draw_info.offset_y);
draw_info.buff.lineTo(screen_info.sun_x +
0.4*img_info.img.width -
Math.random()*img_info.img.width/8 -
draw_info.offset_x,
sky_y - draw_info.offset_y);
}
draw_info.buff.setColor(NIGHT_COLOUR[0],NIGHT_COLOUR[1],NIGHT_COLOUR[2]);
draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x,
screen_info.sun_y - draw_info.offset_y,
screen_info.sun_radius+1);
}
class SolarMode {
test(time, day_info, screen_info){ throw "test undefined";}
draw(time, day_info, screen_info, img_buffer_info){
throw "sun drawing undefined";
}
}
class NightMode extends SolarMode {
toString(){return "NightMode";}
test(time, day_info, screen_info, img_info) {
return (time < day_info.sunrise_date || time > day_info.sunset_date);
}
draw(time, day_info, screen_info, img_info){
draw_night_sun(screen_info.sun_x,screen_info.sun_y,screen_info.sun_radius, img_info);
}
}
class DayLightMode extends SolarMode {
toString(){
return "DayLightMode";
}
test(time, day_info, screen_info){
var sun_height = screen_info.sunrise_y - screen_info.sun_y;
/*console.log("DayLightMode " +
"time=" + time.toISOString() +
" sunset_date=" + day_info.sunset_date.toISOString() +
" sunrise_date=" + day_info.sunrise_date.toISOString()
);*/
return time < day_info.sunset_date &&
time > day_info.sunrise_date &&
sun_height >= screen_info.sun_radius * 2 + SUNSET_START_HEIGHT;
}
_calc_corona_radius(now, day_info){
if(now < day_info.sunset_date &&
now > day_info.sunrise_date){
var now_fraction_of_day =DateUtils.now_fraction_of_day(now,day_info);
var sunset_fraction = (day_info.sunset_date.getTime() - day_info.day_start.getTime())/DateUtils.DAY_MILLIS;
var now_fraction_from_midday =
1 - Math.abs(now_fraction_of_day-0.5)/(sunset_fraction-0.5);
return CORONA_RADIUS * now_fraction_from_midday;
} else {
return 0;
}
}
_drawCorona(corona_radius,sun_x,sun_y,sun_radius, draw_info){
var thickness_rads = (Math2.TWO_PI/CORONA_LINES)/3;
var from_radius = sun_radius + CORONA_GAP;
if(corona_radius > from_radius + CORONA_MIN_LENGTH) {
for (var i = 0; i < CORONA_LINES; i++) {
var to_x1 = sun_x - draw_info.offset_x + from_radius * Math.cos(i * Math2.TWO_PI / CORONA_LINES + thickness_rads);
var to_y1 = sun_y - draw_info.offset_y + from_radius * Math.sin(i * Math2.TWO_PI / CORONA_LINES + thickness_rads);
var to_x2 = sun_x - draw_info.offset_x + from_radius * Math.cos(i * Math2.TWO_PI / CORONA_LINES - thickness_rads);
var to_y2 = sun_y - draw_info.offset_y + from_radius * Math.sin(i * Math2.TWO_PI / CORONA_LINES - thickness_rads);
var to_x3 = sun_x - draw_info.offset_x + corona_radius * Math.cos(i * Math2.TWO_PI / CORONA_LINES);
var to_y3 = sun_y - draw_info.offset_y + corona_radius * Math.sin(i * Math2.TWO_PI / CORONA_LINES);
draw_info.buff.fillPoly([to_x1, to_y1, to_x2, to_y2, to_x3, to_y3]);
}
}
}
draw(now, day_info, screen_info, img_info){
var sun_color = daytime_sun_color(now,day_info);
var corona_radius = this._calc_corona_radius(now, day_info);
var draw_info = GraphicUtils.draw_info(img_info);
draw_info.buff.setColor(sun_color[0],sun_color[1],sun_color[2]);
if(corona_radius > screen_info.sun_radius){
this._drawCorona(corona_radius,
screen_info.sun_x,
screen_info.sun_y,
screen_info.sun_radius,
draw_info);
}
draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x,
screen_info.sun_y - draw_info.offset_y,
screen_info.sun_radius);
}
}
class TwiLightMode extends SolarMode {
toString(){
return "TwilightMode";
}
test(time, day_info, screen_info){
if(screen_info.sunrise_y == null) {
console.log("warning no sunrise_defined");
return false;
}
var sun_height = screen_info.sunrise_y - screen_info.sun_y;
/*console.log("TwilightMode " +
"time=" + time.toISOString() +
" sun_height=" + sun_height +
" sun_radius=" + screen_info.sun_radius
);*/
if(sun_height > -screen_info.sun_radius &&
sun_height < screen_info.sun_radius * 2 + SUNSET_START_HEIGHT
){
//console.log("selected TwilightMode");
return true;
}
return false;
}
draw(time, day_info, screen_info, img_info){
if(time < day_info.solar_noon) {
draw_random_background(screen_info,
img_info,
[0,0.8,1],
[0.05,0.05,0.0]);
} else {
draw_random_background(screen_info,
img_info,
[1,0.75,Math.random()],
[0,0.05,0.05]);
}
draw_partial_sun(time,day_info,screen_info,img_info);
}
}
class SolarControllerImpl {
constructor(){
this.solar_modes = [new TwiLightMode(), new DayLightMode()];
this.default_mode = new NightMode();
this.last = null;
}
toString(){
return "SolarControllerImpl";
}
mode(time, day_info, screen_info){
if(this.last != null){
if(this.last.test(time,day_info,screen_info)){
return this.last;
}
}
for(var i=0; i<this.solar_modes.length; i++){
if(this.solar_modes[i].test(time,day_info,screen_info) ){
this.last = this.solar_modes[i];
return this.last;
}
}
//console.log("defaulting");
this.last = this.default_mode;
return this.last;
}
}
module.exports = SolarControllerImpl;

View File

@ -0,0 +1,132 @@
var Math2 = require("solar_math_utils.js");
const _MIN_MILLIS = 1000 * 60 ;
const _HOUR_MILLIS = _MIN_MILLIS * 60;
const _DAY_MILLIS = _HOUR_MILLIS * 24;
function _start_of_julian_day(now){
var sod_of_day = new Date(now.getTime());
var local_offset_hours = sod_of_day.getTimezoneOffset()/60;
//console.log("local_offset_hours=" + local_offset_hours);
sod_of_day.setHours(12 + local_offset_hours,0,0,0);
return sod_of_day;
}
function _date_to_julian_date(now){
var year = now.getFullYear();
var month = now.getMonth() + 1;
var day = now.getDate();
var julian_date_full = (1461 * (year + 4800 + (month-14)/12))/4 +(367 * (month-2 - 12*((month - 14)/12)))/12-(3 * ((year + 4900 + (month - 14)/12)/100))/4 + day - 32075;
var julian_date = (julian_date_full| 0);
return julian_date;
}
function _to_time(now,day_fraction){
var datetime = new Date(now.getTime());
var hours = (day_fraction * 24) |0;
var remainder = day_fraction - hours/24;
var mins = (remainder * 24 * 60 | 0);
var remainder = remainder - mins/(24*60);
var secs = (remainder * 24 * 60 * 60 | 0);
var remainder = remainder - secs /(24 * 60 * 60);
var millis = remainder * 24 * 60 * 60 * 1000;
datetime.setHours(hours, mins, secs,millis);
return datetime;
}
const DateUtils = {
DAY_MILLIS : _DAY_MILLIS,
HOUR_MILLIS : _HOUR_MILLIS,
MIN_MILLIS: _MIN_MILLIS,
sunrise_sunset: (now,longitude,latitude, utc_offset)=>{
var sod_julian = _start_of_julian_day(now);
var julian_date = _date_to_julian_date(sod_julian);
//console.log("julian date=" + julian_date);
//var n = julian_date - 2451545.0 + 0.0008;
var julian_century = (julian_date-2451545)/36525;
//console.log("julian_century=" + julian_century);
var geom_mean_long_sun_degrees = (280.46646+julian_century*(36000.76983 + julian_century*0.0003032)) % 360;
//console.log("geom_mean_long_sun=" + geom_mean_long_sun_degrees);
var geom_mean_anomaly_sun_degrees = 357.52911+julian_century*(35999.05029 - 0.0001537*julian_century);
//console.log("solar_mean_anomaly_sun=" + geom_mean_anomaly_sun_degrees);
var eccent_earth_orbit = 0.016708634-julian_century*(0.000042037+0.0000001267*julian_century);
//console.log("eccent_earth_orbit=" + eccent_earth_orbit);
var sun_eq_of_ctr = Math.sin(Math2.to_radians(geom_mean_anomaly_sun_degrees))*
(1.914602-julian_century*(0.004817+0.000014*julian_century))+
Math.sin(Math2.to_radians(2*geom_mean_anomaly_sun_degrees))*(0.019993-0.000101*julian_century)+
Math.sin(Math2.to_radians(3*geom_mean_anomaly_sun_degrees))*0.000289;
//console.log("sun_eq_of_ctr=" + sun_eq_of_ctr);
var sun_true_long_degrees = geom_mean_long_sun_degrees + sun_eq_of_ctr;
//console.log("sun_true_long_degrees=" + sun_true_long_degrees);
var sun_true_anom_degrees = geom_mean_anomaly_sun_degrees + sun_eq_of_ctr;
//console.log("sun_true_anom_degrees=" + sun_true_anom_degrees);
var sun_rad_vector_AUs = (1.000001018*(1-eccent_earth_orbit*eccent_earth_orbit))/(1+eccent_earth_orbit*Math.cos(Math2.to_radians(sun_true_anom_degrees)))
//console.log("sun_rad_vector_AUs=" + sun_rad_vector_AUs);
var sun_app_long_degress = sun_true_long_degrees-0.00569-0.00478*Math.sin(Math2.to_radians(125.04-1934.136*julian_century));
//console.log("sun_app_long_degress=" + sun_app_long_degress);
var mean_obliq_ecliptic_degrees = 23+(26+((21.448-julian_century*(46.815+julian_century*(0.00059-julian_century*0.001813))))/60)/60;
//console.log("mean_obliq_ecliptic_degrees=" + mean_obliq_ecliptic_degrees);
var obliq_corr_degrees = mean_obliq_ecliptic_degrees+0.00256*Math.cos(Math2.to_radians(125.04-1934.136*julian_century))
//console.log("obliq_corr_degrees=" + obliq_corr_degrees);
var sun_declin_degrees = Math2.to_degrees(
Math.asin(Math.sin(Math2.to_radians(obliq_corr_degrees))*Math.sin(Math2.to_radians(sun_app_long_degress)))
);
//console.log("sun_declin_degrees=" + sun_declin_degrees);
var var_y = Math.tan(Math2.to_radians(obliq_corr_degrees/2))*Math.tan(Math2.to_radians(obliq_corr_degrees/2));
//console.log("var_y=" + var_y);
var eq_of_time = 4*Math2.to_degrees(
var_y*Math.sin(2*Math2.to_radians(geom_mean_long_sun_degrees))-
2*eccent_earth_orbit*Math.sin(Math2.to_radians(geom_mean_anomaly_sun_degrees))+
4*eccent_earth_orbit*var_y*Math.sin(Math2.to_radians(geom_mean_anomaly_sun_degrees))*Math.cos(2*Math2.to_radians(geom_mean_long_sun_degrees))-
0.5*var_y*var_y*Math.sin(4*Math2.to_radians(geom_mean_long_sun_degrees))-
1.25*eccent_earth_orbit*eccent_earth_orbit*Math.sin(2*Math2.to_radians(geom_mean_anomaly_sun_degrees))
);
//console.log("eq_of_time=" + eq_of_time);
var HA_sunrise_degrees = Math2.to_degrees(
Math.acos(
Math.cos(Math2.to_radians(90.833))/(Math.cos(Math2.to_radians(latitude))*Math.cos(Math2.to_radians(sun_declin_degrees)))-
Math.tan(Math2.to_radians(latitude))*Math.tan(Math2.to_radians(sun_declin_degrees))
)
);
//console.log("HA_sunrise_degrees=" + HA_sunrise_degrees);
var local_offset_hours = new Date().getTimezoneOffset()/60;
if(utc_offset == null){
utc_offset = -local_offset_hours;
}
var timezone_offset_hours = (utc_offset - local_offset_hours);
console.log("timezone_offset_hours=" + timezone_offset_hours +
" longitude" + longitude +
" utc_offset=" + utc_offset
);
var solar_noon = (720-4*longitude-eq_of_time+timezone_offset_hours*60)/1440;
var solar_noon_datetime = _to_time(now,solar_noon);
console.log("solar_noon=" + solar_noon + "->" + solar_noon_datetime.toISOString());
var sunrise_time_LST = (solar_noon*1440-HA_sunrise_degrees*4)/1440;
var sunrise_time_LST_datetime = _to_time(now,sunrise_time_LST);
console.log("sunrise_time_LST=" + sunrise_time_LST +
"->" + sunrise_time_LST_datetime.toISOString());
var sunset_time_LST =(solar_noon*1440+HA_sunrise_degrees*4)/1440;
var sunset_time_LST_datetime = _to_time(now,sunset_time_LST);
console.log("sunset_time_LST=" + sunset_time_LST +
"->" + sunset_time_LST_datetime.toISOString());
return {
day_start: new Date(solar_noon_datetime.getTime() - _DAY_MILLIS / 2),
sunrise_date: sunrise_time_LST_datetime,
//sunrise_fraction: sunrise_time_LST,
sunset_date: sunset_time_LST_datetime,
//sunset_fraction: sunset_time_LST,
solar_noon: solar_noon_datetime,
day_end: new Date(solar_noon_datetime.getTime() + _DAY_MILLIS / 2)
};
},
now_fraction_of_day: (now,day_info)=>{
return (now.getTime() - day_info.day_start.getTime())/_DAY_MILLIS;
},
}
module.exports = DateUtils;

View File

@ -0,0 +1,94 @@
var DateUtils = require("solar_date_utils.js");
var Math2 = require("solar_math_utils.js");
function _draw_info(img_info){
if (img_info == null) {
return {
buff: g,
offset_x: 0,
offset_y: 0
};
} else {
return {
buff: img_info.img_buffer,
offset_x: img_info.x,
offset_y: img_info.y
};
}
}
const GraphicUtils = {
draw_info : (img_info)=>_draw_info(img_info),
draw_cosine : (from_x,to_x, line_colour, screen_info, img_info)=>{
//console.log("draw_cosine from_x=" + from_x + " to_x=" + to_x);
var draw_info = _draw_info(img_info);
draw_info.buff.reset();
draw_info.buff.setColor(line_colour[0],line_colour[1],line_colour[2]);
first = true;
for(var x=from_x; x<to_x;x++){
var radians = Math2.TWO_PI *((x-screen_info.screen_start_x) - screen_info.screen_centre_x)/(screen_info.screen_width);
var y = screen_info.screen_centre_y - screen_info.screen_height * Math.cos(radians)/2;
if(first) {
draw_info.buff.moveTo(x-draw_info.offset_x, y-draw_info.offset_y);
first = false;
} else {
draw_info.buff.lineTo(x-draw_info.offset_x,y - draw_info.offset_y);
}
//console.log("(x,y)=" + x +"," + y + " radians=" +radians );
}
draw_info.buff.reset();
},
draw_sunrise_line : (sunrise_colour, day_info, screen_info, img_info)=> {
var rise_y = screen_info.sunrise_y;
if (rise_y == null && day_info != null) {
var rise_fraction = (day_info.sunrise_date.getTime() - day_info.day_start.getTime()) / DateUtils.DAY_MILLIS;
var rise_radians = Math2.TWO_PI * (rise_fraction - 0.5);
var rise_y = screen_info.screen_centre_y - (screen_info.screen_height * Math.cos(rise_radians) / 2);
//console.log("rise_y=" + rise_y + " rise_fraction=" + rise_fraction + " rise_radian=" + rise_radians);
screen_info.sunrise_y = rise_y;
}
if(rise_y != null) {
var draw_info = _draw_info(img_info);
draw_info.buff.setColor(sunrise_colour[0], sunrise_colour[1], sunrise_colour[2]);
draw_info.buff.drawLine(screen_info.screen_start_x - draw_info.offset_x,
rise_y - draw_info.offset_y,
screen_info.screen_width,
rise_y - draw_info.offset_y);
}
},
fill_circle_partial_y : (center_x,center_y,radius,from_y,to_y, img_info)=>{
from_y = (from_y | 0);
to_y = (to_y | 0);
if(from_y > to_y){
var tmp_y = from_y;
from_y = to_y;
to_y = tmp_y;
}
var draw_info = _draw_info(img_info);
for(var y=from_y; y<=to_y; y++){
// now (y-center_y)^2 + (x-center_x)^2 = radius^2
// so (x-center_x)^2 = radius^2 - (y-center_y)^2
// so (x-center_x) = sqrt( radius^2 - (y-center_y)^2 )
// so x = center_x +/- sqrt( radius^2 - (y-center_y)^2 )
var x_dist2 =radius*radius - (y-center_y)*(y-center_y);
if(x_dist2 >= 0) {
var x_dist = Math.sqrt(x_dist2);
var from_x = (center_x - x_dist | 0);
var to_x = center_x + x_dist ;
var to_x_rounded = (to_x | 0);
if(to_x - to_x_rounded > 0.01)
to_x_rounded += 1;
draw_info.buff.drawLine(from_x-draw_info.offset_x,
y-draw_info.offset_y,
to_x_rounded-draw_info.offset_x,
y-draw_info.offset_y);
}
}
}
}
module.exports = GraphicUtils;

View File

@ -0,0 +1,4 @@
{
"coordinates": [-21.9,64.133],
"utc_offset": 0
}

View File

@ -0,0 +1,4 @@
{
"coordinates": [-159.4879501,22.2224610],
"utc_offset": -10
}

View File

@ -0,0 +1,4 @@
{
"coordinates": [139.6503,35.6762],
"utc_offset": 9
}

View File

@ -0,0 +1,3 @@
{
"coordinates": [-0.12574, 51.50853]
}

View File

@ -0,0 +1,84 @@
const storage = require("Storage");
class LocationManager {
constructor(locations) {
this.idx=0;
this.locations = locations;
this.listeners = [];
this.in_use = true;
this.gps_queried = false;
this.gpsPower = 0;
this.location_info = null;
}
init(){
this.location_info = storage.readJSON("solar_loc." + this.getName() + ".json");
if(this.isGPSLocation() && !this.gps_queried) {
console.log("updating local location");
this._gpsUpdate();
this.gps_queried = true;
} else {
}
}
setGPSPower(power){
this.gpsPower = power;
Bangle.setGPSPower(this.gpsPower);
}
getGPSPower(){return this.gpsPower;}
_gpsUpdate(){
this.setGPSPower(1);
Bangle.on('GPS', (g) => {
if (!this.in_use)
return;
if (g.fix) {
var loc_info = {
last_update: new Date(),
coordinates: [g.lon, g.lat]
};
console.log("Received gps fixing:" + JSON.stringify(loc_info));
storage.writeJSON("solar_loc.local.json", this.location_info);
if(this.isGPSLocation()){
this.location_info = loc_info;
this.notifyUpdate();
}
this.setGPSPower(0);
}
});
}
isGPSLocation(){return this.getName() == 'local';}
addUpdateListener(listener){this.listeners.push(listener);}
nextLocation() {
if(this.locations.length > 1) {
this.idx += 1;
this.idx = this.idx % this.locations.length;
console.log("location now:" + this.getName());
this.init();
this.notifyUpdate();
} else {
console.log("no extra locations found");
}
}
notifyUpdate(){
for(var i=0; i<this.listeners.length; i++){
this.listeners[i](this);
}
}
getUTCOffset(){return this.location_info.utc_offset;}
getName(){return this.locations[this.idx];}
getCoordinates(){return this.location_info.coordinates;}
shutdown(){
this.in_use=false;
this.setGPSPower(0);
}
}
const LOCATIONS_FILE = "solar_locations.json";
const LocationUtils = {
load_locations : ()=>{
var locations = storage.readJSON(LOCATIONS_FILE);
console.log("loaded locations:" + locations);
var mgr = new LocationManager(locations);
mgr.init();
return mgr;
}
}
module.exports = LocationUtils;

View File

@ -0,0 +1 @@
["local","Tokyo","Iceland","Kauai"]

View File

@ -0,0 +1,34 @@
const _TWO_PI = 2 * Math.PI;
const Maths2 = {
TWO_PI: _TWO_PI,
to_radians: (degrees)=> _TWO_PI * degrees / 360,
to_degrees: (radians)=> 360 * radians/ (_TWO_PI),
interpolate: (vector1, vector2, fraction)=>{
var result = [];
for(var i=0; i< vector1.length; i++){
var value = vector1[i] + (vector2[i] - vector1[i]) * fraction;
result.push(value);
}
return result;
},
format00: (num)=>{
var value = (num | 0);
if(value > 99 || value < 0)
throw "must be between in range 0-99";
if(value < 10)
return "0" + value.toString();
else
return value.toString();
},
random_walk: (value,step,max,min)=>{
if(Math.random()>0.5){
value -= step;
} else {
value += step;
}
value = Math.min(value,max);
value = Math.max(value,min);
return value;
}
}
module.exports = Maths2;