Skip to main content

Timer module

Timer calls one of your functions after a while (after) or every so often (every): for a reminder, a blink, reading a sensor now and then. The system keeps the time by itself, but your program runs the function, between one instruction and the next: it does not run in parallel with it (see When it runs).

JARU Timer module screenshot for scheduling callbacks, periodic tasks, and timed delays

use Timer

Platforms​

It works the same on Windows and on every board. There can be up to 32 timers at once: with the 32 in use, after() and every() return an error value and create nothing.

Functions​

FunctionWhat it doesReturns
after(ms, function, data)Calls the function once, after ms milliseconds.The timer number
every(ms, function, data)Calls the function every ms milliseconds.The timer number
clear(id)Stops a timer.true

The errors are not thrown: they are returned, as a value:

  • with the wrong number of arguments, ERR_INVALID_ARG_NUMBER;
  • with a time that is not a number, something that is not a function or data that is not a number, ERR_TYPE_MISMATCH;
  • with no room for another timer: after() returns ERR_INDEX_OUT_OF_RANGE, and every(), ERR_OUT_OF_MEMORY.

try does not catch those errors, and in an if an error value counts as true. A timer number is always greater than 0: check it with id > 0. More on error values in Errors and exceptions.

after​

after(ms, function, data) calls the function once, after ms milliseconds. It returns the timer number.

use Timer

var done = false

func notice(id, periodic, data)
println("Now: timer ", id, ", periodic ", periodic, ", data ", data)
done = true
end

var id = Timer.after(500, notice, 7)
println("Created timer ", id)

// The program has to stay alive for it to fire
while (not done)
pause(10)
end

What it prints:

Created timer 1
Now: timer 1, periodic false, data 7
ParameterWhat it is
msThe milliseconds it waits. It takes decimals, but only the whole part counts.
functionThe function it calls: see The timer function.
dataOptional: a number the function receives. With decimals, the whole part stays (2.7 arrives as 2). By default, 0.

After calling the function, the timer deletes itself: there is no need for clear().

every​

every(ms, function, data) calls the function every ms milliseconds, until you stop it with clear(). The parameters are those of after(). It returns the timer number.

use Timer

var count = 0

func tick(id, periodic, data)
count = count + 1
println("Tick ", count)
if (count == 3) then
Timer.clear(id) // it stops itself
end
end

Timer.every(200, tick)

while (count < 3)
pause(10)
end

What it prints:

Tick 1
Tick 2
Tick 3

Use an interval of 1 ms or more: with 0, on Windows it fires only once.

clear​

clear(id) stops the timer and deletes it: its function is no longer called, even if a firing was waiting. It returns true, also if the timer does not exist or had already finished. An id that is not an integer returns ERR_TYPE_MISMATCH.

use Timer

func never(id, periodic, data)
println("This does not show")
end

var id = Timer.after(1000, never)
println(Timer.clear(id)) // true
println(Timer.clear(id)) // true: it no longer exists, and nothing happens

The timer function​

The function always receives three parameters:

ParameterWhat it is
idThe number of the timer that calls it.
periodictrue if it comes from every(), false if from after().
dataThe number you passed when creating the timer, or 0.
Three parameters, no more and no fewer

If the function does not have exactly three parameters, the timer does not call it, and it does not warn you: it looks as if it never fires. Even if you do not use all three, declare them.

How to pass the function​

use Timer

var done = false

func myNotice(id, periodic, data)
println("Function")
end

class TrafficLight
def init()
this.state = 0
end
def change(id, periodic, data)
this.state = (this.state + 1) % 3
end
end

var crossing = TrafficLight()

// 1) The function, by its name, without quotes
Timer.after(100, myNotice)

// 2) A lambda, without declaring a separate function
Timer.after(200, fn(id, p, d):
println("Lambda")
end)

// 3) A method of an object: it runs with its this
Timer.every(100, crossing.change)

// 4) The name of a global function, in quotes
Timer.after(300, "myNotice")

Timer.after(400, fn(id, p, d):
done = true
end)
while (not done)
pause(10)
end
  • With a method (crossing.change), the timer keeps the object alive while the timer exists: there is no need to keep it in another variable. It is let go with clear(), or when an after() timer fires.
  • With the name in quotes, the function is looked up when the timer fires, among the global functions: it does not work for methods. If it does not exist, nothing happens. The other three forms are checked when the timer is created.

When it runs​

The timer keeps the time by itself, but the VM runs the function inside your program:

  • Only while the program is still alive. When the program ends its timers are deleted, and whatever had not fired yet no longer fires. That is why the examples end in a loop.
  • At the VM's safe points: when a loop goes round, and when a function is called or returns. Never inside pause(): whatever fires during a pause waits until it ends. That is why the function can arrive somewhat later than asked.
  • One at a time. While a timer function runs, no other one starts: the rest wait their turn.

A timer function has to be short: while it runs, your program waits, and so do the other timers.

Full example​

Three LEDs that light up in turn every half second, and a second timer that turns everything off after 3 seconds. The LEDs are on pins 2, 4 and 5: change them for those of your board.

use Timer, GPIO

const LED_RED = 2
const LED_GREEN = 4
const LED_BLUE = 5

var state = 0
var running = true

func lightCycle(id, periodic, data)
GPIO.write(LED_RED, 0)
GPIO.write(LED_GREEN, 0)
GPIO.write(LED_BLUE, 0)
if (state == 0) then
GPIO.write(LED_RED, 1)
elsif (state == 1) then
GPIO.write(LED_GREEN, 1)
else
GPIO.write(LED_BLUE, 1)
end
state = (state + 1) % 3
end

// The data is the number of the cycle timer, to stop it
func turnAllOff(id, periodic, cycleId)
Timer.clear(cycleId)
GPIO.write(LED_RED, 0)
GPIO.write(LED_GREEN, 0)
GPIO.write(LED_BLUE, 0)
running = false
end

GPIO.pinmode(LED_RED, GPIO.OUTPUT)
GPIO.pinmode(LED_GREEN, GPIO.OUTPUT)
GPIO.pinmode(LED_BLUE, GPIO.OUTPUT)

var cycle = Timer.every(500, lightCycle)
Timer.after(3000, turnAllOff, cycle)

while (running)
pause(10)
end
println("System off")

Pitfalls and known issues​

  • The program has to stay alive. If it ends, its timers are deleted without firing.
  • Nothing fires inside pause(). A long pause delays every timer function until it ends.
  • The function has to have three parameters. With any other number, the timer does not call it and does not warn you.
  • Errors are returned, not thrown. Check the timer number with id > 0.
  • At most 32 timers. If you ask for one more, you get an error value and it isn't created. If you need many, use a single every() timer that takes care of several tasks.
  • The data can only be a number, and it arrives without decimals.
  • every() with 0 ms fires only once on Windows: use 1 ms or more.

All the known issues of JARU, together, are in Known issues.