works reliably - especially when using gunicorn
This commit is contained in:
parent
c937450f92
commit
39cd98823a
7 changed files with 253 additions and 38 deletions
19
README
19
README
|
|
@ -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
139
README.md
Normal 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 11–12; 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 11–12; 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 11–12; 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
|
||||||
|
|
@ -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 11–12; 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
12
main.py
|
|
@ -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
39
scenes/clock.py
Normal 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
32
scenes/colorclock.py
Normal 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)
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue