-- Eclipse Magic
-- Programmed exposure sequence for a solar eclipse
-- Copyright 2017 by Brian Greenberg, grnbrg@grnbrg.org.
-- Modified by G Abramson 2019-2020
-- Distributed under the GNU General Public License.
-- 1. Set TEST or SHOOT
-- 2. Set Contact times for test and shoot
-- 3. Set Exposure settings for all phases
-- 4. Check (set) camera time
-- 5. Run script from ML menu
require ("logger")
-- Variable definitons that have to go here. Ignore them.
c1 = {}
c2 = {}
c3 = {}
c4 = {}
-- *** TEST: SET 1. SHOOT: SET 0. ***********************************************
-- Test: beeps and logs, no shoot, time starts counting with script run.
-- Shoot: actually shoot, log, timed with camera clock
TestBeepNoShutter = 0
-- *** CONTACTS *****************************************************************
-- Set the 4 contact times here. Time zone is irrelevant, as long as your camera and these
-- times are the same. Make sure the times are correct for your location, and that your camera
-- is accurately set to GPS or NTP time.
--
if ( TestBeepNoShutter == 0 )
then
-- Piedra del Įguila (playas) 70 00 31.91 W 40 03 16.16 S
c1.hr = 11; c1.min = 45; c1.sec = 40;
c2.hr = 13; c2.min = 08; c2.sec = 05;
-- MAX 13 09 05
c3.hr = 13; c3.min = 10; c3.sec = 02;
c4.hr = 14; c4.min = 35; c4.sec = 50;
else
-- Testing:
c1.hr = 00; c1.min = 00; c1.sec = 30;
c2.hr = 00; c2.min = 08; c2.sec = 07;
c3.hr = 00; c3.min = 10; c3.sec = 01;
c4.hr = 00; c4.min = 15; c4.sec = 30;
end
--
-- Send log information to file ECLIPSE.LOG at the
-- top directory of the camera card. Useful for testing.
--
LogToFile = 1
LoggingFile = nil
-- APERTURE
-- Set an aperture value. The script assumes that the aperture stays constant throughout the
-- eclipse. The camera will (try to) set this aperture (f-number) at the beginning of the script.
-- Useful, as it is easy to forget this, if you are shooting with a regular camera lens.
-- If the script and camera are being used with a fixed aperture lens (or telescope), then
-- set the "SetAperture" to 0.
SetAperture = 1
Aperture = 8 -- Aperture for totality
C23Aperture = 16 -- Smaller aperture for nicer ring and beads
PAperture = 11 -- Aperture for partial (filtered)
--
-- If you shoot with LiveView active, you will reduce the amount of mirror slap, giving
-- less vibration. But, some cameras (Such as the 5DmkII.) have a limited range of shutter
-- speed when LV and Movie mode are enabled. This reduces the available speeds from 30 seconds
-- through 1/8000th of a second to 1/30th to 1/4000th of a second. Exceeding that range while
-- LV and Movie mode are enabled will crash the script.
--
-- Setting this variable to 1 will, before taking any images, check if LV is running, and if it
-- is, will tell you to turn it off. If you have disabled movie mode (a menu option on some cameras,
-- like the 5DmkII, and a hardware switch on others) you should set this to 0.
--
-- Test your camera. If the exposure speeds you want crash the script in LV, check how to disable
-- movie mode.
--
WarnLiveView = 0
--
-- If you're shooting in Live View (to reduce mirror slap) you'll probably still want to check
-- your focus occasionally. However, the status display of the script pretty much fills the screen,
-- making this difficult. Enabling HideConsole will display the console for the indicated number
-- of seconds both before and after the next shutter event, and turn the console off between. This
-- also gives you an idea of how much time you have before the next shutter event -- if the console
-- is hidden, you have at least ConsoleShowDelay seconds to mess around. If you turn this off, the
-- script will start with the console displayed, and you will have to manage it through the Magic
-- Lantern menu. If you turn it off, it stays off until you turn it on again.
--
HideConsole = 1
ConsoleShowDelay = 30
--
-- Even if you shoot with Live View with Silent Mode 1 (which forces the mirror to be
-- locked up, and eliminates the vibration from the shutter opening) there will be slight
-- vibrations introduced as the shutter closes. According to Jerry Lodriguss at
-- http://www.astropix.com/wp/2017/07/17/mirror-slap-and-shutter-shock/ these vibrations
-- are most prominent between 0.125s and 2s, and can be somewhat mitigated by a slight
-- delay before exposure, to allow the vibration to dampen a bit.
--
-- Enabling this option lets you set a range of shutter speeds to delay before, and
-- how long (in milliseconds) to pause.
--
DoShutterShockDelay = 1
SlowestDelayedShutter = 2
FastestDelayedShutter = (1/8)
ShutterShockDelayMS = 300 -- Value is in milliseconds!
-- ** EXPOSURE SETTINGS *****************************************************************
-- Partial phase settings.
--
PartialISO = 100
PartialShutterSpeed = (1/80) -- Filter Saracco OK
PartialMarginTime = 30 -- Number of seconds after C1 or C3 and before C2 or C4 to start exposures
PartialExposureCount = 72 -- Number of partial phase exposures before and after totality
PartialDoBkt = 1 -- Do you want to do exposure bracketing? 1 - yes, 0 - no
PartialBktStep = 0.666667 -- Number of f-stops in each step. Can 0.333333, 1, 2, etc
PartialBktCount = 1 -- How many brackets on each side of the neutral exposure?
-- ** BAILY's BEADS ****************************************************************
-- Do a fast burst of exposures at C2 and C3, to try to get Baily's beads and chromosphere.
-- You will need to know how many exposures your camera will buffer, and how long it takes the
-- buffer to fill. At "StartOffset" seconds before C2 or C3, the camera will take "BurstCount"
-- exposures, as fast as it can. You should adjust C23StartOffset so that burst of image straddles
-- the contact time. Note that, between the setting of the camera clock and the jitter in this
-- script, there will be some error in the timing. +/- half a second or more is possible.
--
C23BurstCount = 7 -- Note that most Canon DSLRs can't take more than 13-14 RAW images
-- in a burst before the buffer is full, and they slow to ~1 image/second.
C2BurstStartOffset = 4
C2BurstTime = 8
C3BurstStartOffset = 4
C3BurstTime = 8
C23BurstISO = 100
C23BurstShutterSpeed = (1/800)
-- ** DIAMOND RING ********************************************************************
-- Do a fast burst of exposures before C2 and after C3, to try to get the diamond ring.
--
-- Be careful setting the RingStartOffset, Count and Time. If the burst of images for the
-- pre-C2 rings runs longer than expected, it can cause the pre-C2 Baily's Beads exposures to
-- be skipped.
--
-- The post-C3 Rings exposures (if enabled) will run immediately after the post-C3 Baily's Beads
-- exposures.
--
DoRing = 1 -- Are we going to try for a burst for the diamond ring?
RingStartOffset = C2BurstStartOffset + 5 -- How long before C2/after C3 to start? Be careful that
-- this does not interfere with the Baily's burst!
RingBurstCount = 3 -- How many images?
RingBurstTime = 4 -- If we're doing a manual burst, how long should it last?
RingBurstISO = 400
RingBurstShutterSpeed = (1/60)
-- ** TOTALITY ***************************************************************************
-- During the time between C2 and C3, the script will run back and forth between the
-- "MinShutterSpeed" and "MaxShutterSpeed" as quickly as possible, with an extra 2 long exposures
-- at midpoint. "ExpStep" is the size of the f-stop variation, and can be set to 0.333333, 1, 2, etc.
-- Min, Max and PrefISO: Totality exposures will run (where possible) at PrefISO. However, there
-- will also be exposures at MinShutterSpeed from MinISO to PrefISO, and MaxShutterSpeed from
-- PrefISO to MaxISO.
--
TotalityMinISO = 100
TotalityMaxISO = 800
TotalityPrefISO = 200
TotalityMinShutterSpeed = (1/250) -- (MinShutterSpeed is the *fastest* speed to use.)
TotalityMaxShutterSpeed = 1 -- 1 sec OK (MaxShutterSpeed is the *slowest*, longest speed used.)
TotalityExpStep = 1
-- ** ASHEN LIGHT **********************************************************************
-- One of the more difficult exposures to capture is earthshine -- the surface of the moon,
-- illuminated by light reflected from the earth.
--
-- The best time to do this is at the point of maximum eclipse, where the sun is centred behind
-- the moon, as much as possible. Exposures here are kind of guesswork, and I have actually turned this
-- off by default.
--
DoMaxExposures = 1 -- Number of (possibly bracketed) exposures to take at max-eclipse.
MaxOffset = 3 -- How long before max eclipse to start these exposures. You'll have to test
-- or use math to determine this value. (Value is in seconds.)
NumMaxExposures = 1
DoMaxBrackets = 1 -- Brackets?
NumMaxBrackets = 1
MaxBracketStep = 1
MaxISO = 800
MaxShutterSpeed = 1
--
-- Optionally enable high speed burst mode
--
-- As originally written, the script used the camera.burst() function, to take a burst of
-- images as quickly as possible, as though the shutter button was held down. This had two issues:
-- The first being that the buffer in the camera fills after around 2-3 seconds when shooting
-- RAW, and then slows drastically, and the second being that once called, the camera will take
-- exposures until the requested number of images are captured, and if the buffer fills, this may
-- take considerably longer than expected.
--
-- I have (by default) replaced this function with a burst function that tries to take a given number
-- of images over a set period of time, each with a single shutter release call. This is much slower
-- (two or three frames per second, tops) but therefor fills the buffer more slowly, and allows the script
-- to abort the burst (and take fewer than the requested number of images) if the capture runs past the
-- specified time limit.
--
-- If you prefer the old high speed burst call, set this variable to 1.
--
UseBurst = 0
-- ** END SETTINGS **************************************************************************************
-- ** START CALCULATIONS *** BE CAREFUL BELOW THIS LINE ************************************************
--
-- Times are easiest to deal with in seconds. This would be painful if they crossed over midnight,
-- but late-night solar eclipses are rare.
--
c1_sec = c1.hr * 3600 + c1.min * 60 + c1.sec
c2_sec = c2.hr * 3600 + c2.min * 60 + c2.sec
c3_sec = c3.hr * 3600 + c3.min * 60 + c3.sec
c4_sec = c4.hr * 3600 + c4.min * 60 + c4.sec
max_sec = math.floor(c2_sec + ((c3_sec - c2_sec) / 2))
MaxOffset = MaxOffset * DoMaxExposures -- This is ugly, and shouldn't be here.
tick_offset = 0
TestStartTime = 0
--
-- Log to stdout and optionally to a file
--
function log (s, ...)
local str = string.format (s, ...)
str = str .. "\n"
if (LogToFile == 0 or LoggingFile == nil)
then
io.write (str)
else
LoggingFile:write (str)
end
return
end
--
-- Open log file
--
function log_start ()
if (LogToFile ~= 0)
then
local cur_time = dryos.date
-- Opening logger with long filename fails. Works with short name.
-- local filename = string.format("eclipse_%04d%02d%02d_%02d%02d%02d.log", cur_time.year, cur_time.month, cur_time.day, cur_time.hour, cur_time.min, cur_time.sec)
-- local filename = string.format("EM%02d%02d.log", cur_time.hour, cur_time.min)
local filename = string.format("eclipse.log")
print (string.format ("Open log file %s", filename))
LoggingFile = logger (filename)
else
print (string.format ("Logging not configured"))
end
end
--
-- Close log file
--
function log_stop ()
if (LogToFile ~= 0)
then
print (string.format ("Close log file"))
LoggingFile:close ()
end
end
--
-- Get the current time (in seconds) from the camera's clock.
--
function get_cur_secs ()
local cur_time = dryos.date
local cur_secs = (cur_time.hour * 3600 + cur_time.min * 60 + cur_time.sec)
if ( TestBeepNoShutter == 1 )
then
cur_secs = (cur_secs - TestStartTime) -- If we're testing, start the clock at
-- now, not actual time.
end
return cur_secs
end
--
-- Take a time variable expressed in seconds (which is what all times are
-- stored as) and convert it back to HH:MM:SS
--
function pretty_time (time_secs)
local text_time = ""
local hrs = 0
local mins = 0
local secs = 0
hrs = math.floor(time_secs / 3600)
mins = math.floor((time_secs - (hrs * 3600)) / 60)
secs = (time_secs - (hrs*3600) - (mins * 60))
text_time = string.format("%02d:%02d:%02d", hrs, mins, secs)
return text_time
end
--
-- Take a shutter speed expressed in (fractional) seconds and convert it to 1/x.
--
function pretty_shutter (shutter_speed)
local text_time = ""
if (shutter_speed >= 1.0)
then
text_time = tostring (shutter_speed)
else
text_time = string.format ("1/%s", tostring (1/shutter_speed))
end
return text_time
end
--
-- Hurry up and wait for the next important time to arrive.
--
-- Leave the console displayed for 60 seconds at the start and end of
-- a wait. Turn it off between, so that tracking can be done via live view, etc.
--
function wait_until (done_waiting)
local counter = get_cur_secs()
local next_sec = 0
local show_console = ConsoleShowDelay
local console_visible = 1
console.show()
log ("Waiting for %s in %d seconds.", pretty_time(done_waiting), done_waiting - counter)
repeat
task.yield (1000) -- Let the camera do other tasks for a second.
if ((HideConsole == 1) and (show_console > 0))
then
show_console = show_console -1
elseif ((HideConsole == 1) and (show_console == 0))
then
console.hide()
show_console = -1
console_visible = 0
end
if ((HideConsole == 1) and ((done_waiting - counter) < 30 ) and (console_visible == 0))
then
console.show()
console_visible = 1
end
counter = get_cur_secs()
until (counter >= (done_waiting - 1))
if ( counter < done_waiting)
then
-- Loop /should/ exit the second before we are done. But
-- It's possible that it could exit early in our target
-- second. If so, we don't want to wait around to
-- (done_waiting + 1) to exit.
next_sec = (1000 - ((dryos.ms_clock - tick_offset) % 1000))
msleep (next_sec) -- Hard sleep, don't let anything else have priority.
end
end
--
-- Set up the camera, and take a picture. Also deals with any requested bracketing.
--
function take_shot(iso, shutter_speed, dobkt, bktstep, bktcount)
local bktspeed = 0.0
if ((lv.enabled == true) and (WarnLiveView == 1))
then
print ("TURN LIVEVIEW OFF (OR DISABLE MOVIE MODE) AND PRESS A BUTTON!!!")
do_beep()
key.wait()
end
camera.iso.value = iso
if (dobkt == 0)
then -- Single exposure
camera.shutter.value = shutter_speed
log ("Click! Time: %s ISO: %s shutter: %s",
pretty_time(get_cur_secs()), tostring(camera.iso.value), pretty_shutter(camera.shutter.value))
if (DoShutterShockDelay == 1)
then
if ((shutter_speed >= FastestDelayedShutter) and (shutter_speed
<= SlowestDelayedShutter))
then
task.yield(ShutterShockDelayMS)
end
end
if (TestBeepNoShutter == 0)
then
camera.shoot(false)
task.yield(10) -- Exposures can take time. Give other stuff a chance to run.
else
beep(1,50)
task.yield (600 + camera.shutter.ms)
end
else -- Bracketing exposure
-- Loop through the requested number of exposure brackets.
for bktnum = bktcount,(-1 * bktcount),-1 do
bktspeed = shutter_speed * (2.0^(bktnum * bktstep))
camera.shutter.value = bktspeed
log ("Click! Time: %s ISO: %s shutter: %s",
pretty_time(get_cur_secs()), tostring(camera.iso.value), pretty_shutter(camera.shutter.value))
if (DoShutterShockDelay == 1)
then
if ((shutter_speed >= FastestDelayedShutter) and (shutter_speed
<= SlowestDelayedShutter))
then
task.yield(ShutterShockDelayMS)
end
end
if (TestBeepNoShutter == 0) then
camera.shoot(false)
task.yield(10) -- Give other stuff a chance to run
else
beep(1,50)
task.yield ((600 + camera.shutter.ms))
end
end
end
end
--
-- Burst is simpler than single shot, because no brackets. Set the camera and shoot.
--
-- I have tried replacing this with multiple calls to "camera.shoot()", but it is still considerably
-- slower than "camera.burst()", and can also crash the camera -- nothing permanent, but still
-- not something I want to put out.
--
function take_burst (count, iso, speed)
camera.shutter.value = speed
camera.iso.value = iso
if ((lv.enabled == true) and (WarnLiveView == 1))
then
print ("TURN LIVEVIEW OFF (OR DISABLE MOVIE MODE) AND PRESS A BUTTON!!!")
do_beep()
key.wait()
end
log ("Burst! Time: %s ISO: %s shutter: %s count: %d",
pretty_time(get_cur_secs()), tostring(camera.iso.value), pretty_shutter(camera.shutter.value), count)
if (TestBeepNoShutter == 0)
then
camera.burst(count)
task.yield(10)
else
beep(3,50)
task.yield (4000 + (count * camera.shutter.ms))
end
end
--
-- Take X pictures over Y seconds
--
-- The camera.burst() function is useful for taking exposures as fast as possible, but is
-- limited by the camera's buffer space. Depending on the body, burst mode will fill the buffer in
-- around 2 seconds. Even the slower cameras will fill the buffer with RAW images in 3-4 seconds,
-- which is too fast to reliably capture Baily's Beads. Switching to JPG would help, but must be
-- done manually. Not a good option for several reasons.
--
-- This function implements a manually controlled burst mode, which stretches out the exposure speed.
-- This spreads the time where the ~14 exposures that generally fit into the buffer out over a longer
-- time, and also gives the camera time to write to the card. Instead of 14 frames over 4 seconds,
-- (~4fps) then slowing to maybe 4 frames every 3 seconds (1.5fps), we might be able to sustain
-- 2fps for 10 seconds. Actual best framerate and duration will need testing for each camera and
-- memory card.
--
-- Timing is more important than number of exposures, so this function will exit at the end of the
-- specificed timespan, even if the required number of images have not been taken yet.
function take_timed_burst(count, timespan, iso, speed)
local start_time = dryos.ms_clock -- Millisecond clock time that we're starting.
local end_time = (dryos.ms_clock + (timespan * 1000)) -- Clock time (in ms) where that we're done.
local burst_interval = ((timespan * 1000) / count) -- Time between shutters, in milliseconds.
local time_now = start_time
local pause_time = 0
local exposure_num = 0
local last_time = 0
for exposure_num = 1, count, 1
do
last_time =time_now
time_now = dryos.ms_clock
take_shot (iso, speed, 0, 0, 0)
if (time_now > end_time)
then
return -- No time for another image.
else
pause_time = ((start_time + burst_interval * exposure_num) - (dryos.ms_clock + 75))
if (pause_time > 0) -- Pause for the next interval to pass, if we're not running late.
then
task.yield(pause_time)
end
end
end
end
--
-- Simple camera beep.
--
function do_beep()
beep (5,100)
end
--
-- Take the spaced exposures for the C1-C2 and C3-C4 periods. Take the margin times off either
-- end, split the time into the right intervals, and fire off take_picture()
--
function do_partial (start_phase, stop_phase, which_partial)
local image_time = 0
local image_interval = math.floor((stop_phase - start_phase) / (PartialExposureCount))
local exposure_count = 0
if (SetAperture == 1)
then
camera.aperture.value = PAperture -- Set aperture for partial phase shots
log ("Aperture %s for partial phase shots", PAperture)
end
-- In a series of (PartialCount + 1) images, totality is either
-- the first or last image. This arranges the timing so that there
-- will be a equidistance set of ((2 x PartialCount) + 1) exposures,
-- with totality properly centered.
if (which_partial == "Pre")
then
image_time = start_phase
else
image_time = start_phase + image_interval
end
if ( get_cur_secs() >= stop_phase ) -- Are we past this phase already?
then
log ("Skip %s Partial. Finished %d seconds ago.", which_partial, (get_cur_secs() - stop_phase))
return
end
repeat
log ("%s Partial: %d/%d Interval: %d s Remaining: %d",
which_partial, exposure_count + 1, PartialExposureCount, image_interval, stop_phase - get_cur_secs())
if (get_cur_secs() <= image_time)
then
-- Reminder to center sun
wait_until (image_time - 30)
print()
print("********************************************************")
log ("30 seconds to partial shot! Center!")
print("********************************************************")
print()
do_beep()
-- Shoot
wait_until(image_time)
take_shot (PartialISO, PartialShutterSpeed, PartialDoBkt, PartialBktStep, PartialBktCount)
end
image_time = image_time + image_interval
exposure_count = exposure_count + 1
-- Reminder at mid partial
if ( exposure_count == math.floor(PartialExposureCount/2)+1 )
then
print()
print("********************************************************")
log ("Mid partial phase. Check focus w. lunar limb!")
print("********************************************************")
print()
do_beep()
end
until (exposure_count >= PartialExposureCount)
end
--
-- Start the burst shot a little before C2, then start running through exposure settings, going from
-- short, fast exposures to slow, long exposures and then back to short, until just before the midpoint
-- of the eclipse. Take two long exposures at that point, for good measure.
--
function do_c2max()
local cur_shutter_speed = 0
local CurISO = 0
if ( get_cur_secs() >= max_sec ) -- Are we past this phase already?
then
log ("Skip C2->Max. Finished %d seconds ago.", (get_cur_secs() - max_sec))
return
end
if (get_cur_secs() <= (c2_sec - (math.max(RingStartOffset, C2BurstStartOffset) + 30)))
then
log ("Main C2->Max loop for %d seconds.", (c2_sec - (math.max(RingStartOffset, C2BurstStartOffset) + 30)))
wait_until (c2_sec - (math.max(RingStartOffset, C2BurstStartOffset) + 30))
print()
print("********************************************************")
log ("30 seconds to C2! Remove Filter! Center! ")
print("********************************************************")
print()
do_beep()
end
if (SetAperture == 1)
then
camera.aperture.value = C23Aperture -- Set aperture for C2 shots
log ("Aperture %s for C2 shots", C23Aperture)
end
if ((get_cur_secs() <= (c2_sec - RingStartOffset) ) and ( DoRing == 1)) -- Are we taking Diamond Ring shots?
then
wait_until (c2_sec - RingStartOffset)
if (UseBurst == 1)
then
take_burst (RingBurstCount, RingBurstISO, RingBurstShutterSpeed)
else
take_timed_burst (RingBurstCount, RingBurstTime, RingBurstISO, RingBurstShutterSpeed)
end
end
if ( get_cur_secs() < c2_sec ) -- Have we passed the burst for Baily's beads?
then
wait_until (c2_sec - C2BurstStartOffset)
if (UseBurst == 1)
then
take_burst (C23BurstCount, C23BurstISO, C23BurstShutterSpeed)
else
take_timed_burst (C23BurstCount, C2BurstTime, C23BurstISO, C23BurstShutterSpeed)
end
print()
print("********************************************************")
log ("Post C2 warning!")
print("********************************************************")
print()
do_beep()
end
cur_shutter_speed = TotalityMinShutterSpeed
CurISO = TotalityMinISO
if (SetAperture == 1)
then
camera.aperture.value = Aperture -- Reset aparture for totality shots
log ("Aperture %s for totality", Aperture)
end
repeat
take_shot(CurISO, cur_shutter_speed, 0, 0, 0)
if (CurISO < (TotalityPrefISO * 0.95))
then
CurISO = CurISO * 2.0^TotalityExpStep
elseif ((CurISO < (TotalityPrefISO * 1.1)) and (cur_shutter_speed < (TotalityMaxShutterSpeed * 0.95)))
then
cur_shutter_speed = cur_shutter_speed * 2.0^TotalityExpStep
elseif (CurISO < (TotalityMaxISO * 0.95))
then
CurISO = CurISO * 2.0^TotalityExpStep
else
cur_shutter_speed = TotalityMinShutterSpeed
CurISO = TotalityMinISO
end
until (get_cur_secs() >= (max_sec - MaxOffset)) -- Stop, and leave time to do the mid-eclipse earthshine
-- exposures.
end
--
-- do_max -- Take a number of long exposures at the time of maximum eclipse, to try to capture
-- an earthshine image. These can be bracketed.
--
function do_max()
if (DoMaxExposures == 0) -- Are we doing this?
then
log ("Skip Max Eclipse long exposures. Not configured.")
return -- Nope.
end
if ( get_cur_secs() >= max_sec) -- Have we passed max already?
then
log ("Skip Max. Finished %d seconds ago.", (get_cur_secs() - max_sec))
return
end
for count_max_exp = NumMaxExposures , 1 , -1 do
log ("do_max: MaxISO=%d, MaxShutterSpeed=%s, DoMaxBrackets=%d, NumMaxBrackets=%d, MaxBracketStep=%d",
MaxISO, tostring(MaxShutterSpeed), DoMaxBrackets, NumMaxBrackets, MaxBracketStep)
take_shot(MaxISO, MaxShutterSpeed, DoMaxBrackets, MaxBracketStep, NumMaxBrackets)
end
end
--
-- Similar to do_c2max, but reversed. Exposures run from longest to shortest (and then repeat),
-- the burst starts just before C3, and there are no bonus exposures.
--
function do_maxc3()
local cur_shutter_speed = 0
local CurISO = 0
if ( get_cur_secs() >= (c3_sec + RingStartOffset) ) -- Are we past this phase already?
then
log ("Skip Max->C3. Finished %d seconds ago.", (get_cur_secs() - (c3_sec + C3BurstStartOffset)))
return
elseif ( get_cur_secs() < (c3_sec - C3BurstStartOffset) ) -- Do we have time for some totality exposures?
then
cur_shutter_speed = TotalityMaxShutterSpeed
CurISO = TotalityMaxISO
log ("Main Max->C3 loop for %d seconds.", (c3_sec - C3BurstStartOffset - get_cur_secs()))
repeat
take_shot(CurISO, cur_shutter_speed, 0, 0, 0)
if (CurISO > (TotalityPrefISO * 1.05))
then
CurISO = CurISO / 2.0^TotalityExpStep
elseif ((CurISO > (TotalityPrefISO * 0.95)) and (cur_shutter_speed > (TotalityMinShutterSpeed * 1.05)))
then
cur_shutter_speed = cur_shutter_speed / 2.0^TotalityExpStep
elseif (CurISO > (TotalityMinISO * 1.01))
then
CurISO = CurISO / 2.0^TotalityExpStep
else
cur_shutter_speed = TotalityMaxShutterSpeed
CurISO = TotalityMaxISO
end
until (get_cur_secs() >= (c3_sec - (C3BurstStartOffset + 3)))
print()
print("********************************************************")
log ("3 seconds to C3! Filter warning!")
print("********************************************************")
print()
do_beep()
end
if (SetAperture == 1)
then
camera.aperture.value = C23Aperture -- Set aperture for C3
log ("Aperture %s for C3 shots", C23Aperture)
end
wait_until (c3_sec - C3BurstStartOffset)
if (UseBurst == 1)
then
take_burst (C23BurstCount, C23BurstISO, C23BurstShutterSpeed)
else
take_timed_burst(C23BurstCount, C3BurstTime, C23BurstISO, C23BurstShutterSpeed)
end
if (DoRing == 1)
then
if (UseBurst == 1)
then
take_burst (RingBurstCount, RingBurstISO, RingBurstShutterSpeed)
else
take_timed_burst(RingBurstCount, RingBurstTime, RingBurstISO, RingBurstShutterSpeed)
end
end
print()
print("*************************************************************")
log ("End of totality! Replace filter! Display Off!")
print("*************************************************************")
print()
do_beep()
end
--
-- The ringleader.
--
function main()
local starttime
local offset = 0
local offset_count = 0
starttime = get_cur_secs()
TestStartTime = starttime
menu.close()
console.show()
log_start ()
--
-- The camera maintains a millisecond timer since power-on. We can use this to
-- get close to the beginning of a given second. I think.
--
event.seconds_clock = function (ignore)
offset = offset + (dryos.ms_clock - (1000 * offset_count))
offset_count = offset_count + 1
return true
end
print ()
print ()
print ("-------------------------------------")
print (" Eclipse Magic")
print (" Copyright 2017, grnbrg@grnbrg.org")
print (" Mod 2019-2020 by G Abramson")
print (" Released under the GNU GPL")
print ("-------------------------------------")
print ()
print ("Starting 10 second timing calibration....")
--
-- There is a fair amount of jitter in the event timer. Averaging over 10 seconds will
-- give us a reasonable offset.
--
task.yield(10500)
-- Turn off the second_clock event timer.
event.seconds_clock = nil
tick_offset = (math.floor(offset / offset_count) % 1000)
print ("Done!")
print ()
log ("TestBeepNoShutter: %d", TestBeepNoShutter)
log ("C1: %s", pretty_time(c1_sec))
log ("C2: %s", pretty_time(c2_sec))
log ("C3: %s", pretty_time(c3_sec))
log ("C4: %s", pretty_time(c4_sec))
-- If the camera is not in manual mode, trying to set the shutter speed throws errors.
-- Check to make sure we are in manual mode, and refuse to run if we're not.
if (camera.mode == MODE.M)
then
-- if (SetAperture == 1)
-- then
-- camera.aperture.value = Aperture
-- log ("Aperture %s", Aperture)
-- end
do_partial ((c1_sec + PartialMarginTime), c2_sec, "Pre")
do_c2max()
do_max()
do_maxc3()
do_partial (c3_sec, (c4_sec - PartialMarginTime), "Post")
else
beep (5, 100)
log ("Camera must be in manual (M) mode!!")
print()
print("Press any button to exit the script. Change the mode and re-run.")
key.wait()
end
log ("All done. Normal exit.")
log_stop ()
print("Press any button to exit the script.")
key.wait()
console.hide()
end -- Done. Hope there were no clouds.
main() -- Run the program.
-- CHANGES
-- 1.0.1
-- Stopped running the seconds_clock event at all times, and moved the tick_offset
-- calculation to the setup at the start of execution
-- Fixed the pretty-printing of the timer
-- Fixed the sign of the C23BurstStartOffset in do_maxc3
-- Fixed the calculation of the difference between current time and the next second
-- in wait_until()
-- Added code to turn off the console during long waits
-- Massaged the end-of-exposure-bracketing conditions in do_c2max() and do_maxc3()
-- 1.1
-- Changed the partial phase exposure logic, so that instead of there being an exposure
-- at C1 and C2, there is an exposure at C1 and an exposure at (C2 - exposure_interval)
-- so that the totality images are properly centered between the requested partial phase
-- exposures.
-- Added an alarm at 30 seconds before C2 and 3 seconds before C3 to alert for any filter changes.
-- There is also a beep after the C2 and C3 bursts to flag any needed changes.
-- Split the C23BurstStartOffset into separate variables, to allow an asymmetric burst over
-- each period. (ie: 10 seconds before C2 to 3 seconds after C2)
-- 1.2
-- Added ISO brackets to totality exposure sequence. Totality now has a preferred ISO,
-- and will shift to that ISO at the fastest or slowest shutter speeds, then use that
-- preferred ISO for the requested range of shutter speeds, then shift ISO to the end of
-- the requested range.
-- Option to stop LiveView before touching the shutter controls.
-- Removed the two long exposures at max eclipse -- probably not needed.
-- 1.2.1
-- Changed call to lv.stop() to a beep, and instruction to the user to turn off LV.
-- lv.stop() doesn't seem to work.
-- Added print statements to explain filter warning beeps.
-- 1.3.0
-- Changed the startup timing loop to be more accurate if the script is started close to
-- a second boundary. (The average offset of 999ms and 1ms is 1ms, not 500ms)
-- Corrected an error in wait_until() -- Used div, where modulus was correct, and had
-- the tick_offset correction wrong. Don't code while tired. Thanks to
-- matman730 for pointing out this goof.
-- Improved the configuration comments around TestBeepNoShutter. They apparently weren't
-- clear as I thought.
-- 1.4.0 (Not released)
-- Attempt to implment the changing of the file prefix for the saved images. It didn't
-- work well, and I scrapped it.
-- 1.5.0
-- Make it optional to hide the console during script running, and allow the delay before
-- and after the next image to be configured.
-- Shutter shock reduction: Add a configurable (in milliseconds) delay before an exposure
-- where the shutter speed is within a (also configurable) range.
-- Add a mid-eclipse section. This is a short section around the max eclipse point to optionally
-- try for some earthshine exposures.
-- Set the aperture on program start.
-- 1.6.0 -- Contributions from Eric Krohn, (Many thanks!)
-- Added logging to permanent file, "ECLIPSE.LOG" at top level of the memory card
-- More extensive logging added throughout the script
-- Bugfix: In do_max() the last two arguments to take_shot() were reversed.
-- Bugfix: do_max() is unguarded as far as current time
-- Added pretty_shutter() to make the shutter speed numbers more sensible
-- 1.7.0
-- Added optional function to take some diamond ring images before and after the Baily's Beads
-- exposures. Be careful not to overlap the Ring and Beads exposures before C2!
-- Added a new burst function that takes a burst of images, one at a time, rather than using the
-- camera's burst function. Slower, but slower is better for the buffer, and gives us the
-- opportunity to stop shooting at a specific time, where a burst will run until the requested
-- number of images have been captured.
-- 1.7.1:
-- Added reminder at mid partial to check focus.
-- Added reminder 30 sec before each partial shot to recenter.
-- Function beep: just one instead of 3 5-beeps.
-- Added apertures for partial, ring and beads, and totality.