works reliably - especially when using gunicorn

This commit is contained in:
Christoph Haas 2026-01-16 22:36:32 +01:00
parent c937450f92
commit 39cd98823a
7 changed files with 253 additions and 38 deletions

19
README
View file

@ -1,19 +0,0 @@
verschiedene threads zur auswahl, die vom flask gestartet/gestoppt werden können.
- statisches bild anzeigen und sich beenden
-
ein task, der die ganze zeit im hintergrund läuft.
- statisches bild anzeigen
- eine Routine
ein globales bild. (locking?)
die einzelnen
https://github.com/hzeller/rpi-rgb-led-matrix
./main.py
curl http://localhost:6000/start/helloworld
feh --reload 0.15 image.png
https://pypi.org/project/finnhub-python/

139
README.md Normal file
View file

@ -0,0 +1,139 @@
# Benutzung
- als **root** ausführen
- ./main.py
verschiedene threads zur auswahl, die vom flask gestartet/gestoppt werden können.
- statisches bild anzeigen und sich beenden
-
ein task, der die ganze zeit im hintergrund läuft.
- statisches bild anzeigen
- eine Routine
ein globales bild. (locking?)
die einzelnen
https://github.com/hzeller/rpi-rgb-led-matrix
./main.py
curl http://localhost:6000/start/helloworld
feh --reload 0.15 image.png
https://pypi.org/project/finnhub-python/
# Locale
Für deutsche Ausgabe (z.B. den Monatsnamen):
- als root: `dpkg-reconfigure locales`
- **de_DE.UTF-8** aktivieren
# Flackern
## Erkenntnisse
"Hardware pulsing" macht zwar ein ruhiges Bild aber viele Artefakte.
Wenn man "hardware pulsing" abschaltet, muss die Software die Ansteuerung der
Panels machen. Das ist für den Raspi stressiger.
Am besten startet man den Webserver mit:
chrt -f 20 ./main.py
Dann werden Hintergrundprozesse unterdrückt und Artefakte vermieden.
Interessante Optionen:
- `options.gpio_slowdown`: Mehr=mehr Flackern. Weniger=Artifakte/Rauschen im linken Bereich.
- `options.pwm_bits`: Mehr=mehr Flackern. Weniger=weniger Helligkeitsabstufungen
## Einstellung 1
- kaum sichtbare Artefakte
- ca. 10 Hz Bildwiederholrate
- 4 Panels
```py
options = RGBMatrixOptions()
options.rows = MATRIX_HEIGHT
options.cols = MATRIX_WIDTH
options.chain_length = NUMBER_OF_PANELS
options.parallel = 1
options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat'
options.gpio_slowdown = 10 # try 2, 4, or even 5
options.drop_privileges = False
options.pwm_bits = 10 # default 1112; lower = faster refresh
```
## Einstellung 2
- leichte Artifakte, hält 10 Minuten durch
- ca. 30 Hz Bildwiederholrate
- 3 Panels
```py
options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat'
options.gpio_slowdown = 6 # try 2, 4, or even 5
options.disable_hardware_pulsing = True
#options.show_refresh_rate = True
options.drop_privileges = False
```
## Einstellung 3
- kaum Artefakte
- ca. 30 Hz
- 3 Panels
```py
options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat'
options.gpio_slowdown = 7 # try 2, 4, or even 5
options.disable_hardware_pulsing = True
options.drop_privileges = False
options.pwm_bits = 10 # default 1112; lower = faster refresh
```
## Einstellung 4
- 20-30 Hz
- 3 Panels
- keine Artefakte
```py
options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat'
options.gpio_slowdown = 8 # try 2, 4, or even 5
options.disable_hardware_pulsing = True
options.drop_privileges = False
## Einstellung 5
- 50 Hz
- 4 Panels
- keine Artefakte
- hält es langfristig damit durch?
options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat'
options.gpio_slowdown = 4 # try 2, 4, or even 5
options.disable_hardware_pulsing = True
options.drop_privileges = False
options.pwm_bits = 5 # default 1112; lower = faster refresh
# Links
- https://github.com/hzeller/rpi-rgb-led-matrix
# TODO
Das ganze als minimalen Case ohne Multithreading und Klassen.
Ist das Geflacker dann weg?
# 2026-01-16
- git pull
- spannung auf 5,01 Volt reduziert (von 5,2 Volt)
- gpio-slowdown auf 4
- nach dem stop (ctrl-c) und start 10 sekunden warten
- mit "gunicorn -w 1 -k gthread main:app" laufen lassen

View file

@ -1,5 +1,6 @@
from rgbmatrix import RGBMatrix, RGBMatrixOptions from rgbmatrix import RGBMatrix, RGBMatrixOptions
from PIL import ImageFont, Image from PIL import ImageFont, Image
#from time import sleep
# Das Bild nur speichern. Nicht die LED-Matrix ansteuern. # Das Bild nur speichern. Nicht die LED-Matrix ansteuern.
DEV = False DEV = False
@ -18,22 +19,39 @@ class MyMatrix:
try: try:
self.font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-ExtraLight.ttf", 18) self.font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-ExtraLight.ttf", 18)
self.large_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-ExtraLight.ttf", 32)
except IOError: except IOError:
self.font = ImageFont.load_default() self.font = ImageFont.load_default()
self.large_font = ImageFont.load_default()
self.width = MATRIX_WIDTH * NUMBER_OF_PANELS
self.height = MATRIX_HEIGHT
if not DEV: if not DEV:
options = RGBMatrixOptions() options = RGBMatrixOptions()
options.rows = MATRIX_HEIGHT options.rows = MATRIX_HEIGHT
options.cols = MATRIX_WIDTH options.cols = MATRIX_WIDTH
options.chain_length = NUMBER_OF_PANELS options.chain_length = NUMBER_OF_PANELS
options.parallel = 1 #options.parallel = 1
options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat' #options.hardware_mapping = 'regular' # change if using Adafruit HAT, e.g. 'adafruit-hat'
options.gpio_slowdown = 5 # try 2, 4, or even 5 options.gpio_slowdown = 4 # try 2, 4, or even 5
options.disable_hardware_pulsing = True #options.disable_hardware_pulsing = True
#options.show_refresh_rate = True
options.drop_privileges = False options.drop_privileges = False
options.brightness = 60 # scale down brightness if needed
options.pwm_bits = 8 # default 1112; lower = faster refresh
self.matrix = RGBMatrix(options=options) self.matrix = RGBMatrix(options=options)
#sleep(0.2)
#self.matrix.Clear()
#sleep(0.2)
#self.matrix.Clear()
#sleep(0.2)
#self.matrix.Clear()
#sleep(0.2)
def show(self, image): def show(self, image):
"""Save the image to disk in DEV mode. Or send it to the LED matrix panel.""" """Save the image to disk in DEV mode. Or send it to the LED matrix panel."""
if DEV: if DEV:

12
main.py
View file

@ -15,6 +15,7 @@ scenes = {}
for file in os.listdir(SCENES_DIR): for file in os.listdir(SCENES_DIR):
if file.endswith(".py") and file != "__init__.py": if file.endswith(".py") and file != "__init__.py":
module_name = file[:-3] module_name = file[:-3]
print(f"Loading scene: {module_name}")
module = importlib.import_module(f"{SCENES_DIR}.{module_name}") module = importlib.import_module(f"{SCENES_DIR}.{module_name}")
scenes[module_name] = module scenes[module_name] = module
@ -23,7 +24,7 @@ app = Flask(__name__)
# Thread and control event # Thread and control event
animation_thread = None animation_thread = None
stop_event = Event() stop_event = Event()
image_buffer = Image.new("RGB", (128, 64)) #image_buffer = Image.new("RGB", (128, 64))
# Example background animation function # Example background animation function
def run_animation(name): def run_animation(name):
@ -50,7 +51,6 @@ def start_animation(scene_name):
# Reset stop event and start new thread # Reset stop event and start new thread
stop_event.clear() stop_event.clear()
#animation_thread = Thread(target=scenes[scene_name].start, args=(image_buffer, stop_event), daemon=True)
animation_thread = Thread(target=scenes[scene_name].start, args=(stop_event,), daemon=True) animation_thread = Thread(target=scenes[scene_name].start, args=(stop_event,), daemon=True)
animation_thread.start() animation_thread.start()
@ -59,6 +59,11 @@ def start(animation_name):
start_animation(animation_name) start_animation(animation_name)
return jsonify({"status": "started", "animation": animation_name}) return jsonify({"status": "started", "animation": animation_name})
@app.route("/reset")
def reset():
lib.display.matrix.matrix.Clear()
return "ok"
@app.route("/stop") @app.route("/stop")
def stop(): def stop():
global animation_thread, stop_event global animation_thread, stop_event
@ -68,5 +73,6 @@ def stop():
return jsonify({"status": "stopped"}) return jsonify({"status": "stopped"})
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True, port=6000) app.run(port=6000)
#app.run(debug=True, port=6000)

39
scenes/clock.py Normal file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env python3
import io
from time import sleep
from PIL import Image, ImageDraw, ImageFont
import lib.display
import datetime
import locale
locale.setlocale(locale.LC_TIME, "de_DE.UTF-8")
def start(stop_event, text="Hello World!"):
image_buffer = lib.display.matrix.buffer()
draw = ImageDraw.Draw(image_buffer)
while not stop_event.is_set():
# All to black
draw.rectangle( ((0,0),(lib.display.matrix.width-1,lib.display.matrix.height-1)), fill=(0,50,0) )
#draw.rectangle( ((0,0),(lib.display.matrix.width-1,lib.display.matrix.height-1)), fill=(255,255,255) )
# Current time
now = datetime.datetime.now()
#now_string = now.strftime('%d. %B %Y %H:%M:%S')
# Date into top left
draw.text((0, 0), now.strftime('%d. %B %Y'), fill=(255, 255, 0), font=lib.display.matrix.font)
# Time into middle
draw.text((20, 20), now.strftime('%H:%M:%S'), fill=(0, 255, 255), font=lib.display.matrix.large_font)
float_seconds = now.second + now.microsecond/10**6
length = 64*4/60.0*float_seconds
# 64*4 = 60
# x = seconds
# x=64*4/60*seconds
#draw.line( ((0,63), (now.second,63)), fill=(128,128,128), width=1)
draw.line( ((0,62), (length,62)), fill=(128,128,128), width=2)
lib.display.matrix.show(image_buffer)
sleep(0.05)

32
scenes/colorclock.py Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env python3
import io
from time import sleep
from PIL import Image, ImageDraw, ImageFont
import lib.display
import datetime
import locale
locale.setlocale(locale.LC_TIME, "de_DE.UTF-8")
def start(stop_event, text="Hello World!"):
while not stop_event.is_set():
image_buffer = lib.display.matrix.buffer()
draw = ImageDraw.Draw(image_buffer)
now = datetime.datetime.now()
now_string = now.strftime('%d. %B %Y %H:%m:%S')
draw.text((0, 0), now_string, fill=(255, 255, 0), font=lib.display.matrix.font)
draw.text((0, 20), now_string, fill=(0, 255, 255), font=lib.display.matrix.font)
draw.text((0, 40), now_string, fill=(255, 0, 255), font=lib.display.matrix.font)
float_seconds = now.second + now.microsecond/10**6
length = 64*4/60.0*float_seconds
# 64*4 = 60
# x = seconds
# x=64*4/60*seconds
#draw.line( ((0,63), (now.second,63)), fill=(128,128,128), width=1)
draw.line( ((0,62), (length,62)), fill=(128,128,128), width=2)
lib.display.matrix.show(image_buffer)
sleep(0.05)

View file

@ -3,21 +3,21 @@
import io import io
from time import sleep from time import sleep
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
#from lib import display
import lib.display import lib.display
def start(stop_event, text="Hello World!"): def start(stop_event, text="Hello World!"):
t = 0 #t = 0
while not stop_event.is_set():
#image_buffer = Image.new("RGB", (lib.display.matrix_width, lib.display.matrix_height))
image_buffer = lib.display.matrix.buffer() image_buffer = lib.display.matrix.buffer()
draw = ImageDraw.Draw(image_buffer) draw = ImageDraw.Draw(image_buffer)
draw.text((0, 0), f"Hello World {t}", fill=(255, 255, 255), font=lib.display.matrix.font) draw.rectangle( ((0,0),(lib.display.matrix.width-1,lib.display.matrix.height-1)), fill=(0,0,90) )
draw.text((0, 0), "Hello World", fill=(255, 255, 255), font=lib.display.matrix.font)
#draw.text((0, 0), f"Hello World {t}", fill=(255, 255, 255), font=lib.display.matrix.font)
t += 1 #t += 1
print(t) #print(t)
lib.display.matrix.show(image_buffer) lib.display.matrix.show(image_buffer)
while not stop_event.is_set():
#sleep(0.2) sleep(1)