sync with current state
This commit is contained in:
252
main.py
252
main.py
@@ -12,7 +12,10 @@ import time
|
||||
import sys
|
||||
import logging
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import string
|
||||
from collections import OrderedDict
|
||||
import scipy.misc
|
||||
logging.basicConfig(filename='pixelserver.log', level=config.LogLevel)
|
||||
running = True
|
||||
|
||||
@@ -71,6 +74,16 @@ class LogReader(threading.Thread):
|
||||
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.created = self.created
|
||||
return f
|
||||
|
||||
########################################################################
|
||||
# GUI #
|
||||
@@ -84,6 +97,7 @@ if config.UseGui:
|
||||
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))
|
||||
@@ -93,14 +107,26 @@ if config.UseGui:
|
||||
pass
|
||||
with self.cv:
|
||||
self.cv.wait()
|
||||
data = self.datasource.getData()
|
||||
screen.fill((0, 255, 0))
|
||||
for x in range(config.ScreenX):
|
||||
for y in range(config.ScreenY):
|
||||
i = x+y*config.ScreenX
|
||||
color = (data[i*3+0], data[i*3+1], data[i*3+2])
|
||||
pygame.draw.rect(screen, color, pygame.Rect(sf*x, sf*y, sf, sf))
|
||||
frame = self.datasource.getData()
|
||||
screen.fill((0, 0, 0))
|
||||
if frame.channels == 3:
|
||||
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))
|
||||
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))
|
||||
pygame.display.flip()
|
||||
if time.time() < last_frame+1/config.GuiFPS:
|
||||
time.sleep(time.time()-(last_frame+1/config.GuiFPS))
|
||||
#time.sleep(0.01)
|
||||
last_frame = time.time()
|
||||
|
||||
logging.info("Closing GUI")
|
||||
def join(self):
|
||||
with self.cv:
|
||||
@@ -128,19 +154,26 @@ class SerialWriter(threading.Thread):
|
||||
logging.info("Serial Opened")
|
||||
with self.cv:
|
||||
self.cv.wait(timeout = 1/30)
|
||||
data = self.datasource.getData()
|
||||
frame = self.datasource.getData()
|
||||
data = frame.buffer.reshape((config.ScreenX*config.ScreenY*frame.channels,)).astype(np.uint8).tobytes()
|
||||
if self.updateGamma:
|
||||
buf = bytearray(b"\x00")*3*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)
|
||||
ser.write(b"\x02")
|
||||
ser.write(buf)
|
||||
self.updateGamma = False
|
||||
ser.write(b"\01")
|
||||
ser.write(data)
|
||||
if frame.channels == 3:
|
||||
ser.write(b"\01")
|
||||
ser.write(data)
|
||||
elif frame.channels == 4:
|
||||
ser.write(b"\03")
|
||||
ser.write(data)
|
||||
logging.debug("Time to gui: "+str(time.time()-frame.created))
|
||||
ser.flush()
|
||||
except Exception as e:
|
||||
if ser != None:
|
||||
@@ -154,9 +187,9 @@ class SerialWriter(threading.Thread):
|
||||
with self.cv:
|
||||
self.cv.notify_all()
|
||||
super().join()
|
||||
def setGamma(self, r, g, b):
|
||||
def setGamma(self, r, g, b, w):
|
||||
with self.cv:
|
||||
self.r, self.g, self.b = r, g, b
|
||||
self.r, self.g, self.b, self.w = r, g, b, w
|
||||
self.updateGamma = True
|
||||
self.cv.notify_all()
|
||||
|
||||
@@ -164,31 +197,37 @@ class SerialWriter(threading.Thread):
|
||||
# App #
|
||||
########################################################################
|
||||
class App(threading.Thread):
|
||||
def __init__(self, cmd, param, listener, is_persistent):
|
||||
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]
|
||||
self.app = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
|
||||
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()
|
||||
self.watchdog = WatchDog(lambda: self.isAppTimedOut(), lambda: self.terminateApp())
|
||||
self.watchdog.start()
|
||||
self.logreader = LogReader(self)
|
||||
self.logreader.start()
|
||||
self.datasource = DataSource(b"\x00"*config.ScreenX*config.ScreenY*3)
|
||||
self.datasource = DataSource(Frame(np.zeros((config.ScreenY, config.ScreenX, 3))))
|
||||
self.running = True
|
||||
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:
|
||||
data = os.read(oshandle, config.ScreenX*config.ScreenY*3)
|
||||
assert len(data) == config.ScreenX*config.ScreenY*3
|
||||
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))
|
||||
|
||||
frame = Frame(buffer, channels=bytes)
|
||||
self.last_update = time.time()
|
||||
self.datasource.pushData(data)
|
||||
self.datasource.pushData(frame)
|
||||
except Exception as e:
|
||||
logging.debug("Exception in App.run")
|
||||
with self.listener:
|
||||
@@ -197,6 +236,8 @@ class App(threading.Thread):
|
||||
self.logreader.stop()
|
||||
self.watchdog.join()
|
||||
self.logreader.join()
|
||||
self.app.wait()
|
||||
logging.debug("App stopped")
|
||||
def alive(self):
|
||||
return self.app.poll() == None
|
||||
def stop(self):
|
||||
@@ -206,6 +247,8 @@ class App(threading.Thread):
|
||||
self.app.stderr.close()
|
||||
self.watchdog.stop()
|
||||
self.logreader.stop()
|
||||
self.app.wait()
|
||||
logging.debug("App stopped")
|
||||
def getLog(self):
|
||||
return self.logreader.getLog()
|
||||
def terminateApp(self):
|
||||
@@ -220,16 +263,17 @@ class App(threading.Thread):
|
||||
class AppRunner(threading.Thread):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.last_crashlog = ""
|
||||
self.currentApp = -1
|
||||
self.requestedApp = 0
|
||||
self.intensity = config.DefaultBrightness
|
||||
self.app = None
|
||||
self.cv = threading.Condition()
|
||||
self.param = ""
|
||||
self.datasource = DataSource(b"\x00"*config.ScreenX*config.ScreenY*3)
|
||||
self.datasource = DataSource(Frame(np.zeros((config.ScreenY, config.ScreenX, 3))))
|
||||
self.serial = SerialWriter(self.datasource)
|
||||
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"]:
|
||||
@@ -245,34 +289,41 @@ class AppRunner(threading.Thread):
|
||||
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"])
|
||||
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
|
||||
self.persistent_apps[self.currentApp] = newapp
|
||||
return newapp
|
||||
def updateApp(self):
|
||||
if self.app != None and not self.app.is_persistent:
|
||||
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()):
|
||||
self.app = self.persistent_apps[self.currentApp]
|
||||
else:
|
||||
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)
|
||||
except FileNotFoundError as 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:
|
||||
self.last_crashlog = self.app.getLog()
|
||||
self.requestedApp = 0
|
||||
if self.requestedApp != None:
|
||||
self.updateApp()
|
||||
self.requestedApp = None
|
||||
data = bytearray(self.app.datasource.getData())
|
||||
for i in range(len(data)):
|
||||
data[i] = int(data[i]*self.intensity)
|
||||
self.datasource.pushData(data)
|
||||
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:
|
||||
@@ -282,8 +333,77 @@ class AppRunner(threading.Thread):
|
||||
if self.app == None:
|
||||
return ""
|
||||
return self.app.getLog()
|
||||
def setGamma(self, r, g, b):
|
||||
self.serial.setGamma(r, g, b)
|
||||
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 filter
|
||||
|
||||
def FlipXFilter(intensity):
|
||||
return intensity[:,::-1,:]
|
||||
|
||||
def FlipYFilter(intensity):
|
||||
return intensity[::-1,:,:]
|
||||
|
||||
def MakeBrightnessImageFilter(name):
|
||||
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
|
||||
return intensity.astype(np.uint8)
|
||||
return filter
|
||||
|
||||
def strings(str):
|
||||
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
|
||||
continue
|
||||
out = ""
|
||||
while i != len(str) and str[i] in string.ascii_letters+string.digits:
|
||||
out += str[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)
|
||||
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
|
||||
intensity = intensity.astype(float)
|
||||
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
|
||||
return intensity.astype(np.uint8)
|
||||
return filter
|
||||
|
||||
########################################################################
|
||||
# Web Api #
|
||||
@@ -336,6 +456,10 @@ def apps_start(name, param):
|
||||
def apps_log():
|
||||
return runner.getLog()
|
||||
|
||||
@route("/apps/crashlog")
|
||||
def apps_log():
|
||||
return runner.last_crashlog
|
||||
|
||||
@route("/apps/running")
|
||||
def apps_running():
|
||||
return config.Apps[runner.currentApp]["name"]
|
||||
@@ -344,22 +468,56 @@ def apps_running():
|
||||
def index():
|
||||
return bottle.static_file("index.html", root='html')
|
||||
|
||||
@route("/setgamma/<r>/<g>/<b>")
|
||||
def setGamma(r, g, b):
|
||||
@route("/setgamma/<r>/<g>/<b>/<w>")
|
||||
def setGamma(r, g, b, w):
|
||||
r = float(r)
|
||||
g = float(g)
|
||||
b = float(b)
|
||||
runner.setGamma(r, g, b)
|
||||
w = float(w)
|
||||
runner.setGamma(r, g, b, w)
|
||||
return "ok"
|
||||
|
||||
@route("/setbrightness/<i>")
|
||||
def setGamma(i):
|
||||
def setIntensity(i):
|
||||
i = float(i)
|
||||
if i < 0 or i > 1:
|
||||
return "bad_value"
|
||||
runner.intensity = i
|
||||
runner.setFilter("0_intensity", MakeBrightnessFilter(i))
|
||||
return "ok"
|
||||
|
||||
@route("/filter/flipx/<do>")
|
||||
def flipx(do):
|
||||
if do == "true":
|
||||
runner.setFilter("1_flipx", FlipXFilter)
|
||||
else:
|
||||
runner.removeFilter("1_flipx")
|
||||
return "ok"
|
||||
|
||||
@route("/filter/flipy/<do>")
|
||||
def flipy(do):
|
||||
if do == "true":
|
||||
runner.setFilter("1_flipy", FlipYFilter)
|
||||
else:
|
||||
runner.removeFilter("1_flipy")
|
||||
return "ok"
|
||||
|
||||
@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/")
|
||||
def filter_expr():
|
||||
expr = request.forms.get('expr')
|
||||
if expr == "" or expr == "none":
|
||||
runner.removeFilter("5_brightnessfunction")
|
||||
else:
|
||||
runner.setFilter("5_brightnessfunction", MakeBrightnessExprFilter(expr))
|
||||
return "ok"
|
||||
|
||||
|
||||
########################################################################
|
||||
# Startup #
|
||||
########################################################################
|
||||
@@ -369,15 +527,21 @@ for app in config.Apps:
|
||||
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"])
|
||||
#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)
|
||||
|
||||
########################################################################
|
||||
|
Reference in New Issue
Block a user