Page 1 of 1

Can I pass parameter to Timer callback?

Posted: Fri Jan 29, 2021 8:39 pm
by water
How to do that? :?:
Thanks.

Re: Can I pass parameter to Timer callback?

Posted: Sat Jan 30, 2021 1:21 am
by dhylands
Not directly.

You can make the timer callback a bound method in a class. This example shows how this is done:
https://github.com/dhylands/upy-example ... eat_irq.py

So the timer callback function has to take exactly 1 argument, which is the timer object that the IRQ is being called for. However, since the callback is bound to self, you can reference self.ticks and self.led from within the timer callback.

Re: Can I pass parameter to Timer callback?

Posted: Thu Jun 10, 2021 10:15 am
by davidsmith
t1 = Timer(1)
z = 400
t1.init(period=2000, mode=Timer.ONE_SHOT, callback=lambda b: test(z))
it work for me

Re: Can I pass parameter to Timer callback?

Posted: Tue Jul 26, 2022 2:12 pm
by Jibun no kage
@davidsmith, How did it work? Using lambda? What does the callback declaration look like? Your example is incomplete. Can you show the callback declaration please?

Re: Can I pass parameter to Timer callback?

Posted: Wed Jul 27, 2022 8:37 pm
by Jibun no kage
Figured it out, you just qualify the argument list as usual for the call back, so lambda t: SOME_FUNCTION(SOME_ARGUMENT) for example.

Re: Can I pass parameter to Timer callback?

Posted: Thu Jul 28, 2022 12:23 pm
by DeaD_EyE
You can reach this with minimal effort.
  • Anonymous function: lambda

    Code: Select all

    from machine import Timer
    
    
    my_callback = lambda timer: print(42)
    
    Timer(1).init(mode=Timer.ONE_SHOT, period=1000, callback=my_callback)
    
  • Normal function

    Code: Select all

    from machine import Timer
    
    
    def my_callback(timer):
        print(42)
    
    
    Timer(1).init(mode=Timer.ONE_SHOT, period=1000, callback=my_callback)
    
  • With a closure (modified):

    Code: Select all

    from machine import Timer
    
    
    def timer_partial(func, *args, **kwargs):
        
        # added timer as a mandatory argument
        # to filter it out 
        def inner(timer, *iargs, **ikwargs):
            return func(*args, *iargs, **kwargs, **ikwargs)
    
        return inner
    
    
    Timer(1).init(mode=Timer.ONE_SHOT, period=1000, callback=timer_partial(print, "Hello World", end="=======\n"))
    
  • With a class:

    Code: Select all

    from machine import Timer
    
    
    class MyCallback:
        def __init__(self, value):
            self.value = value
            
        def callback(self, timer):
            """
            Callback is called by Timer.
            The first argument is the instance of the class
            The second argument is the instance of the timer, which is not used here
            """
            print("===", self.value, "===")
        
        def __call__(self, timer):
            """
            This enables the possibility to call the instance directly.
            """
            self.callback(timer)
            
        def __repr__(self):
            """
            Method to have a nice representation, but it's not required.
            """
            return f"{self.__class__.__name__}(value={self.value})"
    
    
    my_callback = MyCallback(1337)
    
    # works, because of the __call__ method:
    Timer(1).init(mode=Timer.ONE_SHOT, period=1000, callback=my_callback)
    
    # without having the __call__ method:
    Timer(1).init(mode=Timer.ONE_SHOT, period=1000, callback=my_callback.callback)
    
Another example with a class and automatic initialization of Timer:

Code: Select all

from time import sleep
from machine import Timer


def partial(func, *args, **kwargs):
    def inner(*iargs, **ikwargs):
        return func(*args, *iargs, **kwargs, **ikwargs)

    return inner


class TimerCallback:
    _instances = []
    __slots__ = ("_timer", "_callbacks", "_id", "delay", "mode")

    def __init__(self, id, delay, mode=Timer.PERIODIC):
        if id in self._instances:
            raise RuntimeError(f"Timer with id {id} exists already")

        self.delay = delay
        self.mode = mode

        self._id = id
        self._callbacks = []
        self._timer = Timer(id)
        self._instances.append(id)

    def _callback(self, timer):
        for callback in self._callbacks:
            callback()

    def start(self):
        self._timer.init(mode=self.mode, period=self.delay, callback=self._callback)

    def stop(self):
        self._timer.deinit()

    def add_callback(self, callback, *args, **kwargs):
        if not callable(callback):
            raise TypeError("Callback must be callable")

        prepared_callback = partial(callback, *args, **kwargs)
        self._callbacks.append(prepared_callback)

        return prepared_callback

    def del_callback(self, prepared_callback):
        if prepared_callback in self._callbacks:
            self._callbacks.remove(prepared_callback)

    def kill(self):
        self._timer.deinit()
        if self._id in self._instances:
            self._instances.remove(self._id)


tc = TimerCallback(1, 5000)
tc.add_callback(print, "Hello", "World", sep="\t", end="\n\n\n\n")
my_callback_reference = tc.add_callback(print, "Hello World 2")
tc.start()
sleep(11)
tc.stop()

try:
    tc2 = TimerCallback(1, 300)
except RuntimeError as e:
    print(e.args[0])

tc.kill()
# will remove also the id

try:
    tc2 = TimerCallback(1, 3000)
except RuntimeError as e:
    print(e.args[0])

tc2.add_callback(print, "Lesser args")
tc2.start()

sleep(12)
tc2.kill()
Many functions we use in Micropython are implemented in C to save space on the Microcontroller.
If any possible use-case is implemented by Micropython, the firmware won't fit on all Microcontrollers.