Code cleanup

This commit is contained in:
T 2024-10-28 20:48:28 +01:00
parent 8dc0480c18
commit c38a840d42
3 changed files with 283 additions and 228 deletions

View File

@ -6,6 +6,7 @@ import random
import time import time
import math import math
import numpy as np import numpy as np
# Groesse des Bildschirms bestimmen # Groesse des Bildschirms bestimmen
Nx = int(sys.argv[1]) Nx = int(sys.argv[1])
Ny = int(sys.argv[2]) Ny = int(sys.argv[2])

122
config.py
View File

@ -1,4 +1,21 @@
import logging import logging
from dataclasses import dataclass
from typing import Union, List
@dataclass
class AppConfig:
guiname: str
name: str
cmd: Union[List[str], str]
path: str = "./apps/"
persistent: bool = False
white: bool = False
def __post_init__(self):
if not isinstance(self.cmd, list):
self.cmd = [self.cmd]
# width # width
ScreenX = 80 ScreenX = 80
@ -14,81 +31,66 @@ NoDataTimeout = 40
LogLevel = logging.DEBUG LogLevel = logging.DEBUG
UseGui = True UseGui = False
GuiFPS = 20 GuiFPS = 20
GuiScaleFactor = 15 GuiScaleFactor = 15
WebHost = "0.0.0.0" WebHost = "0.0.0.0"
WebPort = 8000 WebPort = 8000
# first app is always running as default
Apps = [ Apps = [
# first app is always running as default
AppConfig(guiname="Backlight", name="backlight", cmd="./backlight.py", white=True),
{"name": "backlight", "cmd": "apps/backlight.py", "white": True}, AppConfig(guiname="Lines", name="lines", cmd="./lines.py"),
AppConfig(guiname="Wolfram", name="wolfram", cmd="./wolfram.py"),
{"guiname": "Lines", "name": "lines", "cmd": "./lines.py", "persistent": False, "path": "./apps/"}, AppConfig(guiname="Digi Clock", name="digiclock", cmd="./digi_clock.py"),
{"guiname": "Wolfram", "name": "wolfram", "cmd": "./wolfram.py", "persistent": False, "path": "./apps/"}, AppConfig(guiname="Text Scroller MQTT", name="textscroll", cmd="./textscroll.py"),
{"guiname": "Digi Clock", "name": "digiclock", "cmd": "./digi_clock.py", "persistent": False, "path": "./apps/"}, AppConfig(guiname="Flicker", name="flicker", cmd="./flicker"),
{"guiname": "Text Scroller MQTT", "name": "textscroll", "cmd": "./textscroll.py", "persistent": False, "path": "./apps/"},
{"guiname": "Flicker", "name": "flicker", "cmd": "apps/flicker"},
{"guiname": "Pixelflut", "name": "pixelflut", "cmd": "apps/pixelflut", "persistent": True},
# {"guiname": "Pong", "name": "pong", "cmd": "apps/pong.py"},
{"guiname": "YoutubeDL", "name": "youtubedl", "cmd": "apps/youtubedl.sh", "persistent": False},
# {"guiname": "Show Framebuffer", "name": "fbcp", "cmd": ["apps/fbcp", "/dev/fb0"]},
{"guiname": "Strobo", "name": "strobo", "cmd": "apps/strobo.py"},
# {"guiname": "Beispiel", "name": "example", "cmd": "apps/example.py"},
# {"guiname": "Beispiel2", "name": "example2", "cmd": "apps/example2.py", "white": True},
{"guiname": "Fibonacci Clock", "name": "fibonacci-clock", "cmd": "apps/fibonacci-clock.py"},
{"guiname": "Wget Video/Gif/Images", "name": "wget", "cmd": "apps/wget.sh", "persistent": False},
AppConfig(guiname="Pixelflut", name="pixelflut", cmd="./pixelflut", persistent=True),
# App(guiname="Pong", name="pong", cmd="pong.py"),
AppConfig(guiname="YoutubeDL", name="youtubedl", cmd="./youtubedl.sh"),
# App(guiname="Show Framebuffer", name="fbcp", cmd=["fbcp", "/dev/fb0"]),
AppConfig(guiname="Strobo", name="strobo", cmd="./strobo.py"),
# App(guiname="Beispiel", name="example", cmd="example.py"),
# App(guiname="Beispiel2", name="example2", cmd="example2.py", white=True),
AppConfig(guiname="Fibonacci Clock", name="fibonacci-clock", cmd="./fibonacci-clock.py"),
AppConfig(guiname="Wget Video/Gif/Images", name="wget", cmd="./wget.sh"),
# juergen/pixelfoo # juergen/pixelfoo
{"guiname": "Congress noise", "name": "cnoise", "cmd": "pixelfoo/target/release/cnoise", "persistent": False}, AppConfig(guiname="Congress noise", name="cnoise", cmd="./cnoise", path="pixelfoo/target/release/"),
{"guiname": "Game of Life", "name": "life", "cmd": "pixelfoo/target/release/life", "persistent": False}, AppConfig(guiname="Game of Life", name="life", cmd="./life", path="pixelfoo/target/release/"),
{"guiname": "Matrix Code", "name": "matrix-code", "cmd": "pixelfoo/target/release/matrix-code", "persistent": False}, AppConfig(guiname="Matrix Code", name="matrix-code", cmd="./matrix-code", path="pixelfoo/target/release/"),
{"guiname": "Lorenz Attractor", "name": "lorenz", "cmd": "pixelfoo/target/release/lorenz", "persistent": False}, AppConfig(guiname="Lorenz Attractor", name="lorenz", cmd="./lorenz", path="pixelfoo/target/release/"),
{"guiname": "Dual Moodlight", "name": "bimood", "cmd": "pixelfoo/target/release/bimood", "persistent": False}, AppConfig(guiname="Dual Moodlight", name="bimood", cmd="./bimood", path="pixelfoo/target/release/"),
{"guiname": "Maze", "name": "maze", "cmd": "pixelfoo/target/release/maze", "persistent": False}, AppConfig(guiname="Maze", name="maze", cmd="./maze", path="pixelfoo/target/release/"),
{"guiname": "Dual Maze", "name": "dualmaze", "cmd": "pixelfoo/target/release/dualmaze", "persistent": False, "persistent": False}, AppConfig(guiname="Dual Maze", name="dualmaze", cmd="./dualmaze", path="pixelfoo/target/release/"),
{"guiname": "Predator & Prey", "name": "predprey", "cmd": "pixelfoo/target/release/predprey", "persistent": False}, AppConfig(guiname="Predator & Prey", name="predprey", cmd="./predprey", path="pixelfoo/target/release/"),
# { "guiname": "Beat Saber Ceiling", "name": "beatsaberceiling", "cmd": "./beatsaberceiling.py", "path": "apps/beatsaberceiling" }, # App(guiname="Beat Saber Ceiling", name="beatsaberceiling", cmd="./beatsaberceiling.py", path="beatsaberceiling"),
# mathpixel # mathpixel
{"guiname": "Structure formation", "name": "swifthohenberg", "cmd": "apps/swifthohenberg.py", "persistent": False}, AppConfig(guiname="Structure formation", name="swifthohenberg", cmd="./swifthohenberg.py"),
{"guiname": "Quadratisch", "name": "quadratic", "cmd": "apps/quadratic.py"}, AppConfig(guiname="Quadratisch", name="quadratic", cmd="./quadratic.py"),
{"guiname": "Pendel", "name": "pendulum", "cmd": "apps/pendlum.py"}, AppConfig(guiname="Pendel", name="pendulum", cmd="./pendlum.py"),
{"guiname": "Konvergenz", "name": "convergence", "cmd": "apps/convergence.py"}, AppConfig(guiname="Konvergenz", name="convergence", cmd="./convergence.py"),
{"guiname": "Sinic", "name": "sinic", "cmd": "apps/sinic.py"}, AppConfig(guiname="Sinic", name="sinic", cmd="./sinic.py"),
{"guiname": "Sinic 2", "name": "sinic2", "cmd": "apps/sinic2.py"}, AppConfig(guiname="Sinic 2", name="sinic2", cmd="./sinic2.py"),
# pixelthud # pixelthud
{"guiname": "Fading Pixels", "name": "fadingpxls", "cmd": "apps/fading_pixels.py", "persistent": False}, AppConfig(guiname="Fading Pixels", name="fadingpxls", cmd="./fading_pixels.py"),
{"guiname": "Plane Wave", "name": "planewave", "cmd": "apps/plane_wave.py", "persistent": False}, AppConfig(guiname="Plane Wave", name="planewave", cmd="./plane_wave.py"),
{"guiname": "Rock-paper-scissors-spock-lizard", "name": "rps", "cmd": "apps/rps.py", "persistent": False}, AppConfig(guiname="Rock-paper-scissors-spock-lizard", name="rps", cmd="./rps.py"),
{"guiname": "Doom Fire", "name": "doomfire", "cmd": "apps/doom_fire_psx2.py", "persistent": False}, AppConfig(guiname="Doom Fire", name="doomfire", cmd="./doom_fire_psx2.py"),
# {"guiname": "Maxwell FDTD", "name": "fdtd", "cmd": "apps/fdtd.py", "persistent": False}, # App(guiname="Maxwell FDTD", name="fdtd", cmd="./fdtd.py"),
#{"guiname": "Stream", "name": "stream", "cmd": "apps/stream.sh", "persistent": False}, # App(guiname="Stream", name="stream", cmd="./stream.sh"),
#{"guiname": "Wget Video/Gif/Images", "name": "wget", "cmd": "apps/wget.sh", "persistent": False}, # App(guiname="Wget Video/Gif/Images", name="wget", cmd="./wget.sh"),
#{"guiname": "Tetris", "name": "tetris", "cmd": "apps/deckentetris/deckentetris.py", "persistent": False}, # App(guiname="Tetris", name="tetris", cmd="./deckentetris/deckentetris.py"),
#{"guiname": "SkyScrapper", "name": "sky", "cmd": "apps/weather/main.py"}, # App(guiname="SkyScrapper", name="sky", cmd="./weather/main.py"),
#{"guiname": "Strobo", "name": "strobo", "cmd": "apps/strobo.py", "persistent": False}, # App(guiname="Strobo", name="strobo", cmd="./strobo.py"),
#{"guiname": "Snake", "name": "snake", "cmd": "apps/snake.py", "persistent": False}, # App(guiname="Snake", name="snake", cmd="./snake.py"),
#{"name": "gif", "cmd": "apps/gif.sh"}, # App(name="gif", cmd="./gif.sh"),
#{"name": "colormap", "cmd": "apps/colormap.py"}, # App(name="colormap", cmd="./colormap.py"),
] ]
# load additional apps from config/
# import os
# import os.path
# configs = os.listdir("configs/")
# import importlib
# for config in configs:
# file, ext = os.path.splitext(os.path.basename(config))
# if ext != ".py" or file[0] == "_":
# continue
# modname = "configs."+file
# lib = importlib.import_module(modname)
# Apps += lib.Apps

