Scheduler in LUA - Completed and Working Scripts - CHDK Forum supplierdeeply

Scheduler in LUA

  • 5 Replies
  • 6789 Views
*

Offline fbonomi

  • ****
  • 469
  • A570IS SD1100/Ixus80
    • Francesco Bonomi
Scheduler in LUA
« on: 09 / January / 2009, 04:31:48 »
Advertisements
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
Code: [Select]
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:

Code: [Select]
-- 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:

Code: [Select]
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:

Code: [Select]
seconds_per_period=1
 timeline[7200]='shoot()';
 timeline[7205]='shoot()';
 timeline[7210]='shoot()';
 ....
 timeline[7260]='shoot()';

but rather like this:
Code: [Select]
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

Code: [Select]
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...
« Last Edit: 11 / January / 2009, 02:45:56 by fbonomi »

*

Offline reyalp

  • ******
  • 12001
Re: Scheduler in LUA
« Reply #1 on: 09 / January / 2009, 05:37:54 »
Why not just assign functions to your timeline array, rather than strings ?

You could do
Code: (lua) [Select]
timeline[10]=do_something_that_takes_5_minutes -- note: no () assigns the function, with the () would call the function.
or
Code: (lua) [Select]
timeline[11]=function() print(1) end -- an anonymous function
or
Code: (lua) [Select]
timeline={
10=do_something_that_takes_5_minutes,
11=function() print(1) end,
}

Of course in the execution portion, you'd just call
Code: [Select]
timeline[x]()rather than a loadstring.

If you need to call a similar function with different values, you can use a function that returns a closure.
Don't forget what the H stands for.

*

Offline fbonomi

  • ****
  • 469
  • A570IS SD1100/Ixus80
    • Francesco Bonomi
Re: Scheduler in LUA
« Reply #2 on: 09 / January / 2009, 05:43:28 »
Why not just assign functions to your timeline array, rather than strings ?

Because I am a LUA newbie :-)

I can see many advantages in your approach:
- no last-moment compiling
- errors come out immediately
...

Re: Scheduler in LUA
« Reply #3 on: 10 / January / 2009, 21:40:38 »
If you decide you want to read in configuration data from a file, I can strongly recommend using "loadstring". loadstring can read in arbitrarily complex data in a single line - the data in the file just needs to be a LUA table/object. You can even put code in there depending upon your needs! E.g. a configuration file could be:

Code: [Select]
{
 seconds_per_period=1,
 timeline={
  [1]='shoot()',
  [5]='print(23*5)',
  [10]='do_something()'
 }
}

See this script that shows a data file being read in using a single line. The script is also interesting because it outputed the data to that file -- this is used to save state between runs of the script!

PS: I think you have a typo in your original post: timeline[11]='print(1)';  timeline[11]='print(2)';  -- they both use 11 and so print(1) won't occur?


*

Offline fbonomi

  • ****
  • 469
  • A570IS SD1100/Ixus80
    • Francesco Bonomi
Re: Scheduler in LUA
« Reply #4 on: 11 / January / 2009, 02:46:28 »
PS: I think you have a typo in your original post: timeline[11]='print(1)';  timeline[11]='print(2)';  -- they both use 11 and so print(1) won't occur?

thanks!
corrected

Re: Scheduler in LUA
« Reply #5 on: 06 / April / 2009, 11:13:57 »
we need a central repository for code like this - that does a very good job of solving a general problem. i'll make a wiki page if someone doesn't beat me to it.

 

Related Topics