diff --git a/README b/README deleted file mode 100644 index a8b6c9a..0000000 --- a/README +++ /dev/null @@ -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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..abb08d2 --- /dev/null +++ b/README.md @@ -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 diff --git a/lib/display.py b/lib/display.py index 3ad61e4..9b2219a 100644 --- a/lib/display.py +++ b/lib/display.py @@ -1,5 +1,6 @@ from rgbmatrix import RGBMatrix, RGBMatrixOptions from PIL import ImageFont, Image +#from time import sleep # Das Bild nur speichern. Nicht die LED-Matrix ansteuern. DEV = False @@ -18,22 +19,39 @@ class MyMatrix: try: 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: 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: 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 = 5 # try 2, 4, or even 5 - options.disable_hardware_pulsing = True + #options.parallel = 1 + #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.show_refresh_rate = True 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) + #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): """Save the image to disk in DEV mode. Or send it to the LED matrix panel.""" if DEV: diff --git a/main.py b/main.py index 03af24d..f9dd92c 100755 --- a/main.py +++ b/main.py @@ -15,6 +15,7 @@ scenes = {} for file in os.listdir(SCENES_DIR): if file.endswith(".py") and file != "__init__.py": module_name = file[:-3] + print(f"Loading scene: {module_name}") module = importlib.import_module(f"{SCENES_DIR}.{module_name}") scenes[module_name] = module @@ -23,7 +24,7 @@ app = Flask(__name__) # Thread and control event animation_thread = None stop_event = Event() -image_buffer = Image.new("RGB", (128, 64)) +#image_buffer = Image.new("RGB", (128, 64)) # Example background animation function def run_animation(name): @@ -50,7 +51,6 @@ def start_animation(scene_name): # Reset stop event and start new thread 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.start() @@ -59,6 +59,11 @@ def start(animation_name): start_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") def stop(): global animation_thread, stop_event @@ -68,5 +73,6 @@ def stop(): return jsonify({"status": "stopped"}) if __name__ == "__main__": - app.run(debug=True, port=6000) + app.run(port=6000) + #app.run(debug=True, port=6000) diff --git a/scenes/clock.py b/scenes/clock.py new file mode 100644 index 0000000..a7d0f7d --- /dev/null +++ b/scenes/clock.py @@ -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) + diff --git a/scenes/colorclock.py b/scenes/colorclock.py new file mode 100644 index 0000000..b380a5f --- /dev/null +++ b/scenes/colorclock.py @@ -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) + diff --git a/scenes/helloworld.py b/scenes/helloworld.py index d23055a..0b059c7 100644 --- a/scenes/helloworld.py +++ b/scenes/helloworld.py @@ -3,21 +3,21 @@ import io from time import sleep from PIL import Image, ImageDraw, ImageFont -#from lib import display import lib.display def start(stop_event, text="Hello World!"): - t = 0 + #t = 0 + image_buffer = lib.display.matrix.buffer() + draw = ImageDraw.Draw(image_buffer) + 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 + #print(t) + + lib.display.matrix.show(image_buffer) 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() - draw = ImageDraw.Draw(image_buffer) - draw.text((0, 0), f"Hello World {t}", fill=(255, 255, 255), font=lib.display.matrix.font) - t += 1 - print(t) - - lib.display.matrix.show(image_buffer) - - #sleep(0.2) + sleep(1)