dbrgn / RPLCD

A Raspberry Pi LCD library for the widely used Hitachi HD44780 controller, written in Python. GPIO (parallel) and I²C modes supported.
https://rplcd.readthedocs.io/en/latest/
MIT License
261 stars 72 forks source link

Question: compat_mode for i2c or using lib with threads? #131

Closed OllisGit closed 2 years ago

OllisGit commented 2 years ago

I use the display with a i2c connection integrated in a OctoPrint-Plugin.

My output functions is called by OctoPrint-Server each time the progress of a print is changed. Calling my function is done via a new thread.

If the progress of a print changed really quickly the display shows weird characters. image

My assumption is that two (or more threads) started "simultaneously" and then the i2c-expander is not fast enough to send the data to the display. I saw that you implements a compat_mode for gpio, but not for i2c. Why?

I wrote a small test program to reproduce the error. With one thread everything looks fine, but if I use two threads the weird chars appears.

How can I solve this?

THREAD_COUNT = 1

def call_plugins(storage, filename, progress):
    mylcd.clear()
    mylcd.write_string('Printing...')
    mylcd.cursor_pos = (1 ,0)
    mylcd.write_string('Layer: 5/12')

for x in range(0, THREAD_COUNT):
    thread = threading.Thread(target=call_plugins, args=("storage", "filename", "progress"))
    thread.daemon = False
    thread.start()
dbrgn commented 2 years ago

If you use two parallel threads, then there's always a chance for race conditions between the two. A compat mode won't help here. RPCLD is not thread safe, a multi-threaded application needs to synchronize access to the LCD.

My suggestion would be to have a single thread that writes to the display. Other threads could then send "write requests" through something like a channel or synchronized queue, which the writing thread processes and writes to the display.

OllisGit commented 2 years ago

Thanks for the fast response and the advice.

I think I found a solution for me. Instead of a single thread as you suggested, I use a threading.lock.

Now my test program works fine, even with 10 threads!

lock = threading.Lock()

def call_plugins(storage, filename, progress):
    lock.acquire()
    mylcd.clear()
    mylcd.write_string('Printing...')
    mylcd.cursor_pos = (1 ,0)
    mylcd.write_string('Layer: ' + str(storage))
    lock.release()
dbrgn commented 2 years ago

Yep, that works as well for synchronization 🙂