Code cleanup
This commit is contained in:
parent
8dc0480c18
commit
c38a840d42
@ -6,6 +6,7 @@ import random
|
||||
import time
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
# Groesse des Bildschirms bestimmen
|
||||
Nx = int(sys.argv[1])
|
||||
Ny = int(sys.argv[2])
|
||||
@ -21,18 +22,18 @@ except:
|
||||
|
||||
curPixel = 0
|
||||
|
||||
phase = np.random.uniform(0, 2*np.pi, (Ny, Nx))
|
||||
phase = np.random.uniform(0, 2 * np.pi, (Ny, Nx))
|
||||
t = 0
|
||||
f_min = 0.08
|
||||
f_max = 0.10
|
||||
f = np.random.uniform(f_min, f_max, (Ny, Nx))
|
||||
while True:
|
||||
t += time_ms/1000.0
|
||||
s = 0.80 +0.2*np.sin(phase+2*np.pi*t*f)
|
||||
t += time_ms / 1000.0
|
||||
s = 0.80 + 0.2 * np.sin(phase + 2 * np.pi * t * f)
|
||||
img = np.zeros([Ny, Nx, 4])
|
||||
img[:,:,3] = 255*s
|
||||
img[:, :, 3] = 255 * s
|
||||
# Zeige den Puffer an
|
||||
out = img.reshape((Nx*Ny*4,)).astype(np.uint8)
|
||||
out = img.reshape((Nx * Ny * 4,)).astype(np.uint8)
|
||||
os.write(1, out.tobytes())
|
||||
# warte time_ms ms
|
||||
time.sleep(time_ms*0.001)
|
||||
time.sleep(time_ms * 0.001)
|
||||
|
126
config.py
126
config.py
@ -1,8 +1,25 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Union, List
|
||||
|
||||
#width
|
||||
|
||||
@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
|
||||
ScreenX = 80
|
||||
#height
|
||||
# height
|
||||
ScreenY = 40
|
||||
|
||||
DefaultBrightness = 0.6
|
||||
@ -14,81 +31,66 @@ NoDataTimeout = 40
|
||||
|
||||
LogLevel = logging.DEBUG
|
||||
|
||||
UseGui = True
|
||||
UseGui = False
|
||||
GuiFPS = 20
|
||||
GuiScaleFactor = 15
|
||||
|
||||
WebHost = "0.0.0.0"
|
||||
WebPort = 8000
|
||||
|
||||
# first app is always running as default
|
||||
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},
|
||||
|
||||
{"guiname": "Lines", "name": "lines", "cmd": "./lines.py", "persistent": False, "path": "./apps/"},
|
||||
{"guiname": "Wolfram", "name": "wolfram", "cmd": "./wolfram.py", "persistent": False, "path": "./apps/"},
|
||||
{"guiname": "Digi Clock", "name": "digiclock", "cmd": "./digi_clock.py", "persistent": False, "path": "./apps/"},
|
||||
{"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="Lines", name="lines", cmd="./lines.py"),
|
||||
AppConfig(guiname="Wolfram", name="wolfram", cmd="./wolfram.py"),
|
||||
AppConfig(guiname="Digi Clock", name="digiclock", cmd="./digi_clock.py"),
|
||||
AppConfig(guiname="Text Scroller MQTT", name="textscroll", cmd="./textscroll.py"),
|
||||
AppConfig(guiname="Flicker", name="flicker", cmd="./flicker"),
|
||||
|
||||
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
|
||||
{"guiname": "Congress noise", "name": "cnoise", "cmd": "pixelfoo/target/release/cnoise", "persistent": False},
|
||||
{"guiname": "Game of Life", "name": "life", "cmd": "pixelfoo/target/release/life", "persistent": False},
|
||||
{"guiname": "Matrix Code", "name": "matrix-code", "cmd": "pixelfoo/target/release/matrix-code", "persistent": False},
|
||||
{"guiname": "Lorenz Attractor", "name": "lorenz", "cmd": "pixelfoo/target/release/lorenz", "persistent": False},
|
||||
{"guiname": "Dual Moodlight", "name": "bimood", "cmd": "pixelfoo/target/release/bimood", "persistent": False},
|
||||
{"guiname": "Maze", "name": "maze", "cmd": "pixelfoo/target/release/maze", "persistent": False},
|
||||
{"guiname": "Dual Maze", "name": "dualmaze", "cmd": "pixelfoo/target/release/dualmaze", "persistent": False, "persistent": False},
|
||||
{"guiname": "Predator & Prey", "name": "predprey", "cmd": "pixelfoo/target/release/predprey", "persistent": False},
|
||||
AppConfig(guiname="Congress noise", name="cnoise", cmd="./cnoise", path="pixelfoo/target/release/"),
|
||||
AppConfig(guiname="Game of Life", name="life", cmd="./life", path="pixelfoo/target/release/"),
|
||||
AppConfig(guiname="Matrix Code", name="matrix-code", cmd="./matrix-code", path="pixelfoo/target/release/"),
|
||||
AppConfig(guiname="Lorenz Attractor", name="lorenz", cmd="./lorenz", path="pixelfoo/target/release/"),
|
||||
AppConfig(guiname="Dual Moodlight", name="bimood", cmd="./bimood", path="pixelfoo/target/release/"),
|
||||
AppConfig(guiname="Maze", name="maze", cmd="./maze", path="pixelfoo/target/release/"),
|
||||
AppConfig(guiname="Dual Maze", name="dualmaze", cmd="./dualmaze", path="pixelfoo/target/release/"),
|
||||
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
|
||||
{"guiname": "Structure formation", "name": "swifthohenberg", "cmd": "apps/swifthohenberg.py", "persistent": False},
|
||||
{"guiname": "Quadratisch", "name": "quadratic", "cmd": "apps/quadratic.py"},
|
||||
{"guiname": "Pendel", "name": "pendulum", "cmd": "apps/pendlum.py"},
|
||||
{"guiname": "Konvergenz", "name": "convergence", "cmd": "apps/convergence.py"},
|
||||
{"guiname": "Sinic", "name": "sinic", "cmd": "apps/sinic.py"},
|
||||
{"guiname": "Sinic 2", "name": "sinic2", "cmd": "apps/sinic2.py"},
|
||||
AppConfig(guiname="Structure formation", name="swifthohenberg", cmd="./swifthohenberg.py"),
|
||||
AppConfig(guiname="Quadratisch", name="quadratic", cmd="./quadratic.py"),
|
||||
AppConfig(guiname="Pendel", name="pendulum", cmd="./pendlum.py"),
|
||||
AppConfig(guiname="Konvergenz", name="convergence", cmd="./convergence.py"),
|
||||
AppConfig(guiname="Sinic", name="sinic", cmd="./sinic.py"),
|
||||
AppConfig(guiname="Sinic 2", name="sinic2", cmd="./sinic2.py"),
|
||||
|
||||
# pixelthud
|
||||
{"guiname": "Fading Pixels", "name": "fadingpxls", "cmd": "apps/fading_pixels.py", "persistent": False},
|
||||
{"guiname": "Plane Wave", "name": "planewave", "cmd": "apps/plane_wave.py", "persistent": False},
|
||||
{"guiname": "Rock-paper-scissors-spock-lizard", "name": "rps", "cmd": "apps/rps.py", "persistent": False},
|
||||
{"guiname": "Doom Fire", "name": "doomfire", "cmd": "apps/doom_fire_psx2.py", "persistent": False},
|
||||
# {"guiname": "Maxwell FDTD", "name": "fdtd", "cmd": "apps/fdtd.py", "persistent": False},
|
||||
AppConfig(guiname="Fading Pixels", name="fadingpxls", cmd="./fading_pixels.py"),
|
||||
AppConfig(guiname="Plane Wave", name="planewave", cmd="./plane_wave.py"),
|
||||
AppConfig(guiname="Rock-paper-scissors-spock-lizard", name="rps", cmd="./rps.py"),
|
||||
AppConfig(guiname="Doom Fire", name="doomfire", cmd="./doom_fire_psx2.py"),
|
||||
# App(guiname="Maxwell FDTD", name="fdtd", cmd="./fdtd.py"),
|
||||
|
||||
#{"guiname": "Stream", "name": "stream", "cmd": "apps/stream.sh", "persistent": False},
|
||||
#{"guiname": "Wget Video/Gif/Images", "name": "wget", "cmd": "apps/wget.sh", "persistent": False},
|
||||
#{"guiname": "Tetris", "name": "tetris", "cmd": "apps/deckentetris/deckentetris.py", "persistent": False},
|
||||
#{"guiname": "SkyScrapper", "name": "sky", "cmd": "apps/weather/main.py"},
|
||||
#{"guiname": "Strobo", "name": "strobo", "cmd": "apps/strobo.py", "persistent": False},
|
||||
#{"guiname": "Snake", "name": "snake", "cmd": "apps/snake.py", "persistent": False},
|
||||
#{"name": "gif", "cmd": "apps/gif.sh"},
|
||||
#{"name": "colormap", "cmd": "apps/colormap.py"},
|
||||
# App(guiname="Stream", name="stream", cmd="./stream.sh"),
|
||||
# App(guiname="Wget Video/Gif/Images", name="wget", cmd="./wget.sh"),
|
||||
# App(guiname="Tetris", name="tetris", cmd="./deckentetris/deckentetris.py"),
|
||||
# App(guiname="SkyScrapper", name="sky", cmd="./weather/main.py"),
|
||||
# App(guiname="Strobo", name="strobo", cmd="./strobo.py"),
|
||||
# App(guiname="Snake", name="snake", cmd="./snake.py"),
|
||||
# App(name="gif", cmd="./gif.sh"),
|
||||
# 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
|
||||
|
352
main.py
352
main.py
@ -1,24 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import config
|
||||
import subprocess
|
||||
import os
|
||||
import os.path
|
||||
import serial
|
||||
import threading
|
||||
|
||||
import json
|
||||
import bottle
|
||||
from bottle import route, run, request, post
|
||||
import time
|
||||
import sys
|
||||
import logging
|
||||
import math
|
||||
import numpy as np
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
|
||||
import bottle
|
||||
import numpy as np
|
||||
import scipy.misc
|
||||
import serial
|
||||
|
||||
import config
|
||||
|
||||
logging.basicConfig(filename='pixelserver.log', level=config.LogLevel)
|
||||
running = True
|
||||
|
||||
|
||||
########################################################################
|
||||
# Utils #
|
||||
########################################################################
|
||||
@ -26,42 +28,52 @@ class DataSource:
|
||||
def __init__(self, initial):
|
||||
self.data = initial
|
||||
self.listeners = []
|
||||
|
||||
def getData(self):
|
||||
return self.data
|
||||
|
||||
def addListener(self, listener):
|
||||
self.listeners.append(listener)
|
||||
return self
|
||||
|
||||
def pushData(self, data):
|
||||
self.data = data
|
||||
for listener in self.listeners:
|
||||
with listener:
|
||||
listener.notify_all()
|
||||
|
||||
|
||||
class WatchDog(threading.Thread):
|
||||
def __init__(self, check, action):
|
||||
super().__init__()
|
||||
super().__init__(daemon=True)
|
||||
self.check = check
|
||||
self.action = action
|
||||
self.running = True
|
||||
|
||||
def run(self):
|
||||
while running and self.running:
|
||||
if self.check():
|
||||
logging.error("Watchdog: Executed")
|
||||
self.action()
|
||||
time.sleep(1)
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
class LogReader(threading.Thread):
|
||||
def __init__(self, runner):
|
||||
super().__init__()
|
||||
super().__init__(daemon=True)
|
||||
self.runner = runner
|
||||
self.log = ""
|
||||
self.running = True
|
||||
|
||||
def clear(self):
|
||||
self.log = ""
|
||||
|
||||
def getLog(self):
|
||||
return self.log
|
||||
|
||||
def run(self):
|
||||
logging.info("LogReader started")
|
||||
while running and self.running:
|
||||
@ -72,35 +84,42 @@ class LogReader(threading.Thread):
|
||||
logging.error(str(e))
|
||||
time.sleep(1)
|
||||
logging.info("LogReader closed")
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
class Frame:
|
||||
def __init__(self, buffer, channels=3):
|
||||
self.buffer = buffer
|
||||
self.created = time.time()
|
||||
self.channels = channels
|
||||
|
||||
def clone(self):
|
||||
f = Frame(self.buffer+0, self.channels)
|
||||
f = Frame(self.buffer + 0, self.channels)
|
||||
f.created = self.created
|
||||
return f
|
||||
|
||||
|
||||
########################################################################
|
||||
# GUI #
|
||||
########################################################################
|
||||
if config.UseGui:
|
||||
import pygame
|
||||
|
||||
|
||||
class Gui(threading.Thread):
|
||||
def __init__(self, datasource):
|
||||
super().__init__()
|
||||
super().__init__(daemon=True)
|
||||
self.cv = threading.Condition()
|
||||
self.datasource = datasource.addListener(self.cv)
|
||||
|
||||
def run(self):
|
||||
last_frame = time.time()
|
||||
logging.info("Starting GUI")
|
||||
sf = config.GuiScaleFactor
|
||||
screen = pygame.display.set_mode((sf*config.ScreenX, sf*config.ScreenY))
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((sf * config.ScreenX, sf * config.ScreenY))
|
||||
pygame.display.set_caption("Pixelserver - GUI Vis")
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
@ -113,35 +132,38 @@ if config.UseGui:
|
||||
for x in range(config.ScreenX):
|
||||
for y in range(config.ScreenY):
|
||||
color = (frame.buffer[y, x, 0], frame.buffer[y, x, 1], frame.buffer[y, x, 2])
|
||||
pygame.draw.rect(screen, color, pygame.Rect(sf*x, sf*y, sf, sf))
|
||||
pygame.draw.rect(screen, color, pygame.Rect(sf * x, sf * y, sf, sf))
|
||||
elif frame.channels == 4:
|
||||
for x in range(config.ScreenX):
|
||||
for y in range(config.ScreenY):
|
||||
w = frame.buffer[y, x, 3]//2
|
||||
color = (frame.buffer[y, x, 0]//2+w, frame.buffer[y, x, 1]//2+w, frame.buffer[y, x, 2]//2+w)
|
||||
pygame.draw.rect(screen, color, pygame.Rect(sf*x, sf*y, sf, sf))
|
||||
#logging.debug("Time to gui: "+str(time.time()-frame.created))
|
||||
w = frame.buffer[y, x, 3] // 2
|
||||
color = (frame.buffer[y, x, 0] // 2 + w, frame.buffer[y, x, 1] // 2 + w, frame.buffer[y, x, 2] // 2 + w)
|
||||
pygame.draw.rect(screen, color, pygame.Rect(sf * x, sf * y, sf, sf))
|
||||
# logging.debug("Time to gui: " + str(time.time() - frame.created))
|
||||
pygame.display.flip()
|
||||
if time.time() < last_frame+1/config.GuiFPS:
|
||||
time.sleep(time.time()-(last_frame+1/config.GuiFPS))
|
||||
#time.sleep(0.01)
|
||||
if time.time() < last_frame + 1 / config.GuiFPS:
|
||||
time.sleep(max(0.01, time.time() - (last_frame + 1 / config.GuiFPS)))
|
||||
# time.sleep(0.01)
|
||||
last_frame = time.time()
|
||||
|
||||
logging.info("Closing GUI")
|
||||
def join(self):
|
||||
|
||||
def join(self, **kwargs):
|
||||
with self.cv:
|
||||
self.cv.notify_all()
|
||||
super().join()
|
||||
|
||||
|
||||
########################################################################
|
||||
# Serial #
|
||||
########################################################################
|
||||
class SerialWriter(threading.Thread):
|
||||
def __init__(self, datasource):
|
||||
super().__init__()
|
||||
super().__init__(daemon=True)
|
||||
self.cv = threading.Condition()
|
||||
self.datasource = datasource.addListener(self.cv)
|
||||
self.updateGamma = False
|
||||
|
||||
def run(self):
|
||||
should_connect = True
|
||||
ser = None
|
||||
@ -153,17 +175,17 @@ class SerialWriter(threading.Thread):
|
||||
should_connect = False
|
||||
logging.info("Serial Opened")
|
||||
with self.cv:
|
||||
self.cv.wait(timeout = 1/30)
|
||||
self.cv.wait(timeout=1 / 30)
|
||||
frame = self.datasource.getData()
|
||||
data = frame.buffer.reshape((config.ScreenX*config.ScreenY*frame.channels,)).astype(np.uint8).tobytes()
|
||||
data = frame.buffer.reshape((config.ScreenX * config.ScreenY * frame.channels,)).astype(np.uint8).tobytes()
|
||||
if self.updateGamma:
|
||||
buf = bytearray(b"\x00")*4*256
|
||||
buf = bytearray(b"\x00") * 4 * 256
|
||||
for i in range(256):
|
||||
apply = lambda x, g: max(0, min(255, int(math.pow(x/255, g)*255)))
|
||||
buf[i] = apply(i, self.r)
|
||||
buf[i+256] = apply(i, self.g)
|
||||
buf[i+512] = apply(i, self.b)
|
||||
buf[i+512+256] = apply(i, self.w)
|
||||
apply = lambda x, g: max(0, min(255, int(math.pow(x / 255, g) * 255)))
|
||||
buf[i] = apply(i, self.r)
|
||||
buf[i + 256] = apply(i, self.g)
|
||||
buf[i + 512] = apply(i, self.b)
|
||||
buf[i + 512 + 256] = apply(i, self.w)
|
||||
ser.write(b"\x02")
|
||||
ser.write(buf)
|
||||
self.updateGamma = False
|
||||
@ -173,36 +195,37 @@ class SerialWriter(threading.Thread):
|
||||
elif frame.channels == 4:
|
||||
ser.write(b"\03")
|
||||
ser.write(data)
|
||||
logging.debug("Time to gui: "+str(time.time()-frame.created))
|
||||
logging.debug("Time to gui: " + str(time.time() - frame.created))
|
||||
ser.flush()
|
||||
except Exception as e:
|
||||
if ser != None:
|
||||
if ser is not None:
|
||||
ser.close()
|
||||
ser = None
|
||||
logging.warning("Serial was close because: "+str(e))
|
||||
logging.warning(f"Serial was close because: {e}")
|
||||
should_connect = True
|
||||
time.sleep(5)
|
||||
logging.info("Closing SerialWriter")
|
||||
def join(self):
|
||||
|
||||
def join(self, **kwargs):
|
||||
with self.cv:
|
||||
self.cv.notify_all()
|
||||
super().join()
|
||||
|
||||
def setGamma(self, r, g, b, w):
|
||||
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.cv.notify_all()
|
||||
|
||||
|
||||
########################################################################
|
||||
# App #
|
||||
########################################################################
|
||||
class App(threading.Thread):
|
||||
def __init__(self, cmd, param, listener, is_persistent, is_white=False, path="."):
|
||||
super().__init__()
|
||||
#start app
|
||||
if type(cmd) != list:
|
||||
cmd = [cmd,]
|
||||
args = cmd+[str(config.ScreenX), str(config.ScreenY), param]
|
||||
super().__init__(daemon=True)
|
||||
# start app
|
||||
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.last_update = time.time()
|
||||
self.cv = threading.Condition()
|
||||
@ -215,20 +238,21 @@ class App(threading.Thread):
|
||||
self.listener = listener
|
||||
self.is_persistent = is_persistent
|
||||
self.is_white = is_white
|
||||
|
||||
def run(self):
|
||||
while running and self.running and self.alive():
|
||||
oshandle = self.app.stdout.fileno()
|
||||
try:
|
||||
bytes = 4 if self.is_white else 3
|
||||
data = os.read(oshandle, config.ScreenX*config.ScreenY*bytes)
|
||||
assert len(data) == config.ScreenX*config.ScreenY*bytes
|
||||
buffer = np.frombuffer(data, dtype=np.uint8, count=config.ScreenX*config.ScreenY*bytes)
|
||||
buffer = buffer.reshape((config.ScreenY, config.ScreenX, bytes))
|
||||
channels = 4 if self.is_white else 3
|
||||
data = os.read(oshandle, config.ScreenX * config.ScreenY * channels)
|
||||
assert len(data) == config.ScreenX * config.ScreenY * channels
|
||||
buffer = np.frombuffer(data, dtype=np.uint8, count=config.ScreenX * config.ScreenY * channels)
|
||||
buffer = buffer.reshape((config.ScreenY, config.ScreenX, channels))
|
||||
|
||||
frame = Frame(buffer, channels=bytes)
|
||||
frame = Frame(buffer, channels=channels)
|
||||
self.last_update = time.time()
|
||||
self.datasource.pushData(frame)
|
||||
except Exception as e:
|
||||
except:
|
||||
logging.debug("Exception in App.run")
|
||||
with self.listener:
|
||||
self.listener.notify_all()
|
||||
@ -238,8 +262,10 @@ class App(threading.Thread):
|
||||
self.logreader.join()
|
||||
self.app.wait()
|
||||
logging.debug("App stopped")
|
||||
|
||||
def alive(self):
|
||||
return self.app.poll() == None
|
||||
return self.app.poll() is None
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self.app.kill()
|
||||
@ -249,20 +275,24 @@ class App(threading.Thread):
|
||||
self.logreader.stop()
|
||||
self.app.wait()
|
||||
logging.debug("App stopped")
|
||||
|
||||
def getLog(self):
|
||||
return self.logreader.getLog()
|
||||
|
||||
def terminateApp(self):
|
||||
logging.error("Terminate app!")
|
||||
self.stop()
|
||||
|
||||
def isAppTimedOut(self):
|
||||
return time.time()-self.last_update > config.NoDataTimeout
|
||||
return time.time() - self.last_update > config.NoDataTimeout
|
||||
|
||||
|
||||
########################################################################
|
||||
# Main #
|
||||
########################################################################
|
||||
class AppRunner(threading.Thread):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
super().__init__(daemon=True)
|
||||
self.last_crashlog = ""
|
||||
self.currentApp = -1
|
||||
self.requestedApp = 0
|
||||
@ -274,137 +304,159 @@ class AppRunner(threading.Thread):
|
||||
self.serial.start()
|
||||
self.persistent_apps = {}
|
||||
self.filters = OrderedDict()
|
||||
#start persistent apps
|
||||
for app, i in zip(config.Apps, range(len(config.Apps))):
|
||||
if app["persistent"]:
|
||||
# start persistent apps
|
||||
for i, app in enumerate(config.Apps):
|
||||
if app.persistent:
|
||||
self.startApp(i)
|
||||
if config.UseGui:
|
||||
self.gui = Gui(self.datasource)
|
||||
self.gui.start()
|
||||
|
||||
def requestApp(self, app, param=""):
|
||||
with self.cv:
|
||||
self.requestedApp = app
|
||||
self.param = param
|
||||
self.cv.notify_all()
|
||||
logging.info("Requesting app: "+str(app))
|
||||
logging.info("Requesting app: " + str(app))
|
||||
|
||||
def startApp(self, i, param=""):
|
||||
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.start()
|
||||
if app["persistent"]:
|
||||
self.persistent_apps[self.currentApp] = newapp
|
||||
if app.persistent:
|
||||
self.persistent_apps[self.currentApp] = newapp
|
||||
return newapp
|
||||
|
||||
def updateApp(self):
|
||||
try:
|
||||
if self.app != None and not self.app.is_persistent:
|
||||
self.app.stop()
|
||||
self.currentApp = self.requestedApp
|
||||
if (self.currentApp in self.persistent_apps.keys()
|
||||
and self.persistent_apps[self.currentApp].alive()):
|
||||
if self.app is not None and not self.app.is_persistent:
|
||||
self.app.stop()
|
||||
self.currentApp = self.requestedApp
|
||||
if self.currentApp in self.persistent_apps.keys() and self.persistent_apps[self.currentApp].alive():
|
||||
self.app = self.persistent_apps[self.currentApp]
|
||||
else:
|
||||
self.app = self.startApp(self.requestedApp, self.param)
|
||||
else:
|
||||
self.app = self.startApp(self.requestedApp, self.param)
|
||||
except FileNotFoundError as e:
|
||||
print(e)
|
||||
print(e)
|
||||
|
||||
def run(self):
|
||||
logging.info("Starting Apprunner")
|
||||
while running:
|
||||
with self.cv:
|
||||
if self.app == None or not self.app.alive():
|
||||
if self.app != None:
|
||||
if self.app is None or not self.app.alive():
|
||||
if self.app is not None:
|
||||
self.last_crashlog = self.app.getLog()
|
||||
self.requestedApp = 0
|
||||
if self.requestedApp != None:
|
||||
if self.requestedApp is not None:
|
||||
self.updateApp()
|
||||
self.requestedApp = None
|
||||
frame = self.app.datasource.getData().clone()
|
||||
#logging.debug("Runner in time: "+str(time.time()-frame.created))
|
||||
for _, f in self.filters.items():
|
||||
frame.buffer = f(frame.buffer)
|
||||
#logging.debug("Runner out time: "+str(time.time()-frame.created))
|
||||
self.datasource.pushData(frame)
|
||||
|
||||
if self.app is not None:
|
||||
frame = self.app.datasource.getData().clone()
|
||||
# logging.debug("Runner in time: " + str(time.time() - frame.created))
|
||||
for _, f in self.filters.items():
|
||||
frame.buffer = f(frame.buffer)
|
||||
# logging.debug("Runner out time: " + str(time.time() - frame.created))
|
||||
self.datasource.pushData(frame)
|
||||
self.cv.wait()
|
||||
self.serial.join()
|
||||
if config.UseGui:
|
||||
self.gui.join()
|
||||
logging.info("Close Apprunner")
|
||||
|
||||
def getLog(self):
|
||||
if self.app == None:
|
||||
if self.app is None:
|
||||
return ""
|
||||
return self.app.getLog()
|
||||
|
||||
def setGamma(self, r, g, b, w):
|
||||
self.serial.setGamma(r, g, b, w)
|
||||
|
||||
def setFilter(self, name, filter):
|
||||
self.filters[name] = filter
|
||||
|
||||
def removeFilter(self, name):
|
||||
if name in self.filters.keys():
|
||||
del self.filters[name]
|
||||
|
||||
|
||||
########################################################################
|
||||
# Filter Api #
|
||||
########################################################################
|
||||
def MakeBrightnessFilter(intensity):
|
||||
def filter(img):
|
||||
return (img*intensity).astype(np.uint8)
|
||||
return (img * intensity).astype(np.uint8)
|
||||
|
||||
return filter
|
||||
|
||||
|
||||
def FlipXFilter(intensity):
|
||||
return intensity[:,::-1,:]
|
||||
return intensity[:, ::-1, :]
|
||||
|
||||
|
||||
def FlipYFilter(intensity):
|
||||
return intensity[::-1,:,:]
|
||||
return intensity[::-1, :, :]
|
||||
|
||||
|
||||
def MakeBrightnessImageFilter(name):
|
||||
img = scipy.misc.imread("filter/"+name+".png", flatten=True)/255
|
||||
#img = np.transpose(img)
|
||||
img = scipy.misc.imread("filter/" + name + ".png", flatten=True) / 255
|
||||
|
||||
# img = np.transpose(img)
|
||||
def filter(intensity):
|
||||
intensity = intensity.astype(float)
|
||||
for i in range(intensity.shape[2]):
|
||||
intensity[:,:,i] *= img
|
||||
intensity[:, :, i] *= img
|
||||
return intensity.astype(np.uint8)
|
||||
|
||||
return filter
|
||||
|
||||
def strings(str):
|
||||
allowed_chars = string.ascii_letters+string.digits+"+-*/()."
|
||||
|
||||
def strings(s):
|
||||
allowed_chars = string.ascii_letters + string.digits + "+-*/()."
|
||||
i = 0
|
||||
outlist = []
|
||||
while i != len(str):
|
||||
if str[i] not in allowed_chars:
|
||||
raise Exception("Unexpected char "+str[i])
|
||||
if str[i] not in string.ascii_letters:
|
||||
i+=1
|
||||
while i != len(s):
|
||||
if s[i] not in allowed_chars:
|
||||
raise Exception("Unexpected char " + s[i])
|
||||
if s[i] not in string.ascii_letters:
|
||||
i += 1
|
||||
continue
|
||||
out = ""
|
||||
while i != len(str) and str[i] in string.ascii_letters+string.digits:
|
||||
out += str[i]
|
||||
i+=1
|
||||
while i != len(s) and s[i] in string.ascii_letters + string.digits:
|
||||
out += s[i]
|
||||
i += 1
|
||||
outlist.append(out)
|
||||
return outlist
|
||||
|
||||
|
||||
def eval_safer(expr, x, y, t):
|
||||
symbols = {"x": x, "y": y, "t": t,
|
||||
"sin": np.sin, "cos": np.cos, "exp": np.exp, "tan": np.tan}
|
||||
strs = strings(expr)
|
||||
for s in strs:
|
||||
if s not in symbols.keys():
|
||||
raise Exception("unexpected symbol: "+s)
|
||||
raise Exception("unexpected symbol: " + s)
|
||||
return eval(expr, {}, symbols)
|
||||
|
||||
|
||||
def MakeBrightnessExprFilter(expr):
|
||||
t0 = time.time()
|
||||
x, y = np.meshgrid(np.arange(config.ScreenX), np.arange(config.ScreenY))
|
||||
eval_safer(expr, 0, 0, 0)
|
||||
|
||||
def filter(intensity):
|
||||
t = time.time()-t0
|
||||
t = time.time() - t0
|
||||
intensity = intensity.astype(float)
|
||||
filter = 0*x+eval_safer(expr, x, y, t)
|
||||
filter = 0 * x + eval_safer(expr, x, y, t)
|
||||
filter = np.clip(np.nan_to_num(filter), 0, 1)
|
||||
for i in range(intensity.shape[2]):
|
||||
intensity[:,:,i] *= filter
|
||||
intensity[:, :, i] *= filter
|
||||
return intensity.astype(np.uint8)
|
||||
|
||||
return filter
|
||||
|
||||
|
||||
########################################################################
|
||||
# Web Api #
|
||||
########################################################################
|
||||
@ -412,63 +464,74 @@ def MakeBrightnessExprFilter(expr):
|
||||
def enable_cors_generic_route():
|
||||
add_cors_headers()
|
||||
|
||||
|
||||
@bottle.hook('after_request')
|
||||
def enable_cors_after_request_hook():
|
||||
add_cors_headers()
|
||||
|
||||
|
||||
def add_cors_headers():
|
||||
bottle.response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
bottle.response.headers['Access-Control-Allow-Methods'] = \
|
||||
'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-Methods'] = 'GET, POST, PUT, OPTIONS'
|
||||
bottle.response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
|
||||
|
||||
|
||||
def startApp(name, param=""):
|
||||
for i in range(len(config.Apps)):
|
||||
if config.Apps[i]["name"] == name:
|
||||
for i, app in enumerate(config.Apps):
|
||||
if app.name == name:
|
||||
runner.requestApp(i, param)
|
||||
return "ok"
|
||||
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):
|
||||
return startApp(name)
|
||||
|
||||
@post("/apps/start/<name>")
|
||||
|
||||
@bottle.post("/apps/start/<name>")
|
||||
def apps_start_post(name):
|
||||
param = request.forms.get('param')
|
||||
param = bottle.request.forms.get('param')
|
||||
return startApp(name, param)
|
||||
|
||||
@route("/apps/start/<name>/<param>")
|
||||
|
||||
@bottle.route("/apps/start/<name>/<param>")
|
||||
def apps_start(name, param):
|
||||
return startApp(name, param)
|
||||
|
||||
@route("/apps/log")
|
||||
|
||||
@bottle.route("/apps/log")
|
||||
def apps_log():
|
||||
return runner.getLog()
|
||||
|
||||
@route("/apps/crashlog")
|
||||
|
||||
@bottle.route("/apps/crashlog")
|
||||
def apps_log():
|
||||
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():
|
||||
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):
|
||||
r = float(r)
|
||||
g = float(g)
|
||||
@ -477,15 +540,17 @@ def setGamma(r, g, b, w):
|
||||
runner.setGamma(r, g, b, w)
|
||||
return "ok"
|
||||
|
||||
@route("/setbrightness/<i>")
|
||||
|
||||
@bottle.route("/setbrightness/<i>")
|
||||
def setIntensity(i):
|
||||
i = float(i)
|
||||
if i < 0 or i > 1:
|
||||
if not 0 <= i <= 1:
|
||||
return "bad_value"
|
||||
runner.setFilter("0_intensity", MakeBrightnessFilter(i))
|
||||
return "ok"
|
||||
|
||||
@route("/filter/flipx/<do>")
|
||||
|
||||
@bottle.route("/filter/flipx/<do>")
|
||||
def flipx(do):
|
||||
if do == "true":
|
||||
runner.setFilter("1_flipx", FlipXFilter)
|
||||
@ -493,7 +558,8 @@ def flipx(do):
|
||||
runner.removeFilter("1_flipx")
|
||||
return "ok"
|
||||
|
||||
@route("/filter/flipy/<do>")
|
||||
|
||||
@bottle.route("/filter/flipy/<do>")
|
||||
def flipy(do):
|
||||
if do == "true":
|
||||
runner.setFilter("1_flipy", FlipYFilter)
|
||||
@ -501,16 +567,19 @@ def flipy(do):
|
||||
runner.removeFilter("1_flipy")
|
||||
return "ok"
|
||||
|
||||
@route("/filter/img/<name>")
|
||||
|
||||
@bottle.route("/filter/img/<name>")
|
||||
def setfilter(name):
|
||||
if name == "none":
|
||||
runner.removeFilter("3_imgfilter")
|
||||
else:
|
||||
runner.setFilter("3_imgfilter", MakeBrightnessImageFilter(name))
|
||||
return "ok"
|
||||
@post("/filter/expr/")
|
||||
|
||||
|
||||
@bottle.post("/filter/expr/")
|
||||
def filter_expr():
|
||||
expr = request.forms.get('expr')
|
||||
expr = bottle.request.forms.get('expr')
|
||||
if expr == "" or expr == "none":
|
||||
runner.removeFilter("5_brightnessfunction")
|
||||
else:
|
||||
@ -521,28 +590,11 @@ def filter_expr():
|
||||
########################################################################
|
||||
# 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.start()
|
||||
|
||||
#runner.setFilter("5_crazy", MakeBrightnessExprFilter("0.5+0.25*sin(x/3)/x"))
|
||||
run(host=config.WebHost, port=config.WebPort)
|
||||
# runner.setFilter("5_crazy", MakeBrightnessExprFilter("0.5+0.25*sin(x/3)/x"))
|
||||
bottle.run(host=config.WebHost, port=config.WebPort)
|
||||
|
||||
########################################################################
|
||||
# Shutdown #
|
||||
|
Loading…
Reference in New Issue
Block a user