JC3248W535EN event problem

Thank you, that would be great. I lack the knowledge from the _thread module since I used asyncio previously with lvgl8.

asynchio has a huge amount of overhead associated with it and it’s overhead that you really don’t need to have when working on a constrained device like the ESP32.

Give me a little bit to locate the thread worker code. I have it saved somewhere, just have to locate it.

@de-dh

Here is some pseudo code for ya…

import _thread
import sys
import lvgl as lv
import time

import task_handler


class Worker:

    def __init__(self, func, refresh_now, *args, **kwargs):

        self.args = args
        self.kwargs = kwargs
        self.func = func
        self.err = None
        self.retval = None
        self.refresh_now = refresh_now
        self.lock = _thread.allocate_lock()    

    def __call__(self):
        try:
            self.retval = self.func(*self.args, **self.kwargs)                 
        except Exception as err:
            self.err = err
        self.lock.release()


class LVGLThreadWorker:

    def __init__(self):
        self.queue = []
        self.queue_lock = _thread.allocate_lock()
        self.process_lock = _thread.allocate_lock()
        self.is_running = False
        self._exit = False
        self.start_time = time.ticks_ms()
        self.th = task_handler.TaskHandler()
        
        # we are going to take control of the task_handler to utilize the timer 
        # it has running to signal our threadworker to add a worker that is going
        # to refresh the display. By returning `False` from the callback that stops 
        # the task handler from calling lv.task_handler. It does update the tick
        # so we only need to measure the time from when the callback is called
        # to when the worker gets run. This might be a while depending on 
        # how much has been queued.
        self.th.add_event_cb(task_handler.TASK_HANDLER_STARTED, self.__task_handler_cb)

    def __task_handler_cb(self, *_):
        def _do():
            stop_time = time.ticks_ms()
            diff = time.ticks_diff(stop_time, self.start_time)
            
            lv.tick_inc(diff)
            lv.task_handler()

        self.start_time = time.ticks_ms()
        self.add(_do)
        
        return False
    
    # the refresh now will update the display immediatly after
    # the worker has run. This is a nice feature to have if you want the
    # display to show updates ASAP. It i CPU instesive to do the refreshes so it
    # is advised to use this feature sparingly.
    def add(self, func, refresh_now=False, *args, **kwargs):
        worker = Worker(func, refresh_now, args, kwargs)

        with self.queue_lock:
            self.queue.append(worker)

        self.process_lock.release()

    # this is the method you use to queue a job if there is a return value 
    # that is needed. calling this will cause the calling thread to release 
    # it's context so the thread working will continue to run and only when 
    # the job is ran will the context flip to go back to the calling thread
    def addwait(self, func, *args, **kwargs):
        worker = Worker(func, args, kwargs)
        # aquire the lock so the next call to acquire will stall
        worker.lock.acquire()

        with self.queue_lock:
            self.queue.append(worker)

        if self.process_lock.locked():
            self.process_lock.release()
        
        # stalling the calling thread so the thread worker\ is able to
        # process more jobs
        worker.lock.acquire()
        
        # job has finished running and at the moment the processing has been paused
        # to allow this thread to continue to run. we want to release that stall so 
        # when the next context switch happens the processing thread will start to run
        # again
        worker.lock.release()
        
        # check if there is an exception that needs to be raised
        if worker.err is not None:
            raise worker.err
        
        return worker.retval

    def start(self):
        if self.is_running:
            return
        
    def stop(self):
        self._exit = True
        self.process_lock.release()

    def _loop(self):
        self.process_lock.acquire()
        self.is_running = True
        self._exit = False

        while not self._exit:
            self.process_lock.acquire()
            with self.queue_lock:
                queue_len = len(self.queue)

            while queue_len:
                with self.queue_lock:
                    worker = self.queue.pop(0)
                    queue_len = len(self.queue)

                worker()
                
                if worker.lock.locked():
                    worker.lock.acquire()

                if worker.err is not None:
                    sys.print_exception(worker.err)
                elif worker.refresh_now:
                    scrn = lv.screen_active()
                    disp = scrn.get_display()
                    lv.refr_now(disp)
        
        self.is_running = False
        self.process_lock.release()

and here are some examples of how to use it.


# startup code

# do driver startup code here

import machine


pin1 = machine.Pin(10, machine.Pin.IN)
sensor1 = machine.ADC(pin1)

pin2 = machine.Pin(12, machine.Pin.IN)
sensor2 = machine.ADC(pin2)


scrn = lv.screen_active()

bar1 = lv.bar(scrn)
bar1.set_pos(20, 20)

bar2 = lv.bar(scrn)
bar2.set_pos(20, 80)

tw = LVGLThreadWorker()
tw.start()


# example 1

def read_pins():
    
    while True:
        time.sleep_ms(5)
        
        value1 = sensor1.read_u16()
        value2 = sensor2.read_u16()
                
        s1_value = int(value1 / 4096.0 * 100.0)
        s2_value = int(value2 / 4096.0 * 100.0)
        
        tw.add(bar1.set_value, s1_value)
        tw.add(bar2.set_value, s2_value)


_thread.create_new_thread(read_pins)


# example 2

def read_pins():

    while True:
        time.sleep_ms(5)

        value1 = sensor1.read_u16()
        value2 = sensor2.read_u16()

        s1_value = int(value1 / 4096.0 * 100.0)
        s2_value = int(value2 / 4096.0 * 100.0)

        tw.add(bar1.set_value, s1_value)
        tw.add(bar2.set_value, s2_value, refresh_now=True)


_thread.create_new_thread(read_pins)


# example3

def read_pins():

    while True:
        time.sleep_ms(5)

        value1 = sensor1.read_u16()
        value2 = sensor2.read_u16()
        s1_value = int(value1 / 4096.0 * 100.0)
        s2_value = int(value2 / 4096.0 * 100.0)
        
        def _do(v1, v2):
            bar1.set_value(v1)
            bar1.set_value(v2)
        
        tw.add(_do, s1_value, s2_value, refresh_now=True)


_thread.create_new_thread(read_pins)

There are bound to be some things that need to be fixed in it. I just keyed it up really fast. It is used for showing the idea more so than anything else.

Thank you so much!

Great, this example should definitely be showcased n the documentation !

This is for really advanced uses. I also added in a FreeRTOS binding that exposes almost all of the FreeRTOS functions an macros. Using that will actually allow you to specify what CPU core to run a task (thread) on. Using it is for really advanced users that know their way around FreeRTOS.