hzeller / rpi-rgb-led-matrix

Controlling up to three chains of 64x64, 32x32, 16x32 or similar RGB LED displays using Raspberry Pi GPIO
GNU General Public License v2.0
3.71k stars 1.17k forks source link

Clearing matrix between images in Python script #448

Closed nschwanke closed 6 years ago

nschwanke commented 6 years ago

I am attempting to display different animations in a Python script on my led matrix by accessing the image-viewer utility via the subprocesses.Popen command. This works successfully, however when I want to switch to a new one the matrix is overwritten instead of cleared and replaced. I have attempted to catch the pid with a variable p and use p.kill() or p.terminate() to end the utility subprocces before beginning again but this does not work.

Is what I am trying to do possible? Or do I really need to write my own image-viewer in python to get the type of state machine driven animations wanted?

angelogoncalve commented 6 years ago

You can use this to stop / kill a thread (https://eli.thegreenplace.net/2011/12/27/python-threads-communication-and-stopping):

import os, time import threading, Queue

class WorkerThread(threading.Thread): """ A worker thread that takes directory names from a queue, finds all files in them recursively and reports the result.

    Input is done by placing directory names (as strings) into the
    Queue passed in dir_q.

    Output is done by placing tuples into the Queue passed in result_q.
    Each tuple is (thread name, dirname, [list of files]).

    Ask the thread to stop by calling its join() method.
"""
def __init__(self, dir_q, result_q):
    super(WorkerThread, self).__init__()
    self.dir_q = dir_q
    self.result_q = result_q
    self.stoprequest = threading.Event()

def run(self):
    # As long as we weren't asked to stop, try to take new tasks from the
    # queue. The tasks are taken with a blocking 'get', so no CPU
    # cycles are wasted while waiting.
    # Also, 'get' is given a timeout, so stoprequest is always checked,
    # even if there's nothing in the queue.
    while not self.stoprequest.isSet():
        try:
            dirname = self.dir_q.get(True, 0.05)
            filenames = list(self._files_in_dir(dirname))
            self.result_q.put((self.name, dirname, filenames))
        except Queue.Empty:
            continue

def join(self, timeout=None):
    self.stoprequest.set()
    super(WorkerThread, self).join(timeout)

def _files_in_dir(self, dirname):
    """ Given a directory name, yields the names of all files (not dirs)
        contained in this directory and its sub-directories.
    """
    for path, dirs, files in os.walk(dirname):
        for file in files:
            yield os.path.join(path, file)

Here's some simple code that uses this worker thread. It creates a thread pool with 4 threads, feeds them work and waits for all the results to arrive:

def main(args):

Create a single input and a single output queue for all threads.

dir_q = Queue.Queue()
result_q = Queue.Queue()

# Create the "thread pool"
pool = [WorkerThread(dir_q=dir_q, result_q=result_q) for i in range(4)]

# Start all threads
for thread in pool:
    thread.start()

# Give the workers some work to do
work_count = 0
for dir in args:
    if os.path.exists(dir):
        work_count += 1
        dir_q.put(dir)

print 'Assigned %s dirs to workers' % work_count

# Now get all the results
while work_count > 0:
    # Blocking 'get' from a Queue.
    result = result_q.get()
    print 'From thread %s: %s files found in dir %s' % (
        result[0], len(result[2]), result[1])
    work_count -= 1

# Ask threads to die and wait for them to do it
for thread in pool:
    thread.join()

if name == 'main': import sys main(sys.argv[1:])

angelogoncalve commented 6 years ago

You can also do this as an another solution I think:

import subprocess import time

class LedDisplay(object): def init(self): self._proc = subprocess.Popen(["/home/pi/display16x32/rpi-rgb-led-matrix/text-example", "-r", "16", "-f", "/home/pi/4x6.bdf"], stdin=subprocess.PIPE)

def clear(self):
    self._proc.stdin.write('\n')

def display(self, line1, line2=None):
    self._proc.stdin.write('%s\n' % line1)
    if line2:
        self._proc.stdin.write('%s\n' % line2)

def stop(self):
    self._proc.communicate()

disp = LedDisplay()

def main(): while True: game_info = ['portuguese', 1] disp.display('Team: %s' % game_info[0], 'Score: %s' % game_info[1]) time.sleep(5)

if name == "main": main()

angelogoncalve commented 6 years ago

self._proc = subprocess.Popen(["/home/pi/display16x32/rpi-rgb-led-matrix/text-example", "-r", "16", "-f", "/home/pi/display16x32/rpi-rgb-led-matrix/fonts/4x6.bdf"], stdin=subprocess.PIPE)

nschwanke commented 6 years ago

Thanks for your responses! I decided to go a different route and do the scripting completely in python and utilize the matrix.Clear() function at the end of loops running the animations.

angelogoncalve commented 6 years ago

Yes you can do that if the animation is static and use the method Clear() in the matrix (matrix.Clear()). But if the animation has a scroll during a time or a number of times I think this solution will not work, and you need to use threads, for when you ask for a method to show in the matrix to kill all the threads that are running and do the new thread.

hzeller commented 6 years ago

Yes, doing things programmatically directly in Python this is the right approach @nschwanke Killing and restarting the process would just ask for trouble.

angelogoncalve commented 6 years ago

Dear Henner I have a draw number function and a scroll text fuction and between them I use threads to not have sobrepose the pixels and also matrix.Clear() function.