Page 1 of 1

A Pauser class

Posted: Thu May 30, 2019 7:56 am
by blippy
Elevator pitch: Pauser encapsulates the notion of state change, concurrency and delays. It is portable across languages, requires no special language features or infrastructure.

Motivation: So I had been tooling around trying to get concurrency working, wanting delays, and trying to debounce a switch, amongst other things. There's all sorts of solutions available, including protothreads, RTOSs, coroutines, etc.. I found most of them difficult to understand or problematical to use. For example, you don't want to switch to an RTOS, because that's a whole new environment.

Solution: Enter my idea of a Pauser class. It could also be implemented in C, if you like. I'll use MicroPython in this example, but minor tweaks will make it compatible with Python.

I'll explain how it works by way of example: debouncing a button. Debouncing a button is a surprisingly difficult endeavour. Let's see if we can't simplify things!

Suppose sw1 is a button switch, which you've set up correctly as an input pullup. sw1.value() returns whether the pin is high or low. I think you use digitalRead on regular Python. A value of 1 means the button isn't pressed, whilst 0 means it is pressed.

So, the first step is to set up a "pauser":

Code: Select all

p = Pauser()
p.pause(button_pressed, condition = lambda: sw1.value() == 0)
The pauser class is instantiated. Then, using the pause() command, we set up a callback function, a condition under which the callback function is activated, and an optional delay until the condition is tested.

We need to periodically update the pauser, which we can do in our main loop:

Code: Select all

def myloop():
	while True:
		p.update()
The update() function checks to see if there's anything to do, waits (or not) for a delay, and then triggers the callback if the condition is satisfied.

Here's the implementation of button_pressed():

Code: Select all

def button_pressed(pauser):
    print('Button pressed')
    pauser.pause(button_released, condition = lambda: sw1.value() == 1, 
                 delay_ms = 20)
So it does whatever you want, in this case printing "Button Pressed", and then changes state. Note that the callback takes the pauser as an argument so that it can do that. What the third line is doing is saying "wait for 20ms, then if the button has gone high again, call the button_released() function".

This is how button_released() is implemented:

Code: Select all

def button_released(pauser):
    #print("Button released")
    pauser.pause(button_pressed, condition = lambda: sw1.value() == 0, 
                 delay_ms = 20)
It basically does the opposite: wait for 20ms, then if the switch is low again, switch back to button_pressed().

Note that the delay doesn't block the operation of anything else. Huzzah!

Here's the implementation of Pauser:

Code: Select all

class Pauser:
    def __init__(self):
        self.callback = None
        self.condition = None
        self.start = None
        self.delay_ms = None
    
    def pause(self, callback, condition = None, delay_ms = 0):
        self.callback = callback
        self.condition = condition
        self.start = ticks_ms()
        self.delay_ms = delay_ms
        
    def update(self):
        if self.callback == None: return
        if ticks_diff(ticks_ms(), self.start) < self.delay_ms:
            return
        try: 
            triggered = self.condition()
        except TypeError:
            triggered = True
        if triggered:
            fn = self.callback
            self.callback = None
            fn(self)

Re: A Pauser class

Posted: Thu May 30, 2019 2:09 pm
by donmerch
Nice!