This is part of the code I wrote for the icbnn balloon launch (
CHDK in the stratosphere )
It's a simple but fairly flexible scheduler that allows to specified snippets of lua code to be executed at a certain moment.
Of course, it's NOT interrupt-driven :-) and therefore it requires that the main program periodically (for example, about every second) calls a function to check if enough time has passed to execute the next "event". See below
It does not use time in hours:minutes, but rather met, "Mission Elapsed Time" (see
Mission Elapsed Time Explained ) . Remember, we are in space :-)
All times are therefore calculated in mets (met in seconds) and metp (met in periods) where each period is made up of a certain number of seconds.
This value must be defined in seconds_per_period and also defines the "granularity" of the scheduler, i.e. the the minimum distance in time between events.
The events are not written in a file, but rather in an array (a file parser would be a nice addition, but I didn't need it)
The array is filled like this:
timeline[
<metp when it must be executed>]='
<some lua code>';
for example:
timeline[123]='shoot()';
This means: at metp 123, execute the lua code "shoot()".
Lua code can be as complex as you want, such as single statements, calls to functions etc.
This is the simplest way to use it:
Sample main program
require("scheduler")
-- we only need a minutes-level resolution
seconds_per_period=60
-- our timeline
timeline[1]='shoot()';
timeline[5]='print(23*5)';
timeline[10]='do_something()';
-- initialize scheduler and set "start of mission"
init_time()
-- loop forever
repeat
sleep(1000)
-- periodically calling this function
refresh_mets()
until 1=2
-- forever :-)
This is the scheduler.lua file:
-- MISSION TIMER AND SCHEDULER
-- executes lua statements at specific times of "mission"
-- By "mission" we mean the run of the program
-- the functions are contained in the timeline[] array
timeline={}
-- the snippets of lua code specified in timeline[] will be executed at the specified times
-- for exampe:
-- timeline[20]='print(123)';
-- times are in mission periods
-- the duration of each period (in seconds) is:
-- seconds_per_period=15 (SET IN MAIN PROGRAM)
-- this also defines the granularity of the scheduler
-- (i.e. the minimum time resolution between statements)
-- The scheduler must be initialized by calling time_init()
-- then all we have to do is periodically call refresh_mets()
-- and the snippets in timeline[] will be executed accordingly
-- the accuracy of the scheduling is determined by the delay
-- between subsequent calls to refresh_mets()
-- Start-of-mission time
-- (the get_tick_count() value when the mission started)
somt = -1
-- Mission elapsed time in periods
-- (how many period are elapsed since somt)
metp = -1
-- Mission elapsed time in seconds
-- (how many seconds are elapsed since somt)
mets = -1
-- value of get_tick_count() when the next period will start (to ease calculations)
period_rollover =-1
-- clear the timeline
function clear_timeline()
timeline={}
end
-- Refreshes the value of mets
-- and checks if we need to go into next period
function refresh_mets()
local t
t=get_tick_count()
-- get met in seconds
mets = (t-somt)/1000
-- check if we are into next period
if t>period_rollover then
refresh_metp()
end
end
-- ok, we are going into next period
function refresh_metp()
-- remember a period has passed
metp=metp+1
-- set somt when next period will pass
period_rollover=period_rollover + seconds_per_period*1000
-- the line above ensures that even if we call refresh_mets()
-- with long dealys we never risk skipping a metp (i.e. we never
-- risk skipping a statement in timeline()
writelog("SCH", "metp "..metp)
-- see if anything is scheduled in timeline at metp
scheduler(metp)
end
-- initialize timing mechanism
function init_time()
somt = get_tick_count()
metp=-1
period_rollover=somt-1
refresh_mets()
end
function scheduler(t)
-- sees what must happen at time t (expressed in periods)
-- does not check at previous values of t (must therefore be called for each t)
if timeline[t] then
writelog("SCH", "Executing "..timeline[t]);
torun=assert(loadstring(timeline[t]))
torun()
end
end
This module calls the writelog function, uncomment the calls if this is not needed:
function timestamp()
h=get_time("h")
m=get_time("m")
s=get_time("s")
return ( h .. ":" .. m .. ":" .. s)
end
function writelog(prefix, msg)
ts=timestamp()
print('###' .. prefix .. ' ' .. ts .. ' ' .. mets .. ' ' .. msg)
end
Important notices:
1) REMEMBER: errors in the code snippets are rather difficoult to catch...
For example , timeline[1]='shoot' will fail because there is no "()" and will be difficult to debug.
ALWAY run the snippets of code directly to test and then copy them in the timeline
2) the precision is only limited by the seconds_per_period value and by the delay that passes between each call of refresh_mets()
refresh_mets does not need to be called at exact intervals, just call it when you have some time, but remember that the scheduler only "schedules" when refresh_mets() is called
3) seconds_per_period should be not too low (to save processing and memory). For example, if you want to shoot a photo every 5 seconds starting after an hour and for a minute don't use the scheduler like this:
seconds_per_period=1
timeline[7200]='shoot()';
timeline[7205]='shoot()';
timeline[7210]='shoot()';
....
timeline[7260]='shoot()';
but rather like this:
seconds_per_period=60
timeline[60]='shoot_for_a_minute_every_5_seconds()';
and define your function shoot_for_a_minute_every_5_seconds
4) don't be fooled: timeline is an array, so if you have two lines with the same index you overwrite the first entry:
timeline[50]='shoot()';
timeline[50]='print(12)';
is identical to:
timeline[50]='print(12)';
also,
timeline[12]='print(1)';
timeline[10]='print(2)';
will print first 2 and then 1!!
5) if the events you are scheduling are short enough, this approach is perfect. Each event is finished before the next event is launched.
If an event is long, the next event might be delayed but will not be skipped
seconds_per_period=60
timeline[10]='do_something_that_takes_5_minutes()';
timeline[11]='print(1)';
timeline[12]='print(2)';
timeline[20]='print(3)';
what will happen is:
min 10: start of do_something_that_takes_5_minutes()
min 15: print(1)
min 16: print(2)
min 20: print(3)
The delayed events will be executed at a a rate of one per period until we catch up with the schedule
If you need to do something more complex, for example like:
for the first 60 minutes, shoot a photo every 30 seconds
for the next 120 minutes, shoot 5 photos every 5 second, then wait 30 seconds
then a slightly more complex approach will be needed. I will make a later post on this...