mirror of https://github.com/koreader/koreader
Plugin: Auto warmth and night mode (#8129)
("Auto nightmode" only on devices without warmth.)pull/8261/head
parent
8a750d4692
commit
20f7d14495
@ -0,0 +1,6 @@
|
|||||||
|
local _ = require("gettext")
|
||||||
|
return {
|
||||||
|
name = "autowarmth",
|
||||||
|
fullname = require("device"):hasNaturalLight() and _("Auto warmth and night mode") or _("Auto night mode"),
|
||||||
|
description = _([[This plugin allows set the frontlight warmth automagically.]]),
|
||||||
|
}
|
@ -0,0 +1,843 @@
|
|||||||
|
--[[--
|
||||||
|
@module koplugin.autowarmth
|
||||||
|
|
||||||
|
Plugin for setting screen warmth based on the sun position and/or a time schedule
|
||||||
|
]]
|
||||||
|
|
||||||
|
local Device = require("device")
|
||||||
|
|
||||||
|
local ConfirmBox = require("ui/widget/confirmbox")
|
||||||
|
local DoubleSpinWidget = require("/ui/widget/doublespinwidget")
|
||||||
|
local DeviceListener = require("device/devicelistener")
|
||||||
|
local Dispatcher = require("dispatcher")
|
||||||
|
local FFIUtil = require("ffi/util")
|
||||||
|
local InfoMessage = require("ui/widget/infomessage")
|
||||||
|
local InputDialog = require("ui/widget/inputdialog")
|
||||||
|
local Font = require("ui/font")
|
||||||
|
local Notification = require("ui/widget/notification")
|
||||||
|
local SpinWidget = require("ui/widget/spinwidget")
|
||||||
|
local SunTime = require("suntime")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
||||||
|
local _ = require("gettext")
|
||||||
|
local T = FFIUtil.template
|
||||||
|
local Screen = require("device").screen
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
|
local activate_sun = 1
|
||||||
|
local activate_schedule = 2
|
||||||
|
local activate_closer_noon = 3
|
||||||
|
local activate_closer_midnight =4
|
||||||
|
|
||||||
|
local midnight_index = 11
|
||||||
|
|
||||||
|
local device_max_warmth = Device:hasNaturalLight() and Device.powerd.fl_warmth_max or 100
|
||||||
|
local device_warmth_fit_scale = device_max_warmth / 100
|
||||||
|
|
||||||
|
local function frac(x)
|
||||||
|
return x - math.floor(x)
|
||||||
|
end
|
||||||
|
|
||||||
|
local AutoWarmth = WidgetContainer:new{
|
||||||
|
name = "autowarmth",
|
||||||
|
easy_mode = G_reader_settings:nilOrTrue("autowarmth_easy_mode"),
|
||||||
|
activate = G_reader_settings:readSetting("autowarmth_activate") or 0,
|
||||||
|
location = G_reader_settings:readSetting("autowarmth_location") or "Geysir",
|
||||||
|
latitude = G_reader_settings:readSetting("autowarmth_latitude") or 64.31, --great Geysir in Iceland
|
||||||
|
longitude = G_reader_settings:readSetting("autowarmth_longitude") or -20.30,
|
||||||
|
altitude = G_reader_settings:readSetting("autowarmth_altitude") or 200,
|
||||||
|
timezone = G_reader_settings:readSetting("autowarmth_timezone") or 0,
|
||||||
|
scheduler_times = G_reader_settings:readSetting("autowarmth_scheduler_times") or
|
||||||
|
{0.0, 5.5, 6.0, 6.5, 7.0, 13.0, 21.5, 22.0, 22.5, 23.0, 24.0},
|
||||||
|
warmth = G_reader_settings:readSetting("autowarmth_warmth")
|
||||||
|
or { 90, 90, 80, 60, 20, 20, 20, 60, 80, 90, 90},
|
||||||
|
sched_times = {},
|
||||||
|
sched_funcs = {}, -- necessary for unschedule, function, warmth
|
||||||
|
}
|
||||||
|
|
||||||
|
-- get timezone offset in hours (including dst)
|
||||||
|
function AutoWarmth:getTimezoneOffset()
|
||||||
|
local utcdate = os.date("!*t")
|
||||||
|
local localdate = os.date("*t")
|
||||||
|
return os.difftime(os.time(localdate), os.time(utcdate))/3600
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:init()
|
||||||
|
self:onDispatcherRegisterActions()
|
||||||
|
self.ui.menu:registerToMainMenu(self)
|
||||||
|
|
||||||
|
G_reader_settings:saveSetting("autowarmth_easy_mode", self.easy_mode)
|
||||||
|
-- schedule recalculation shortly afer midnight
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:onDispatcherRegisterActions()
|
||||||
|
Dispatcher:registerAction("show_ephemeris",
|
||||||
|
{category="none", event="ShowEphemeris", title=_("Show ephemeris"), general=true})
|
||||||
|
Dispatcher:registerAction("auto_warmth_off",
|
||||||
|
{category="none", event="AutoWarmthOff", title=_("Auto warmth off"), screen=true})
|
||||||
|
Dispatcher:registerAction("auto_warmth_cycle_trough",
|
||||||
|
{category="none", event="AutoWarmthMode", title=_("Auto warmth cycle through modes"), screen=true})
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:onShowEphemeris()
|
||||||
|
self:showTimesInfo(_("Information about the sun in"), true, activate_sun, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:onAutoWarmthOff()
|
||||||
|
self.activate = 0
|
||||||
|
G_reader_settings:saveSetting("autowarmth_activate", self.activate)
|
||||||
|
Notification:notify(_("Auto warmth turned off"))
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:onAutoWarmthMode()
|
||||||
|
if self.activate > 0 then
|
||||||
|
self.activate = self.activate - 1
|
||||||
|
else
|
||||||
|
self.activate = activate_closer_midnight
|
||||||
|
end
|
||||||
|
local notify_text
|
||||||
|
if self.activate == 0 then
|
||||||
|
notify_text = _("Auto warmth turned off")
|
||||||
|
elseif self.activate == activate_sun then
|
||||||
|
notify_text = _("Auto warmth use sun position")
|
||||||
|
elseif self.activate == activate_schedule then
|
||||||
|
notify_text = _("Auto warmth use schedule")
|
||||||
|
elseif self.activate == activate_closer_midnight then
|
||||||
|
notify_text = _("Auto warmth use whatever is closer to midnight")
|
||||||
|
elseif self.activate == activate_closer_noon then
|
||||||
|
notify_text = _("Auto warmth use whatever is closer to noon")
|
||||||
|
end
|
||||||
|
G_reader_settings:saveSetting("autowarmth_activate", self.activate)
|
||||||
|
Notification:notify(notify_text)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:onResume()
|
||||||
|
if self.activate == 0 then return end
|
||||||
|
|
||||||
|
local resume_date = os.date("*t")
|
||||||
|
|
||||||
|
-- check if resume and suspend are done on the same day
|
||||||
|
if resume_date.day == SunTime.date.day and resume_date.month == SunTime.date.month
|
||||||
|
and resume_date.year == SunTime.date.year then
|
||||||
|
local now = SunTime:getTimeInSec(resume_date)
|
||||||
|
self:scheduleWarmthChanges(now)
|
||||||
|
else
|
||||||
|
self:scheduleMidnightUpdate() -- resume is on the other day, do all calcs again
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- wrapper for unscheduling, so that only our setWarmth gets unscheduled
|
||||||
|
function AutoWarmth.setWarmth(val)
|
||||||
|
if val then
|
||||||
|
if val > 100 then
|
||||||
|
DeviceListener:onSetNightMode(true)
|
||||||
|
else
|
||||||
|
DeviceListener:onSetNightMode(false)
|
||||||
|
end
|
||||||
|
if Device:hasNaturalLight() then
|
||||||
|
val = math.min(val, 100)
|
||||||
|
Device.powerd:setWarmth(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:scheduleMidnightUpdate()
|
||||||
|
-- first unschedule all old functions
|
||||||
|
UIManager:unschedule(self.scheduleMidnightUpdate) -- when called from menu or resume
|
||||||
|
|
||||||
|
local toRad = math.pi / 180
|
||||||
|
SunTime:setPosition(self.location, self.latitude * toRad, self.longitude * toRad,
|
||||||
|
self.timezone, self.altitude)
|
||||||
|
SunTime:setAdvanced()
|
||||||
|
SunTime:setDate() -- today
|
||||||
|
SunTime:calculateTimes()
|
||||||
|
|
||||||
|
self.sched_times = {}
|
||||||
|
self.sched_funcs = {}
|
||||||
|
|
||||||
|
local function prepareSchedule(times, index1, index2)
|
||||||
|
local time1 = times[index1]
|
||||||
|
if not time1 then return end
|
||||||
|
|
||||||
|
local time = SunTime:getTimeInSec(time1)
|
||||||
|
table.insert(self.sched_times, time)
|
||||||
|
table.insert(self.sched_funcs, {AutoWarmth.setWarmth, self.warmth[index1]})
|
||||||
|
|
||||||
|
local time2 = times[index2]
|
||||||
|
if not time2 then return end -- to near to the pole
|
||||||
|
local warmth_diff = math.min(self.warmth[index2], 100) - math.min(self.warmth[index1], 100)
|
||||||
|
if warmth_diff ~= 0 then
|
||||||
|
local time_diff = SunTime:getTimeInSec(time2) - time
|
||||||
|
local delta_t = time_diff / math.abs(warmth_diff) -- can be inf, no problem
|
||||||
|
local delta_w = warmth_diff > 0 and 1 or -1
|
||||||
|
for i = 1, math.abs(warmth_diff)-1 do
|
||||||
|
local next_warmth = math.min(self.warmth[index1], 100) + delta_w * i
|
||||||
|
-- only apply warmth for steps the hardware has (e.g. Tolino has 0-10 hw steps
|
||||||
|
-- which map to warmth 0, 10, 20, 30 ... 100)
|
||||||
|
if frac(next_warmth * device_warmth_fit_scale) == 0 then
|
||||||
|
table.insert(self.sched_times, time + delta_t * i)
|
||||||
|
table.insert(self.sched_funcs, {self.setWarmth,
|
||||||
|
math.floor(math.min(self.warmth[index1], 100) + delta_w * i)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.activate == activate_sun then
|
||||||
|
self.current_times = {unpack(SunTime.times, 1, midnight_index)}
|
||||||
|
elseif self.activate == activate_schedule then
|
||||||
|
self.current_times = {unpack(self.scheduler_times, 1, midnight_index)}
|
||||||
|
else
|
||||||
|
self.current_times = {unpack(SunTime.times, 1, midnight_index)}
|
||||||
|
if self.activate == activate_closer_noon then
|
||||||
|
for i = 1, midnight_index do
|
||||||
|
if not self.current_times[i] then
|
||||||
|
self.current_times[i] = self.scheduler_times[i]
|
||||||
|
elseif self.scheduler_times[i] and
|
||||||
|
math.abs(self.current_times[i]%24 - 12) > math.abs(self.scheduler_times[i]%24 - 12) then
|
||||||
|
self.current_times[i] = self.scheduler_times[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else -- activate_closer_midnight
|
||||||
|
for i = 1, midnight_index do
|
||||||
|
if not self.current_times[i] then
|
||||||
|
self.current_times[i] = self.scheduler_times[i]
|
||||||
|
elseif self.scheduler_times[i] and
|
||||||
|
math.abs(self.current_times[i]%24 - 12) < math.abs(self.scheduler_times[i]%24 - 12) then
|
||||||
|
self.current_times[i] = self.scheduler_times[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.easy_mode then
|
||||||
|
self.current_times[1] = nil
|
||||||
|
self.current_times[2] = nil
|
||||||
|
self.current_times[3] = nil
|
||||||
|
self.current_times[6] = nil
|
||||||
|
self.current_times[9] = nil
|
||||||
|
self.current_times[10] = nil
|
||||||
|
self.current_times[11] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- here are dragons
|
||||||
|
local i = 1
|
||||||
|
-- find first valid entry
|
||||||
|
while not self.current_times[i] and i <= midnight_index do
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
local next
|
||||||
|
while i <= midnight_index do
|
||||||
|
next = i + 1
|
||||||
|
-- find next valid entry
|
||||||
|
while not self.current_times[next] and next <= midnight_index do
|
||||||
|
next = next + 1
|
||||||
|
end
|
||||||
|
prepareSchedule(self.current_times, i, next)
|
||||||
|
i = next
|
||||||
|
end
|
||||||
|
|
||||||
|
local now = SunTime:getTimeInSec()
|
||||||
|
|
||||||
|
-- reschedule 5sec after midnight
|
||||||
|
UIManager:scheduleIn(24*3600 + 5 - now, self.scheduleMidnightUpdate, self )
|
||||||
|
|
||||||
|
self:scheduleWarmthChanges(now)
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:scheduleWarmthChanges(time)
|
||||||
|
for i = 1, #self.sched_funcs do -- loop not essential, as unschedule unschedules all functions at once
|
||||||
|
if not UIManager:unschedule(self.sched_funcs[i][1]) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local actual_warmth
|
||||||
|
for i = 1, #self.sched_funcs do
|
||||||
|
if self.sched_times[i] > time then
|
||||||
|
UIManager:scheduleIn( self.sched_times[i] - time,
|
||||||
|
self.sched_funcs[i][1], self.sched_funcs[i][2])
|
||||||
|
else
|
||||||
|
actual_warmth = self.sched_funcs[i][2] or actual_warmth
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- update current warmth directly
|
||||||
|
self.setWarmth(actual_warmth)
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:hoursToClock(hours)
|
||||||
|
if hours then
|
||||||
|
hours = hours % 24 * 3600 + 0.01 -- round up, due to reduced precision in settings.reader.lua
|
||||||
|
end
|
||||||
|
return util.secondsToClock(hours, self.easy_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:addToMainMenu(menu_items)
|
||||||
|
menu_items.autowarmth = {
|
||||||
|
text = Device:hasNaturalLight() and _("Auto warmth and night mode")
|
||||||
|
or _("Auto night mode"),
|
||||||
|
checked_func = function() return self.activate ~= 0 end,
|
||||||
|
sub_item_table_func = function()
|
||||||
|
return self:getSubMenuItems()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tidy_menu(menu, request)
|
||||||
|
for i = #menu, 1, -1 do
|
||||||
|
if menu[i].mode ~=nil then
|
||||||
|
if menu[i].mode ~= request then
|
||||||
|
table.remove(menu,i)
|
||||||
|
else
|
||||||
|
menu[i].mode = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return menu
|
||||||
|
end
|
||||||
|
|
||||||
|
local about_text = _([[Set the frontlight warmth (if available) and night mode based on a time schedule or the sun's position.
|
||||||
|
|
||||||
|
There are three types of twilight:
|
||||||
|
|
||||||
|
• Civil: You can read a newspaper
|
||||||
|
• Nautical: You can see the first stars
|
||||||
|
• Astronomical: It is really dark
|
||||||
|
|
||||||
|
Custom warmth values can be set for every kind of twilight and sunrise, noon, sunset and midnight.
|
||||||
|
The screen warmth is continuously adjusted to the current time.
|
||||||
|
|
||||||
|
To use the sun's position, a geographical location must be entered. The calculations are very precise, with a deviation less than minute and a half.]])
|
||||||
|
function AutoWarmth:getSubMenuItems()
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
text = Device:hasNaturalLight() and _("About auto warmth and night mode")
|
||||||
|
or _("About auto night mode"),
|
||||||
|
callback = function()
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = about_text,
|
||||||
|
width = math.floor(Screen:getWidth() * 0.9),
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
separator = true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Expert mode"),
|
||||||
|
checked_func = function()
|
||||||
|
return not self.easy_mode
|
||||||
|
end,
|
||||||
|
help_text = _("In the expert mode, different types of twilight can be used in addition to civil twilight."),
|
||||||
|
callback = function(touchmenu_instance)
|
||||||
|
self.easy_mode = not self.easy_mode
|
||||||
|
G_reader_settings:saveSetting("autowarmth_easy_mode", self.easy_mode)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
touchmenu_instance.item_table = self:getSubMenuItems()
|
||||||
|
touchmenu_instance:updateItems()
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Activate"),
|
||||||
|
checked_func = function()
|
||||||
|
return self.activate ~= 0
|
||||||
|
end,
|
||||||
|
sub_item_table = self:getActivateMenu(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Location settings"),
|
||||||
|
enabled_func = function() return self.activate ~= activate_schedule end,
|
||||||
|
sub_item_table = self:getLocationMenu(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Schedule settings"),
|
||||||
|
enabled_func = function() return self.activate ~= activate_sun end,
|
||||||
|
sub_item_table = self:getScheduleMenu(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = Device:hasNaturalLight() and _("Warmth and night mode settings")
|
||||||
|
or _("Night mode settings"),
|
||||||
|
sub_item_table = self:getWarmthMenu(),
|
||||||
|
separator = true,
|
||||||
|
},
|
||||||
|
self:getTimesMenu(_("Active parameters")),
|
||||||
|
self:getTimesMenu(_("Information about the sun in"), true, activate_sun),
|
||||||
|
self:getTimesMenu(_("Information about the schedule"), false, activate_schedule),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:getActivateMenu()
|
||||||
|
local function getActivateMenuEntry(text, activator)
|
||||||
|
return {
|
||||||
|
text = text,
|
||||||
|
checked_func = function() return self.activate == activator end,
|
||||||
|
callback = function()
|
||||||
|
if self.activate ~= activator then
|
||||||
|
self.activate = activator
|
||||||
|
else
|
||||||
|
self.activate = 0
|
||||||
|
end
|
||||||
|
G_reader_settings:saveSetting("autowarmth_activate", self.activate)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
getActivateMenuEntry( _("Sun position"), activate_sun),
|
||||||
|
getActivateMenuEntry( _("Time schedule"), activate_schedule),
|
||||||
|
getActivateMenuEntry( _("Whatever is closer to noon"), activate_closer_noon),
|
||||||
|
getActivateMenuEntry( _("Whatever is closer to midnight"), activate_closer_midnight),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:getLocationMenu()
|
||||||
|
return {{
|
||||||
|
text_func = function()
|
||||||
|
if self.location ~= "" then
|
||||||
|
return T(_("Location: %1"), self.location)
|
||||||
|
else
|
||||||
|
return _("Location")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
callback = function(touchmenu_instance)
|
||||||
|
local location_name_dialog
|
||||||
|
location_name_dialog = InputDialog:new{
|
||||||
|
title = _("Location name"),
|
||||||
|
input = self.location,
|
||||||
|
buttons = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
text = _("Cancel"),
|
||||||
|
callback = function()
|
||||||
|
UIManager:close(location_name_dialog)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("OK"),
|
||||||
|
callback = function()
|
||||||
|
self.location = location_name_dialog:getInputText()
|
||||||
|
G_reader_settings:saveSetting("autowarmth_location",
|
||||||
|
self.location)
|
||||||
|
UIManager:close(location_name_dialog)
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
UIManager:show(location_name_dialog)
|
||||||
|
location_name_dialog:onShowKeyboard()
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text_func = function()
|
||||||
|
return T(_("Coordinates: (%1, %2)"), self.latitude, self.longitude)
|
||||||
|
end,
|
||||||
|
callback = function(touchmenu_instance)
|
||||||
|
local location_widget = DoubleSpinWidget:new{
|
||||||
|
title_text = _("Set location"),
|
||||||
|
info_text = _("Enter decimal degrees, northern hemisphere and eastern length are '+'."),
|
||||||
|
left_text = _("Latitude"),
|
||||||
|
left_value = self.latitude,
|
||||||
|
left_default = 0,
|
||||||
|
left_min = -90,
|
||||||
|
left_max = 90,
|
||||||
|
left_step = 0.1,
|
||||||
|
precision = "%0.2f",
|
||||||
|
left_hold_step = 5,
|
||||||
|
right_text = _("Longitude"),
|
||||||
|
right_value = self.longitude,
|
||||||
|
right_default = 0,
|
||||||
|
right_min = -180,
|
||||||
|
right_max = 180,
|
||||||
|
right_step = 0.1,
|
||||||
|
right_hold_step = 5,
|
||||||
|
callback = function(lat, long)
|
||||||
|
self.latitude = lat
|
||||||
|
self.longitude = long
|
||||||
|
self.timezone = self:getTimezoneOffset() -- use timezone of device
|
||||||
|
G_reader_settings:saveSetting("autowarmth_latitude", self.latitude)
|
||||||
|
G_reader_settings:saveSetting("autowarmth_longitude", self.longitude)
|
||||||
|
G_reader_settings:saveSetting("autowarmth_timezone", self.timezone)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
UIManager:show(location_widget)
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text_func = function()
|
||||||
|
return T(_("Altitude: %1m"), self.altitude)
|
||||||
|
end,
|
||||||
|
callback = function(touchmenu_instance)
|
||||||
|
UIManager:show(SpinWidget:new{
|
||||||
|
title_text = _("Altitude"),
|
||||||
|
value = self.altitude,
|
||||||
|
value_min = -100,
|
||||||
|
value_max = 15000, -- intercontinental flight
|
||||||
|
wrap = false,
|
||||||
|
value_step = 10,
|
||||||
|
value_hold_step = 100,
|
||||||
|
ok_text = _("Set"),
|
||||||
|
callback = function(spin)
|
||||||
|
self.altitude = spin.value
|
||||||
|
G_reader_settings:saveSetting("autowarmth_altitude", self.altitude)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
extra_text = _("Default"),
|
||||||
|
extra_callback = function()
|
||||||
|
self.altitude = 200
|
||||||
|
G_reader_settings:saveSetting("autowarmth_altitude", self.altitude)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:getScheduleMenu()
|
||||||
|
local function store_times(touchmenu_instance, new_time, num)
|
||||||
|
self.scheduler_times[num] = new_time
|
||||||
|
if num == 1 then
|
||||||
|
if new_time then
|
||||||
|
self.scheduler_times[midnight_index]
|
||||||
|
= new_time + 24 -- next day
|
||||||
|
else
|
||||||
|
self.scheduler_times[midnight_index] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
G_reader_settings:saveSetting("autowarmth_scheduler_times",
|
||||||
|
self.scheduler_times)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end
|
||||||
|
-- mode == nil ... show alway
|
||||||
|
-- == true ... easy mode
|
||||||
|
-- == false ... expert mode
|
||||||
|
local function getScheduleMenuEntry(text, num, mode)
|
||||||
|
return {
|
||||||
|
mode = mode,
|
||||||
|
text_func = function()
|
||||||
|
return T(_"%1: %2", text,
|
||||||
|
self:hoursToClock(self.scheduler_times[num]))
|
||||||
|
end,
|
||||||
|
checked_func = function()
|
||||||
|
return self.scheduler_times[num] ~= nil
|
||||||
|
end,
|
||||||
|
callback = function(touchmenu_instance)
|
||||||
|
local hh = 12
|
||||||
|
local mm = 0
|
||||||
|
if self.scheduler_times[num] then
|
||||||
|
hh = math.floor(self.scheduler_times[num])
|
||||||
|
mm = math.floor(frac(self.scheduler_times[num]) * 60 + 0.5)
|
||||||
|
end
|
||||||
|
UIManager:show(DoubleSpinWidget:new{
|
||||||
|
title_text = _("Set time"),
|
||||||
|
left_text = _("HH"),
|
||||||
|
left_value = hh,
|
||||||
|
left_default = 0,
|
||||||
|
left_min = 0,
|
||||||
|
left_max = 23,
|
||||||
|
left_step = 1,
|
||||||
|
left_hold_step = 3,
|
||||||
|
left_wrap = true,
|
||||||
|
right_text = _("MM"),
|
||||||
|
right_value = mm,
|
||||||
|
right_default = 0,
|
||||||
|
right_min = 0,
|
||||||
|
right_max = 59,
|
||||||
|
right_step = 1,
|
||||||
|
right_hold_step = 5,
|
||||||
|
right_wrap = true,
|
||||||
|
callback = function(left, right)
|
||||||
|
local new_time = left + right / 60
|
||||||
|
local function get_valid_time(n, dir)
|
||||||
|
for i = n+dir, dir > 0 and midnight_index or 1, dir do
|
||||||
|
if self.scheduler_times[i] then
|
||||||
|
return self.scheduler_times[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return dir > 0 and 0 or 26
|
||||||
|
end
|
||||||
|
if num > 1 and new_time < get_valid_time(num, -1) then
|
||||||
|
UIManager:show(ConfirmBox:new{
|
||||||
|
text = _("This time is before the previous time.\nAdjust the previous time?"),
|
||||||
|
ok_callback = function()
|
||||||
|
for i = num-1, 1, -1 do
|
||||||
|
if self.scheduler_times[i] then
|
||||||
|
if new_time < self.scheduler_times[i] then
|
||||||
|
self.scheduler_times[i] = new_time
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
store_times(touchmenu_instance, new_time, num)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
elseif num < 10 and new_time > get_valid_time(num, 1) then
|
||||||
|
UIManager:show(ConfirmBox:new{
|
||||||
|
text = _("This time is after the subsequent time.\nAdjust the subsequent time?"),
|
||||||
|
ok_callback = function()
|
||||||
|
for i = num + 1, midnight_index - 1 do
|
||||||
|
if self.scheduler_times[i] then
|
||||||
|
if new_time > self.scheduler_times[i] then
|
||||||
|
self.scheduler_times[i] = new_time
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
store_times(touchmenu_instance, new_time, num)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
store_times(touchmenu_instance, new_time, num)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
extra_text = _("Invalidate"),
|
||||||
|
extra_callback = function()
|
||||||
|
store_times(touchmenu_instance, nil, num)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local retval = {
|
||||||
|
getScheduleMenuEntry(_("Solar midnight"), 1, false ),
|
||||||
|
getScheduleMenuEntry(_("Astronomical dawn"), 2, false),
|
||||||
|
getScheduleMenuEntry(_("Nautical dawn"), 3, false),
|
||||||
|
getScheduleMenuEntry(_("Civil dawn"), 4),
|
||||||
|
getScheduleMenuEntry(_("Sunrise"), 5),
|
||||||
|
getScheduleMenuEntry(_("Solar noon"), 6, false),
|
||||||
|
getScheduleMenuEntry(_("Sunset"), 7),
|
||||||
|
getScheduleMenuEntry(_("Civil dusk"), 8),
|
||||||
|
getScheduleMenuEntry(_("Nautical dusk"), 9, false),
|
||||||
|
getScheduleMenuEntry(_("Astronomical dusk"), 10, false),
|
||||||
|
}
|
||||||
|
|
||||||
|
return tidy_menu(retval, self.easy_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:getWarmthMenu()
|
||||||
|
-- mode == nil ... show alway
|
||||||
|
-- == true ... easy mode
|
||||||
|
-- == false ... expert mode
|
||||||
|
local function getWarmthMenuEntry(text, num, mode)
|
||||||
|
return {
|
||||||
|
mode = mode,
|
||||||
|
text_func = function()
|
||||||
|
if Device:hasNaturalLight() then
|
||||||
|
if self.warmth[num] <= 100 then
|
||||||
|
return T(_("%1: %2%"), text, self.warmth[num])
|
||||||
|
else
|
||||||
|
return T(_("%1: 100% + ☾"), text)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if self.warmth[num] <= 100 then
|
||||||
|
return T(_("%1: ☼"), text)
|
||||||
|
else
|
||||||
|
return T(_("%1: ☾"), text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
callback = function(touchmenu_instance)
|
||||||
|
if Device:hasNaturalLight() then
|
||||||
|
UIManager:show(SpinWidget:new{
|
||||||
|
title_text = text,
|
||||||
|
value = self.warmth[num],
|
||||||
|
value_min = 0,
|
||||||
|
value_max = 100,
|
||||||
|
wrap = false,
|
||||||
|
value_step = math.floor(100 / device_max_warmth),
|
||||||
|
value_hold_step = 10,
|
||||||
|
ok_text = _("Set"),
|
||||||
|
callback = function(spin)
|
||||||
|
self.warmth[num] = spin.value
|
||||||
|
self.warmth[#self.warmth - num + 1] = spin.value
|
||||||
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
extra_text = _("Use night mode"),
|
||||||
|
extra_callback = function()
|
||||||
|
self.warmth[num] = 110
|
||||||
|
self.warmth[#self.warmth - num + 1] = 110
|
||||||
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
UIManager:show(ConfirmBox:new{
|
||||||
|
text = _("Nightmode"),
|
||||||
|
ok_text = _("Set"),
|
||||||
|
ok_callback = function()
|
||||||
|
self.warmth[num] = 110
|
||||||
|
self.warmth[#self.warmth - num + 1] = 110
|
||||||
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
cancel_text = _("Unset"),
|
||||||
|
cancel_callback = function()
|
||||||
|
self.warmth[num] = 0
|
||||||
|
self.warmth[#self.warmth - num + 1] = 0
|
||||||
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
||||||
|
self:scheduleMidnightUpdate()
|
||||||
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||||
|
end,
|
||||||
|
other_buttons = {{
|
||||||
|
{
|
||||||
|
text = _("Cancel"),
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
keep_menu_open = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local retval = {
|
||||||
|
{
|
||||||
|
text = Device:hasNaturalLight() and _("Set warmth and night mode for:")
|
||||||
|
or _("Set night mode for:"),
|
||||||
|
enabled_func = function() return false end,
|
||||||
|
},
|
||||||
|
getWarmthMenuEntry(_("Solar noon"), 6, false),
|
||||||
|
getWarmthMenuEntry(_("Daytime"), 5),
|
||||||
|
getWarmthMenuEntry(_("Darkest time of civil dawn"), 4, false),
|
||||||
|
getWarmthMenuEntry(_("Darkest time of civil twilight"), 4, true),
|
||||||
|
getWarmthMenuEntry(_("Darkest time of nautical dawn"), 3, false),
|
||||||
|
getWarmthMenuEntry(_("Darkest time of astronomical dawn"), 2, false),
|
||||||
|
getWarmthMenuEntry(_("Midnight"), 1, false),
|
||||||
|
}
|
||||||
|
|
||||||
|
return tidy_menu(retval, self.easy_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- title
|
||||||
|
-- location: add a location string
|
||||||
|
-- activator: nil .. current_times,
|
||||||
|
-- activate_sun .. sun times
|
||||||
|
-- activate_schedule .. scheduler times
|
||||||
|
-- request_easy: true if easy_mode should be used
|
||||||
|
function AutoWarmth:showTimesInfo(title, location, activator, request_easy)
|
||||||
|
local times
|
||||||
|
if not activator then
|
||||||
|
times = self.current_times
|
||||||
|
elseif activator == activate_sun then
|
||||||
|
times = SunTime.times
|
||||||
|
elseif activator == activate_schedule then
|
||||||
|
times = self.scheduler_times
|
||||||
|
end
|
||||||
|
|
||||||
|
-- text to show
|
||||||
|
-- t .. times
|
||||||
|
-- num .. index in times
|
||||||
|
local function info_line(text, t, num, easy)
|
||||||
|
local retval = text .. self:hoursToClock(t[num])
|
||||||
|
if easy then
|
||||||
|
if t[num] and self.current_times[num] and self.current_times[num] ~= t[num] then
|
||||||
|
return text .. "\n"
|
||||||
|
else
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not t[num] then -- entry deactivated
|
||||||
|
return retval .. "\n"
|
||||||
|
elseif Device:hasNaturalLight() then
|
||||||
|
if self.current_times[num] == t[num] then
|
||||||
|
if self.warmth[num] <= 100 then
|
||||||
|
return retval .. " (💡" .. self.warmth[num] .."%)\n"
|
||||||
|
else
|
||||||
|
return retval .. " (💡100% + ☾)\n"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return retval .. "\n"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if self.current_times[num] == t[num] then
|
||||||
|
if self.warmth[num] <= 100 then
|
||||||
|
return retval .. " (☼)\n"
|
||||||
|
else
|
||||||
|
return retval .. " (☾)\n"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return retval .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local location_string = ""
|
||||||
|
if location then
|
||||||
|
location_string = " " .. self:getLocationString()
|
||||||
|
end
|
||||||
|
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
face = Font:getFace("scfont"),
|
||||||
|
width = math.floor(Screen:getWidth() * (self.easy_mode and 0.75 or 0.90)),
|
||||||
|
text = title .. location_string .. ":\n\n" ..
|
||||||
|
info_line(_("Solar midnight: "), times, 1, request_easy) ..
|
||||||
|
_(" Dawn\n") ..
|
||||||
|
info_line(_(" Astronomic: "), times, 2, request_easy) ..
|
||||||
|
info_line(_(" Nautical: "), times, 3, request_easy)..
|
||||||
|
info_line(_(" Civil: "), times, 4) ..
|
||||||
|
_(" Dawn\n") ..
|
||||||
|
info_line(_("Sunrise: "), times, 5) ..
|
||||||
|
info_line(_("\nSolar noon: "), times, 6, request_easy) ..
|
||||||
|
|
||||||
|
info_line(_("\nSunset: "), times, 7) ..
|
||||||
|
_(" Dusk\n") ..
|
||||||
|
info_line(_(" Civil: "), times, 8) ..
|
||||||
|
info_line(_(" Nautical: "), times, 9, request_easy) ..
|
||||||
|
info_line(_(" Astronomic: "), times, 10, request_easy) ..
|
||||||
|
_(" Dusk\n") ..
|
||||||
|
info_line(_("Solar midnight: "), times, midnight_index, request_easy)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- title
|
||||||
|
-- location: add a location string
|
||||||
|
-- activator: nil .. current_times,
|
||||||
|
-- activate_sun .. sun times
|
||||||
|
-- activate_schedule .. scheduler times
|
||||||
|
function AutoWarmth:getTimesMenu(title, location, activator)
|
||||||
|
return {
|
||||||
|
text_func = function()
|
||||||
|
if location then
|
||||||
|
return title .. " " .. self:getLocationString()
|
||||||
|
end
|
||||||
|
return title
|
||||||
|
end,
|
||||||
|
callback = function()
|
||||||
|
self:showTimesInfo(title, location, activator, self.easy_mode)
|
||||||
|
end,
|
||||||
|
keep_menu_open = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function AutoWarmth:getLocationString()
|
||||||
|
if self.location ~= "" then
|
||||||
|
return self.location
|
||||||
|
else
|
||||||
|
return "(" .. self.latitude .. "," .. self.longitude .. ")"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return AutoWarmth
|
@ -0,0 +1,310 @@
|
|||||||
|
|
||||||
|
-- usage
|
||||||
|
-- SunTime:setPosition()
|
||||||
|
-- SunTime:setSimple() or SunTime:setAdvanced()
|
||||||
|
-- SunTime:setDate()
|
||||||
|
-- SunTime:calculate(height, hour) height==Rad(0°)-> Midday; hour=6 or 18 for rise or set
|
||||||
|
-- SunTime:calculateTimes()
|
||||||
|
-- use values
|
||||||
|
|
||||||
|
-- math abbrevations
|
||||||
|
local toRad = math.pi/180
|
||||||
|
local toDeg = 1/toRad
|
||||||
|
|
||||||
|
local floor = math.floor
|
||||||
|
local sin = math.sin
|
||||||
|
local cos = math.cos
|
||||||
|
local tan = math.tan
|
||||||
|
local asin = math.asin
|
||||||
|
local acos = math.acos
|
||||||
|
local atan = math.atan
|
||||||
|
|
||||||
|
local function Rad(x)
|
||||||
|
return x*toRad
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
local SunTime = {}
|
||||||
|
|
||||||
|
SunTime.astronomic = Rad(-18)
|
||||||
|
SunTime.nautic = Rad(-12)
|
||||||
|
SunTime.civil = Rad(-6)
|
||||||
|
-- SunTime.eod = Rad(-49/60) -- approx. end of day
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
-- simple 'Equation of time' good for dates between 2008-2027
|
||||||
|
-- errors for latitude 20° are within 1min
|
||||||
|
-- 47° are within 1min 30sec
|
||||||
|
-- 65° are within 5min
|
||||||
|
-- https://www.astronomie.info/zeitgleichung/#Auf-_und_Untergang (German)
|
||||||
|
function SunTime:getZglSimple()
|
||||||
|
local T = self.date.yday
|
||||||
|
return -0.171 * sin(0.0337 * T + 0.465) - 0.1299 * sin(0.01787 * T - 0.168)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- more advanced 'Equation of time' goot for dates between 1800-2200
|
||||||
|
-- errors are better than with the simple method
|
||||||
|
-- https://de.wikipedia.org/wiki/Zeitgleichung (German) and
|
||||||
|
-- more infos on http://www.hlmths.de/Scilab/Zeitgleichung.pdf (German)
|
||||||
|
function SunTime:getZglAdvanced()
|
||||||
|
local e = self.num_ex
|
||||||
|
local e2 = e*e
|
||||||
|
local e3 = e2*e
|
||||||
|
local e4 = e3*e
|
||||||
|
local e5 = e4*e
|
||||||
|
|
||||||
|
local M = self.M
|
||||||
|
-- https://de.wikibooks.org/wiki/Astronomische_Berechnungen_f%C3%BCr_Amateure/_Himmelsmechanik/_Sonne
|
||||||
|
local C = (2*e - e3/4 + 5/96*e5) * sin(M)
|
||||||
|
+ (5/4*e2 + 11/24*e4) * sin(2*M)
|
||||||
|
+ (13/12*e3 - 43/64*e5) * sin(3*M)
|
||||||
|
+ 103/96*e4 * sin(4*M)
|
||||||
|
+ 1097/960*e5 * sin(5*M) -- rad
|
||||||
|
|
||||||
|
local lamb = self.L + C
|
||||||
|
local tanL = tan(self.L)
|
||||||
|
local tanLamb = tan(lamb)
|
||||||
|
local cosEps = cos(self.epsilon)
|
||||||
|
|
||||||
|
local zgl = atan( (tanL - tanLamb*cosEps) / (1 + tanL*tanLamb*cosEps) ) --rad
|
||||||
|
return zgl*toDeg/15 -- to hours *4'/60
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set current date or year/month/day daylightsaving hh/mm/ss
|
||||||
|
-- if dst == nil use curent daylight saving of the system
|
||||||
|
function SunTime:setDate(year, month, day, dst, hour, min, sec)
|
||||||
|
self.oldDate = self.date
|
||||||
|
|
||||||
|
self.date = os.date("*t")
|
||||||
|
|
||||||
|
if year and month and day and hour and min and sec then
|
||||||
|
self.date.year = year
|
||||||
|
self.date.month = month
|
||||||
|
self.date.day = day
|
||||||
|
local feb = 28
|
||||||
|
if year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0) then
|
||||||
|
feb = 29
|
||||||
|
end
|
||||||
|
local days_in_month = {31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||||
|
self.date.yday = day
|
||||||
|
for i = 1, month-1 do
|
||||||
|
self.date.yday = self.date.yday + days_in_month[i]
|
||||||
|
end
|
||||||
|
self.date.hour = hour or 12
|
||||||
|
self.date.min = min or 0
|
||||||
|
self.date.sec = sec or 0
|
||||||
|
if dst ~= nil then
|
||||||
|
self.date.isdst = dst
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- use cached results
|
||||||
|
if self.olddate and self.oldDate.day == self.date.day and
|
||||||
|
self.oldDate.month == self.date.month and
|
||||||
|
self.oldDate.year == self.date.year and
|
||||||
|
self.oldDate.isdst == self.date.isdst then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self:initVars()
|
||||||
|
|
||||||
|
if not self.getZgl then
|
||||||
|
self.getZgl = self.getZglAdvanced
|
||||||
|
end
|
||||||
|
|
||||||
|
self.zgl = self:getZgl()
|
||||||
|
end
|
||||||
|
|
||||||
|
function SunTime:setPosition(name, latitude, longitude, time_zone, altitude)
|
||||||
|
altitude = altitude or 200
|
||||||
|
|
||||||
|
self.oldDate = nil --invalidate cache
|
||||||
|
self.pos = {name, latitude = latitude, longitude = longitude, altitude = altitude}
|
||||||
|
self.time_zone = time_zone
|
||||||
|
self.refract = Rad(33/60 * .5 ^ (altitude / 5500))
|
||||||
|
end
|
||||||
|
|
||||||
|
function SunTime:setSimple()
|
||||||
|
self.getZgl = self.getZglSimple
|
||||||
|
end
|
||||||
|
function SunTime:setAdvanced()
|
||||||
|
self.getZgl = self.getZglAdvanced
|
||||||
|
end
|
||||||
|
|
||||||
|
function SunTime:daysSince2000()
|
||||||
|
local delta = self.date.year - 2000
|
||||||
|
local leap = floor(delta/4)
|
||||||
|
local days_since_2000 = delta * 365 + leap + self.date.yday -- WMO No.8
|
||||||
|
return days_since_2000
|
||||||
|
end
|
||||||
|
|
||||||
|
-- more accurate parameters of earth orbit from
|
||||||
|
-- Title: Numerical expressions for precession formulae and mean elements for the Moon and the planets
|
||||||
|
-- Authors: Simon, J. L., Bretagnon, P., Chapront, J., Chapront-Touze, M., Francou, G., & Laskar, J., ,
|
||||||
|
-- Journal: Astronomy and Astrophysics (ISSN 0004-6361), vol. 282, no. 2, p. 663-683
|
||||||
|
-- Bibliographic Code: 1994A&A...282..663S
|
||||||
|
function SunTime:initVars()
|
||||||
|
self.days_since_2000 = self:daysSince2000()
|
||||||
|
local T = self.days_since_2000/36525
|
||||||
|
-- self.num_ex = 0.016709 - 0.000042 * T
|
||||||
|
-- self.num_ex = 0.0167086342 - 0.000042 * T
|
||||||
|
-- see wikipedia: https://de.wikipedia.org/wiki/Erdbahn-> Meeus
|
||||||
|
self.num_ex = 0.0167086342 + T*(-0.0004203654e-1
|
||||||
|
+ T*(-0.0000126734e-2 + T*( 0.0000001444e-3
|
||||||
|
+ T*(-0.0000000002e-4 + T* 0.0000000003e-5))))
|
||||||
|
|
||||||
|
-- self.epsilon = (23 + 26/60 + 21/3600 - 46.82/3600 * T) * toRad
|
||||||
|
-- see wikipedia: https://de.wikipedia.org/wiki/Erdbahn-> Meeus
|
||||||
|
local epsilon = 23 + 26/60 + (21.412 + T*(-46.80927
|
||||||
|
+ T*(-0.000152 + T*(0.00019989
|
||||||
|
+ T*(-0.00000051 - T*0.00000025)))))/3600 --°
|
||||||
|
self.epsilon = epsilon * toRad
|
||||||
|
|
||||||
|
-- local L = (280.4656 + 36000.7690 * T ) --°
|
||||||
|
-- see Numerical expressions for precession formulae ...
|
||||||
|
-- shift from time to Equinox as data is given for JD2000.0, but date is in days from 20000101
|
||||||
|
local nT = T * 1.0000388062
|
||||||
|
--mean longitude
|
||||||
|
local L = 100.46645683 + (nT*(1295977422.83429E-1
|
||||||
|
+ nT*(-2.04411E-2 - nT* 0.00523E-3)))/3600--°
|
||||||
|
self.L = (L - floor(L/360)*360) * toRad
|
||||||
|
|
||||||
|
-- wikipedia: https://de.wikipedia.org/wiki/Erdbahn-> Meeus
|
||||||
|
local omega = 102.93734808 + nT*(17.194598028e-1
|
||||||
|
+ nT*( 0.045688325e-2 + nT*(-0.000017680e-3
|
||||||
|
+ nT*(-0.000033583e-4 + nT*( 0.000000828e-5
|
||||||
|
+ nT* 0.000000056e-6))))) --°
|
||||||
|
|
||||||
|
-- Mittlere Länage
|
||||||
|
local M = L - omega
|
||||||
|
self.M = (M - floor(M/360)*360) * toRad
|
||||||
|
|
||||||
|
-- Deklination nach astronomie.info
|
||||||
|
-- local decl = 0.4095 * sin(0.016906 * (self.date.yday - 80.086))
|
||||||
|
--Deklination nach Brodbeck (2001)
|
||||||
|
-- local decl = 0.40954 * sin(0.0172 * (self.date.yday - 79.349740))
|
||||||
|
|
||||||
|
--Deklination nach John Kalisch (derived from WMO-No.8)
|
||||||
|
--local x = (36000/36525 * (self.date.yday+hour/24) - 2.72)*toRad
|
||||||
|
--local decl = asin(0.397748 * sin(x + (1.915*sin(x) - 77.51)*toRad))
|
||||||
|
|
||||||
|
-- Deklination WMO-No.8 page I-7-37
|
||||||
|
--local T = self.days_since_2000 + hour/24
|
||||||
|
--local L = 280.460 + 0.9856474 * T -- self.M
|
||||||
|
--L = (L - floor(L/360)*360) * toRad
|
||||||
|
--local g = 357.528 + 0.9856003 * T
|
||||||
|
--g = (g - floor(g/360)*360) * toRad
|
||||||
|
--local l = L + (1.915 * sin (g) + 0.020 * sin (2*g))*toRad
|
||||||
|
--local ep = self.epsilon
|
||||||
|
-- -- sin(decl) = sin(ep)*sin(l)
|
||||||
|
--self.decl = asin(sin(ep)*sin(l))
|
||||||
|
|
||||||
|
-- Deklination WMO-No.8 page I-7-37
|
||||||
|
local l = self.L + math.pi + (1.915 * sin (self.M) + 0.020 * sin (2*self.M))*toRad
|
||||||
|
self.decl = asin(sin(self.epsilon)*sin(l))
|
||||||
|
|
||||||
|
-- Nutation see https://de.wikipedia.org/wiki/Nutation_(Astronomie)
|
||||||
|
local A = { 2.18243920 - 33.7570460 * T,
|
||||||
|
-2.77624462 + 1256.66393 * T,
|
||||||
|
7.62068856 + 16799.4182 * T,
|
||||||
|
4.36487839 - 67.140919 * T}
|
||||||
|
local B = {92025e-4 + 8.9e-4 * T,
|
||||||
|
5736e-4 - 3.1e-4 * T,
|
||||||
|
977e-4 - 0.5e-4 * T,
|
||||||
|
-895e-4 + 0.5e-4 * T}
|
||||||
|
local delta_epsilon = 0
|
||||||
|
for i = 1, #A do
|
||||||
|
delta_epsilon = delta_epsilon + B[i]*cos(A[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add nutation to declination
|
||||||
|
self.decl = self.decl + delta_epsilon/3600
|
||||||
|
|
||||||
|
-- https://de.wikipedia.org/wiki/Kepler-Gleichung#Wahre_Anomalie
|
||||||
|
self.E = self.M + self.num_ex * sin(self.M) + self.num_ex^2 / 2 * sin(2*self.M)
|
||||||
|
self.a = 149598022.96E3 -- große Halbaches in m
|
||||||
|
self.r = self.a * (1 - self.num_ex * cos(self.E))
|
||||||
|
-- self.eod = -atan(6.96342e8/self.r) - Rad(33.3/60) -- without nutation
|
||||||
|
self.eod = -atan(6.96342e8/self.r) - self.refract -- with nutation
|
||||||
|
-- ^--sun radius ^- astronomical refraction (500m altitude)
|
||||||
|
end
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
function SunTime:getTimeDiff(height)
|
||||||
|
local val = (sin(height) - sin(self.pos.latitude)*sin(self.decl))
|
||||||
|
/ (cos(self.pos.latitude)*cos(self.decl))
|
||||||
|
|
||||||
|
if math.abs(val) > 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return 12/math.pi * acos(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get time for a certain height
|
||||||
|
-- set hour to 6 for rise or 18 for set
|
||||||
|
-- result rise or set time
|
||||||
|
-- nil sun does not reach the height
|
||||||
|
function SunTime:calculateTime(height, hour)
|
||||||
|
hour = hour or 12
|
||||||
|
local dst = self.date.isdst and 1 or 0
|
||||||
|
local timeDiff = self:getTimeDiff(height, hour)
|
||||||
|
if not timeDiff then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local local_correction = self.time_zone - self.pos.longitude*12/math.pi + dst - self.zgl
|
||||||
|
if hour < 12 then
|
||||||
|
return 12 - timeDiff + local_correction
|
||||||
|
else
|
||||||
|
return 12 + timeDiff + local_correction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SunTime:calculateTimeIter(height, hour)
|
||||||
|
return self:calculateTime(height, hour)
|
||||||
|
end
|
||||||
|
|
||||||
|
function SunTime:calculateTimes()
|
||||||
|
self.rise = self:calculateTimeIter(self.eod, 6)
|
||||||
|
self.set = self:calculateTimeIter(self.eod, 18)
|
||||||
|
|
||||||
|
self.rise_civil = self:calculateTimeIter(self.civil, 6)
|
||||||
|
self.set_civil = self:calculateTimeIter(self.civil, 18)
|
||||||
|
self.rise_nautic = self:calculateTimeIter(self.nautic, 6)
|
||||||
|
self.set_nautic = self:calculateTimeIter(self.nautic, 18)
|
||||||
|
self.rise_astronomic = self:calculateTimeIter(self.astronomic, 6)
|
||||||
|
self.set_astronomic = self:calculateTimeIter(self.astronomic, 18)
|
||||||
|
|
||||||
|
self.noon = (self.rise + self.set) / 2
|
||||||
|
self.midnight = self.noon + 12
|
||||||
|
|
||||||
|
self.times = {}
|
||||||
|
self.times[1] = self.noon - 12
|
||||||
|
self.times[2] = self.rise_astronomic
|
||||||
|
self.times[3] = self.rise_nautic
|
||||||
|
self.times[4] = self.rise_civil
|
||||||
|
self.times[5] = self.rise
|
||||||
|
self.times[6] = self.noon
|
||||||
|
self.times[7] = self.set
|
||||||
|
self.times[8] = self.set_civil
|
||||||
|
self.times[9] = self.set_nautic
|
||||||
|
self.times[10] = self.set_astronomic
|
||||||
|
self.times[11] = self.noon + 12
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get time in seconds (either actual time in hours or date struct)
|
||||||
|
function SunTime:getTimeInSec(val)
|
||||||
|
if not val then
|
||||||
|
val = os.date("*t")
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(val) == "table" then
|
||||||
|
return val.hour*3600 + val.min*60 + val.sec
|
||||||
|
end
|
||||||
|
|
||||||
|
return val*3600
|
||||||
|
end
|
||||||
|
|
||||||
|
return SunTime
|
Loading…
Reference in New Issue