252
main.py
View File

@ -1,24 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import config
import subprocess
import os
import os.path
import serial
import threading
import json import json
import bottle
from bottle import route, run, request, post
import time
import sys
import logging import logging
import math import math
import numpy as np import os
import string import string
import subprocess
import threading
import time
from collections import OrderedDict from collections import OrderedDict
import bottle
import numpy as np
import scipy.misc import scipy.misc
import serial
import config
logging.basicConfig(filename='pixelserver.log', level=config.LogLevel) logging.basicConfig(filename='pixelserver.log', level=config.LogLevel)
running = True running = True
######################################################################## ########################################################################
# Utils # # Utils #
######################################################################## ########################################################################
@ -26,42 +28,52 @@ class DataSource:
def __init__(self, initial): def __init__(self, initial):
self.data = initial self.data = initial
self.listeners = [] self.listeners = []
def getData(self): def getData(self):
return self.data return self.data
def addListener(self, listener): def addListener(self, listener):
self.listeners.append(listener) self.listeners.append(listener)
return self return self
def pushData(self, data): def pushData(self, data):
self.data = data self.data = data
for listener in self.listeners: for listener in self.listeners:
with listener: with listener:
listener.notify_all() listener.notify_all()
class WatchDog(threading.Thread): class WatchDog(threading.Thread):
def __init__(self, check, action): def __init__(self, check, action):
super().__init__() super().__init__(daemon=True)
self.check = check self.check = check
self.action = action self.action = action
self.running = True self.running = True
def run(self): def run(self):
while running and self.running: while running and self.running:
if self.check(): if self.check():
logging.error("Watchdog: Executed") logging.error("Watchdog: Executed")
self.action() self.action()
time.sleep(1) time.sleep(1)
def stop(self): def stop(self):
self.running = False self.running = False
class LogReader(threading.Thread): class LogReader(threading.Thread):
def __init__(self, runner): def __init__(self, runner):
super().__init__() super().__init__(daemon=True)
self.runner = runner self.runner = runner
self.log = "" self.log = ""
self.running = True self.running = True
def clear(self): def clear(self):
self.log = "" self.log = ""
def getLog(self): def getLog(self):
return self.log return self.log
def run(self): def run(self):
logging.info("LogReader started") logging.info("LogReader started")
while running and self.running: while running and self.running:
@ -72,34 +84,41 @@ class LogReader(threading.Thread):
logging.error(str(e)) logging.error(str(e))
time.sleep(1) time.sleep(1)
logging.info("LogReader closed") logging.info("LogReader closed")
def stop(self): def stop(self):
self.running = False self.running = False
class Frame: class Frame:
def __init__(self, buffer, channels=3): def __init__(self, buffer, channels=3):
self.buffer = buffer self.buffer = buffer
self.created = time.time() self.created = time.time()
self.channels = channels self.channels = channels
def clone(self): def clone(self):
f = Frame(self.buffer + 0, self.channels) f = Frame(self.buffer + 0, self.channels)
f.created = self.created f.created = self.created
return f return f
######################################################################## ########################################################################
# GUI # # GUI #
######################################################################## ########################################################################
if config.UseGui: if config.UseGui:
import pygame import pygame
class Gui(threading.Thread): class Gui(threading.Thread):
def __init__(self, datasource): def __init__(self, datasource):
super().__init__() super().__init__(daemon=True)
self.cv = threading.Condition() self.cv = threading.Condition()
self.datasource = datasource.addListener(self.cv) self.datasource = datasource.addListener(self.cv)
def run(self): def run(self):
last_frame = time.time() last_frame = time.time()
logging.info("Starting GUI") logging.info("Starting GUI")
sf = config.GuiScaleFactor sf = config.GuiScaleFactor
pygame.init()
screen = pygame.display.set_mode((sf * config.ScreenX, sf * config.ScreenY)) screen = pygame.display.set_mode((sf * config.ScreenX, sf * config.ScreenY))
pygame.display.set_caption("Pixelserver - GUI Vis") pygame.display.set_caption("Pixelserver - GUI Vis")
while running: while running:
@ -123,25 +142,28 @@ if config.UseGui:
# logging.debug("Time to gui: " + str(time.time() - frame.created)) # logging.debug("Time to gui: " + str(time.time() - frame.created))
pygame.display.flip() pygame.display.flip()
if time.time() < last_frame + 1 / config.GuiFPS: if time.time() < last_frame + 1 / config.GuiFPS:
time.sleep(time.time()-(last_frame+1/config.GuiFPS)) time.sleep(max(0.01, time.time() - (last_frame + 1 / config.GuiFPS)))
# time.sleep(0.01) # time.sleep(0.01)
last_frame = time.time() last_frame = time.time()
logging.info("Closing GUI") logging.info("Closing GUI")
def join(self):
def join(self, **kwargs):
with self.cv: with self.cv:
self.cv.notify_all() self.cv.notify_all()
super().join() super().join()
######################################################################## ########################################################################
# Serial # # Serial #
######################################################################## ########################################################################
class SerialWriter(threading.Thread): class SerialWriter(threading.Thread):
def __init__(self, datasource): def __init__(self, datasource):
super().__init__() super().__init__(daemon=True)
self.cv = threading.Condition() self.cv = threading.Condition()
self.datasource = datasource.addListener(self.cv) self.datasource = datasource.addListener(self.cv)
self.updateGamma = False self.updateGamma = False
def run(self): def run(self):
should_connect = True should_connect = True
ser = None ser = None
@ -176,32 +198,33 @@ class SerialWriter(threading.Thread):
logging.debug("Time to gui: " + str(time.time() - frame.created)) logging.debug("Time to gui: " + str(time.time() - frame.created))
ser.flush() ser.flush()
except Exception as e: except Exception as e:
if ser != None: if ser is not None:
ser.close() ser.close()
ser = None ser = None
logging.warning("Serial was close because: "+str(e)) logging.warning(f"Serial was close because: {e}")
should_connect = True should_connect = True
time.sleep(5) time.sleep(5)
logging.info("Closing SerialWriter") logging.info("Closing SerialWriter")
def join(self):
def join(self, **kwargs):
with self.cv: with self.cv:
self.cv.notify_all() self.cv.notify_all()
super().join() super().join()
def setGamma(self, r, g, b, w): def setGamma(self, r, g, b, w):
with self.cv: with self.cv:
self.r, self.g, self.b, self.w = r, g, b, w self.r, self.g, self.b, self.w = r, g, b, w
self.updateGamma = True self.updateGamma = True
self.cv.notify_all() self.cv.notify_all()
######################################################################## ########################################################################
# App # # App #
######################################################################## ########################################################################
class App(threading.Thread): class App(threading.Thread):
def __init__(self, cmd, param, listener, is_persistent, is_white=False, path="."): def __init__(self, cmd, param, listener, is_persistent, is_white=False, path="."):
super().__init__() super().__init__(daemon=True)
# start app # start app
if type(cmd) != list:
cmd = [cmd,]
args = cmd + [str(config.ScreenX), str(config.ScreenY), param] args = cmd + [str(config.ScreenX), str(config.ScreenY), param]
self.app = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, cwd=path) self.app = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, cwd=path)
self.last_update = time.time() self.last_update = time.time()
@ -215,20 +238,21 @@ class App(threading.Thread):
self.listener = listener self.listener = listener
self.is_persistent = is_persistent self.is_persistent = is_persistent
self.is_white = is_white self.is_white = is_white
def run(self): def run(self):
while running and self.running and self.alive(): while running and self.running and self.alive():
oshandle = self.app.stdout.fileno() oshandle = self.app.stdout.fileno()
try: try:
bytes = 4 if self.is_white else 3 channels = 4 if self.is_white else 3
data = os.read(oshandle, config.ScreenX*config.ScreenY*bytes) data = os.read(oshandle, config.ScreenX * config.ScreenY * channels)
assert len(data) == config.ScreenX*config.ScreenY*bytes assert len(data) == config.ScreenX * config.ScreenY * channels
buffer = np.frombuffer(data, dtype=np.uint8, count=config.ScreenX*config.ScreenY*bytes) buffer = np.frombuffer(data, dtype=np.uint8, count=config.ScreenX * config.ScreenY * channels)
buffer = buffer.reshape((config.ScreenY, config.ScreenX, bytes)) buffer = buffer.reshape((config.ScreenY, config.ScreenX, channels))
frame = Frame(buffer, channels=bytes) frame = Frame(buffer, channels=channels)
self.last_update = time.time() self.last_update = time.time()
self.datasource.pushData(frame) self.datasource.pushData(frame)
except Exception as e: except:
logging.debug("Exception in App.run") logging.debug("Exception in App.run")
with self.listener: with self.listener:
self.listener.notify_all() self.listener.notify_all()
@ -238,8 +262,10 @@ class App(threading.Thread):
self.logreader.join() self.logreader.join()
self.app.wait() self.app.wait()
logging.debug("App stopped") logging.debug("App stopped")
def alive(self): def alive(self):
return self.app.poll() == None return self.app.poll() is None
def stop(self): def stop(self):
self.running = False self.running = False
self.app.kill() self.app.kill()
@ -249,20 +275,24 @@ class App(threading.Thread):
self.logreader.stop() self.logreader.stop()
self.app.wait() self.app.wait()
logging.debug("App stopped") logging.debug("App stopped")
def getLog(self): def getLog(self):
return self.logreader.getLog() return self.logreader.getLog()
def terminateApp(self): def terminateApp(self):
logging.error("Terminate app!") logging.error("Terminate app!")
self.stop() self.stop()
def isAppTimedOut(self): def isAppTimedOut(self):
return time.time() - self.last_update > config.NoDataTimeout return time.time() - self.last_update > config.NoDataTimeout
######################################################################## ########################################################################
# Main # # Main #
######################################################################## ########################################################################
class AppRunner(threading.Thread): class AppRunner(threading.Thread):
def __init__(self): def __init__(self):
super().__init__() super().__init__(daemon=True)
self.last_crashlog = "" self.last_crashlog = ""
self.currentApp = -1 self.currentApp = -1
self.requestedApp = 0 self.requestedApp = 0
@ -275,49 +305,54 @@ class AppRunner(threading.Thread):
self.persistent_apps = {} self.persistent_apps = {}
self.filters = OrderedDict() self.filters = OrderedDict()
# start persistent apps # start persistent apps
for app, i in zip(config.Apps, range(len(config.Apps))): for i, app in enumerate(config.Apps):
if app["persistent"]: if app.persistent:
self.startApp(i) self.startApp(i)
if config.UseGui: if config.UseGui:
self.gui = Gui(self.datasource) self.gui = Gui(self.datasource)
self.gui.start() self.gui.start()
def requestApp(self, app, param=""): def requestApp(self, app, param=""):
with self.cv: with self.cv:
self.requestedApp = app self.requestedApp = app
self.param = param self.param = param
self.cv.notify_all() self.cv.notify_all()
logging.info("Requesting app: " + str(app)) logging.info("Requesting app: " + str(app))
def startApp(self, i, param=""): def startApp(self, i, param=""):
app = config.Apps[i] app = config.Apps[i]
newapp = App(app["cmd"], param, self.cv, is_persistent=app["persistent"], is_white=app["white"], path=app["path"]) newapp = App(app.cmd, param, self.cv, is_persistent=app.persistent, is_white=app.white, path=app.path)
newapp.datasource.addListener(self.cv) newapp.datasource.addListener(self.cv)
newapp.start() newapp.start()
if app["persistent"]: if app.persistent:
self.persistent_apps[self.currentApp] = newapp self.persistent_apps[self.currentApp] = newapp
return newapp return newapp
def updateApp(self): def updateApp(self):
try: try:
if self.app != None and not self.app.is_persistent: if self.app is not None and not self.app.is_persistent:
self.app.stop() self.app.stop()
self.currentApp = self.requestedApp self.currentApp = self.requestedApp
if (self.currentApp in self.persistent_apps.keys() if self.currentApp in self.persistent_apps.keys() and self.persistent_apps[self.currentApp].alive():
and self.persistent_apps[self.currentApp].alive()):
self.app = self.persistent_apps[self.currentApp] self.app = self.persistent_apps[self.currentApp]
else: else:
self.app = self.startApp(self.requestedApp, self.param) self.app = self.startApp(self.requestedApp, self.param)
except FileNotFoundError as e: except FileNotFoundError as e:
print(e) print(e)
def run(self): def run(self):
logging.info("Starting Apprunner") logging.info("Starting Apprunner")
while running: while running:
with self.cv: with self.cv:
if self.app == None or not self.app.alive(): if self.app is None or not self.app.alive():
if self.app != None: if self.app is not None:
self.last_crashlog = self.app.getLog() self.last_crashlog = self.app.getLog()
self.requestedApp = 0 self.requestedApp = 0
if self.requestedApp != None: if self.requestedApp is not None:
self.updateApp() self.updateApp()
self.requestedApp = None self.requestedApp = None
if self.app is not None:
frame = self.app.datasource.getData().clone() frame = self.app.datasource.getData().clone()
# logging.debug("Runner in time: " + str(time.time() - frame.created)) # logging.debug("Runner in time: " + str(time.time() - frame.created))
for _, f in self.filters.items(): for _, f in self.filters.items():
@ -329,59 +364,72 @@ class AppRunner(threading.Thread):
if config.UseGui: if config.UseGui:
self.gui.join() self.gui.join()
logging.info("Close Apprunner") logging.info("Close Apprunner")
def getLog(self): def getLog(self):
if self.app == None: if self.app is None:
return "" return ""
return self.app.getLog() return self.app.getLog()
def setGamma(self, r, g, b, w): def setGamma(self, r, g, b, w):
self.serial.setGamma(r, g, b, w) self.serial.setGamma(r, g, b, w)
def setFilter(self, name, filter): def setFilter(self, name, filter):
self.filters[name] = filter self.filters[name] = filter
def removeFilter(self, name): def removeFilter(self, name):
if name in self.filters.keys(): if name in self.filters.keys():
del self.filters[name] del self.filters[name]
######################################################################## ########################################################################
# Filter Api # # Filter Api #
######################################################################## ########################################################################
def MakeBrightnessFilter(intensity): def MakeBrightnessFilter(intensity):
def filter(img): def filter(img):
return (img * intensity).astype(np.uint8) return (img * intensity).astype(np.uint8)
return filter return filter
def FlipXFilter(intensity): def FlipXFilter(intensity):
return intensity[:, ::-1, :] return intensity[:, ::-1, :]
def FlipYFilter(intensity): def FlipYFilter(intensity):
return intensity[::-1, :, :] return intensity[::-1, :, :]
def MakeBrightnessImageFilter(name): def MakeBrightnessImageFilter(name):
img = scipy.misc.imread("filter/" + name + ".png", flatten=True) / 255 img = scipy.misc.imread("filter/" + name + ".png", flatten=True) / 255
# img = np.transpose(img) # img = np.transpose(img)
def filter(intensity): def filter(intensity):
intensity = intensity.astype(float) intensity = intensity.astype(float)
for i in range(intensity.shape[2]): for i in range(intensity.shape[2]):
intensity[:, :, i] *= img intensity[:, :, i] *= img
return intensity.astype(np.uint8) return intensity.astype(np.uint8)
return filter return filter
def strings(str):
def strings(s):
allowed_chars = string.ascii_letters + string.digits + "+-*/()." allowed_chars = string.ascii_letters + string.digits + "+-*/()."
i = 0 i = 0
outlist = [] outlist = []
while i != len(str): while i != len(s):
if str[i] not in allowed_chars: if s[i] not in allowed_chars:
raise Exception("Unexpected char "+str[i]) raise Exception("Unexpected char " + s[i])
if str[i] not in string.ascii_letters: if s[i] not in string.ascii_letters:
i += 1 i += 1
continue continue
out = "" out = ""
while i != len(str) and str[i] in string.ascii_letters+string.digits: while i != len(s) and s[i] in string.ascii_letters + string.digits:
out += str[i] out += s[i]
i += 1 i += 1
outlist.append(out) outlist.append(out)
return outlist return outlist
def eval_safer(expr, x, y, t): def eval_safer(expr, x, y, t):
symbols = {"x": x, "y": y, "t": t, symbols = {"x": x, "y": y, "t": t,
"sin": np.sin, "cos": np.cos, "exp": np.exp, "tan": np.tan} "sin": np.sin, "cos": np.cos, "exp": np.exp, "tan": np.tan}
@ -391,10 +439,12 @@ def eval_safer(expr, x, y, t):
raise Exception("unexpected symbol: " + s) raise Exception("unexpected symbol: " + s)
return eval(expr, {}, symbols) return eval(expr, {}, symbols)
def MakeBrightnessExprFilter(expr): def MakeBrightnessExprFilter(expr):
t0 = time.time() t0 = time.time()
x, y = np.meshgrid(np.arange(config.ScreenX), np.arange(config.ScreenY)) x, y = np.meshgrid(np.arange(config.ScreenX), np.arange(config.ScreenY))
eval_safer(expr, 0, 0, 0) eval_safer(expr, 0, 0, 0)
def filter(intensity): def filter(intensity):
t = time.time() - t0 t = time.time() - t0
intensity = intensity.astype(float) intensity = intensity.astype(float)
@ -403,8 +453,10 @@ def MakeBrightnessExprFilter(expr):
for i in range(intensity.shape[2]): for i in range(intensity.shape[2]):
intensity[:, :, i] *= filter intensity[:, :, i] *= filter
return intensity.astype(np.uint8) return intensity.astype(np.uint8)
return filter return filter
######################################################################## ########################################################################
# Web Api # # Web Api #
######################################################################## ########################################################################
@ -412,63 +464,74 @@ def MakeBrightnessExprFilter(expr):
def enable_cors_generic_route(): def enable_cors_generic_route():
add_cors_headers() add_cors_headers()
@bottle.hook('after_request') @bottle.hook('after_request')
def enable_cors_after_request_hook(): def enable_cors_after_request_hook():
add_cors_headers() add_cors_headers()
def add_cors_headers(): def add_cors_headers():
bottle.response.headers['Access-Control-Allow-Origin'] = '*' bottle.response.headers['Access-Control-Allow-Origin'] = '*'
bottle.response.headers['Access-Control-Allow-Methods'] = \ bottle.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
'GET, POST, PUT, OPTIONS' bottle.response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
bottle.response.headers['Access-Control-Allow-Headers'] = \
'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
def startApp(name, param=""): def startApp(name, param=""):
for i in range(len(config.Apps)): for i, app in enumerate(config.Apps):
if config.Apps[i]["name"] == name: if app.name == name:
runner.requestApp(i, param) runner.requestApp(i, param)
return "ok" return "ok"
return "not_found" return "not_found"
@route("/apps/list")
def apps_list():
s = []
for app in config.Apps:
s.append({"name": app["name"],
"guiname": app["guiname"],
"persistent": app["persistent"]})
return json.dumps(s)
@route("/apps/start/<name>") @bottle.route("/apps/list")
def apps_list():
return json.dumps([
{
"name": app.name,
"guiname": app.guiname,
"persistent": app.persistent,
} for app in config.Apps
])
@bottle.route("/apps/start/<name>")
def apps_start_param(name): def apps_start_param(name):
return startApp(name) return startApp(name)
@post("/apps/start/<name>")
@bottle.post("/apps/start/<name>")
def apps_start_post(name): def apps_start_post(name):
param = request.forms.get('param') param = bottle.request.forms.get('param')
return startApp(name, param) return startApp(name, param)
@route("/apps/start/<name>/<param>")
@bottle.route("/apps/start/<name>/<param>")
def apps_start(name, param): def apps_start(name, param):
return startApp(name, param) return startApp(name, param)
@route("/apps/log")
@bottle.route("/apps/log")
def apps_log(): def apps_log():
return runner.getLog() return runner.getLog()
@route("/apps/crashlog")
@bottle.route("/apps/crashlog")
def apps_log(): def apps_log():
return runner.last_crashlog return runner.last_crashlog
@route("/apps/running")
def apps_running():
return config.Apps[runner.currentApp]["name"]
@route("/") @bottle.route("/apps/running")
def apps_running():
return config.Apps[runner.currentApp].name
@bottle.route("/")
def index(): def index():
return bottle.static_file("index.html", root='html') return bottle.static_file("index.html", root='html')
@route("/setgamma/<r>/<g>/<b>/<w>")
@bottle.route("/setgamma/<r>/<g>/<b>/<w>")
def setGamma(r, g, b, w): def setGamma(r, g, b, w):
r = float(r) r = float(r)
g = float(g) g = float(g)
@ -477,15 +540,17 @@ def setGamma(r, g, b, w):
runner.setGamma(r, g, b, w) runner.setGamma(r, g, b, w)
return "ok" return "ok"
@route("/setbrightness/<i>")
@bottle.route("/setbrightness/<i>")
def setIntensity(i): def setIntensity(i):
i = float(i) i = float(i)
if i < 0 or i > 1: if not 0 <= i <= 1:
return "bad_value" return "bad_value"
runner.setFilter("0_intensity", MakeBrightnessFilter(i)) runner.setFilter("0_intensity", MakeBrightnessFilter(i))
return "ok" return "ok"
@route("/filter/flipx/<do>")
@bottle.route("/filter/flipx/<do>")
def flipx(do): def flipx(do):
if do == "true": if do == "true":
runner.setFilter("1_flipx", FlipXFilter) runner.setFilter("1_flipx", FlipXFilter)
@ -493,7 +558,8 @@ def flipx(do):
runner.removeFilter("1_flipx") runner.removeFilter("1_flipx")
return "ok" return "ok"
@route("/filter/flipy/<do>")
@bottle.route("/filter/flipy/<do>")
def flipy(do): def flipy(do):
if do == "true": if do == "true":
runner.setFilter("1_flipy", FlipYFilter) runner.setFilter("1_flipy", FlipYFilter)
@ -501,16 +567,19 @@ def flipy(do):
runner.removeFilter("1_flipy") runner.removeFilter("1_flipy")
return "ok" return "ok"
@route("/filter/img/<name>")
@bottle.route("/filter/img/<name>")
def setfilter(name): def setfilter(name):
if name == "none": if name == "none":
runner.removeFilter("3_imgfilter") runner.removeFilter("3_imgfilter")
else: else:
runner.setFilter("3_imgfilter", MakeBrightnessImageFilter(name)) runner.setFilter("3_imgfilter", MakeBrightnessImageFilter(name))
return "ok" return "ok"
@post("/filter/expr/")
@bottle.post("/filter/expr/")
def filter_expr(): def filter_expr():
expr = request.forms.get('expr') expr = bottle.request.forms.get('expr')
if expr == "" or expr == "none": if expr == "" or expr == "none":
runner.removeFilter("5_brightnessfunction") runner.removeFilter("5_brightnessfunction")
else: else:
@ -521,28 +590,11 @@ def filter_expr():
######################################################################## ########################################################################
# Startup # # Startup #
######################################################################## ########################################################################
#normalize config
for app in config.Apps:
if "persistent" not in app.keys():
app["persistent"] = False
if "guiname" not in app.keys():
app["guiname"] = app["name"]
if "white" not in app.keys():
app["white"] = False
if "path" not in app.keys():
app["path"] = "."
cmd = app["cmd"]
#remove non existing apps
#if type(cmd) == str and not os.path.isfile(cmd):
#config.Apps.remove(app)
#logging.warning("Removed app "+app["name"])
runner = AppRunner() runner = AppRunner()
runner.start() runner.start()
# runner.setFilter("5_crazy", MakeBrightnessExprFilter("0.5+0.25*sin(x/3)/x")) # runner.setFilter("5_crazy", MakeBrightnessExprFilter("0.5+0.25*sin(x/3)/x"))
run(host=config.WebHost, port=config.WebPort) bottle.run(host=config.WebHost, port=config.WebPort)
######################################################################## ########################################################################
# Shutdown # # Shutdown #