miketeachman / micropython-rotary

MicroPython module to read a rotary encoder.
MIT License
269 stars 56 forks source link

Counter get stuck on "schedule queue full" error #14

Open willbelr opened 2 years ago

willbelr commented 2 years ago

With a "binary" bounded range (-1 or 1), when the "schedule queue full" RuntimeError is encountered in rotary.py, it blocks all future inputs occuring in the same direction. Thus, the rotary encoder must be rotated in the opposite direction to change self._value.

For my use case, the solution was to reverse the saved value on error instead of silencing it :

        try:
            if old_value != self._value and len(self._listener) != 0:
                micropython.schedule(_trigger, self)
        except RuntimeError:
            self._value = 1 if not self.value else 0

However this will break other range modes such as RotaryIRQ.RANGE_UNBOUNDED.

Below is a sample code derived from the async class example:

import uasyncio as asyncio
from rotary_irq_rp2 import RotaryIRQ

class RotaryEncoder():
    def __init__(self, rotary_encoder, callback):
        self.cb = callback
        self.re = rotary_encoder
        self.re.add_listener(self._callback)
        self.re.reset()
        self.event = asyncio.Event()
        asyncio.create_task(self._activate())

    async def _activate(self):
        while True:
            await self.event.wait()
            value = self.re.value()
            if value:
                self.cb(value)
                self.re.reset()
            self.event.clear()

    def _callback(self):
        self.event.set()

async def main():
    def re_callback(value):
        print("$", value)

    RotaryEncoder(RotaryIRQ(pin_num_clk=16,
                            pin_num_dt=17,
                            min_val=-1,
                            max_val=1,
                            range_mode=RotaryIRQ.RANGE_BOUNDED),
                  re_callback)

    while True:
        await asyncio.sleep_ms(10)

try:
    asyncio.run(main())
except (KeyboardInterrupt, Exception) as e:
    print('Exception {} {}\n'.format(type(e).__name__, e))
finally:
    ret = asyncio.new_event_loop()  # Clear retained uasyncio state
dmccreary commented 11 months ago

n micropython, we get the following error in the rotary class:

line 30: micropython.schedule(self.call_handlers, Rotary.ROT_CW)

Traceback (most recent call last): File "rotary.py", line 30, in rotary_change RuntimeError: schedule queue full

In MicroPython, the micropython.schedule() function is used to schedule a function to be called later in the context of the MicroPython scheduler. It's especially useful when you need to call a function in response to an interrupt, but the function might not be safe to call directly from an interrupt context (for example, it may allocate memory).

The error "RuntimeError: schedule queue full" indicates that the internal queue MicroPython uses to keep track of scheduled functions is full. There's only a limited number of slots in this queue.

Here are some reasons why you might be encountering this error:

High Interrupt Rate: If the rotary_change function is getting called very frequently (because of rapid changes in the rotary encoder), it might be trying to schedule more functions than the queue can handle.

Scheduled Function Duration: If the scheduled function (self.call_handlers) takes a long time to execute, it can prevent the queue from being emptied in a timely manner, which means new functions can't be scheduled until the old ones are executed.

Other Scheduled Functions: If other parts of your code are also scheduling functions using micropython.schedule(), they could be filling up the queue, leaving no space for the rotary_change function to schedule its handler.

To solve the problem:

Debounce: If the interrupt from the rotary encoder isn't debounced, you might get multiple interrupts for a single "event". Consider adding a debounce mechanism.

Optimize the Handler: Ensure that the function you're scheduling (self.call_handlers) is as fast as possible. If you can optimize it, you'll free up the queue more quickly.

Check for Other Schedulers: Look through your code to see if other parts of your application are also scheduling functions frequently, and optimize or reduce their frequency if possible.

Direct Handling: If the operation in the scheduled function is simple and doesn't require memory allocation or other operations that aren't safe in an interrupt context, you might consider handling it directly in the interrupt handler rather than scheduling it.

Increase the Queue Size (Advanced): If you're building your own MicroPython firmware, you might be able to increase the size of the schedule queue, though this will consume more RAM.

Remember, the goal is to balance the rate at which functions are added to the schedule queue with the rate at which they're removed (executed). If you can achieve this balance, you won't run into the "schedule queue full" error.