Live Painting Mode
This commit is contained in:
123
main.py
123
main.py
@@ -12,6 +12,11 @@ from collections import OrderedDict
|
||||
import bottle
|
||||
import numpy as np
|
||||
import serial
|
||||
# noinspection PyUnresolvedReferences
|
||||
import bottle.ext.websocket as bottle_ws
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bottle.ext.websocket import GeventWebSocketServer
|
||||
import geventwebsocket.websocket
|
||||
|
||||
import config
|
||||
import filters
|
||||
@@ -27,7 +32,7 @@ class DataSource:
|
||||
self.data = initial
|
||||
self.listeners = []
|
||||
|
||||
def getData(self):
|
||||
def getData(self) -> "Frame":
|
||||
return self.data
|
||||
|
||||
def addListener(self, listener):
|
||||
@@ -89,7 +94,7 @@ class LogReader(threading.Thread):
|
||||
|
||||
class Frame:
|
||||
def __init__(self, buffer, channels=3):
|
||||
self.buffer = buffer
|
||||
self.buffer: np.ndarray = buffer
|
||||
self.created = time.time()
|
||||
self.channels = channels
|
||||
|
||||
@@ -223,13 +228,14 @@ class SerialWriter(threading.Thread):
|
||||
# App #
|
||||
########################################################################
|
||||
class App(threading.Thread):
|
||||
def __init__(self, cmd, param, listener, is_persistent, is_white=False, path="."):
|
||||
def __init__(self, name, cmd, param, listener, is_persistent, is_white=False, path="."):
|
||||
super().__init__(daemon=True)
|
||||
# start app
|
||||
self.name = name
|
||||
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()
|
||||
# self.cv = threading.Condition()
|
||||
self.watchdog = WatchDog(lambda: self.isAppTimedOut(), lambda: self.terminateApp())
|
||||
self.watchdog.start()
|
||||
self.logreader = LogReader(self)
|
||||
@@ -253,8 +259,8 @@ class App(threading.Thread):
|
||||
frame = Frame(buffer, channels=channels)
|
||||
self.last_update = time.time()
|
||||
self.datasource.pushData(frame)
|
||||
except:
|
||||
logging.debug("Exception in App.run")
|
||||
except Exception as ex:
|
||||
logging.debug(f"Exception in App.run: {ex}")
|
||||
with self.listener:
|
||||
self.listener.notify_all()
|
||||
self.watchdog.stop()
|
||||
@@ -288,6 +294,35 @@ class App(threading.Thread):
|
||||
return time.time() - self.last_update > config.NoDataTimeout
|
||||
|
||||
|
||||
class PixelCanvas(App):
|
||||
# noinspection PyMissingConstructor
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self, daemon=True)
|
||||
self.name = "pixelcanvas"
|
||||
self.running = True
|
||||
self.is_persistent = True
|
||||
self.datasource = DataSource(Frame(np.zeros((config.ScreenY, config.ScreenX, 3))))
|
||||
|
||||
def run(self):
|
||||
while running and self.running:
|
||||
time.sleep(1)
|
||||
|
||||
def alive(self):
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def getLog(self):
|
||||
return ""
|
||||
|
||||
def terminateApp(self):
|
||||
pass
|
||||
|
||||
def isAppTimedOut(self):
|
||||
return False
|
||||
|
||||
|
||||
########################################################################
|
||||
# Main #
|
||||
########################################################################
|
||||
@@ -322,7 +357,10 @@ class AppRunner(threading.Thread):
|
||||
|
||||
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)
|
||||
if app.name == "pixelcanvas":
|
||||
newapp = PixelCanvas()
|
||||
else:
|
||||
newapp = App(app.name, 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:
|
||||
@@ -451,6 +489,75 @@ def apps_running():
|
||||
return config.Apps[runner.currentApp].name
|
||||
|
||||
|
||||
@bottle.route("/frame")
|
||||
def frame():
|
||||
data = runner.datasource.getData()
|
||||
return {"data": data.buffer.flatten().tolist(), "channels": data.channels}
|
||||
|
||||
|
||||
@bottle.route("/pixel/<x:int>/<y:int>/<r:int>/<g:int>/<b:int>/<w:int>")
|
||||
def pixel(x, y, r, g, b, w):
|
||||
if runner.app.name != "pixelcanvas":
|
||||
startApp("pixelcanvas")
|
||||
|
||||
data = runner.app.datasource.getData().clone()
|
||||
data.created = time.time()
|
||||
data.buffer[y][x][0] = r
|
||||
data.buffer[y][x][1] = g
|
||||
data.buffer[y][x][2] = b
|
||||
if data.channels == 4:
|
||||
data.buffer[y][x][3] = w
|
||||
runner.datasource.pushData(data)
|
||||
return "ok"
|
||||
|
||||
|
||||
@bottle.get("/frame_ws", apply=[bottle_ws.websocket])
|
||||
def frame_ws(ws: geventwebsocket.websocket.WebSocket):
|
||||
while not ws.closed:
|
||||
msg = json.loads(ws.receive())
|
||||
|
||||
if msg["ty"] == "frame":
|
||||
data = runner.datasource.getData()
|
||||
if msg.get("time", None) == str(data.created):
|
||||
ws.send(json.dumps({
|
||||
"type": "frame_unchanged",
|
||||
}, separators=(',', ':')))
|
||||
else:
|
||||
ws.send(json.dumps({
|
||||
"ty": "frame",
|
||||
"data": data.buffer.flatten().tolist(),
|
||||
"channels": data.channels,
|
||||
"time": str(data.created),
|
||||
}, separators=(',', ':')))
|
||||
elif msg["ty"] == "pixel":
|
||||
if runner.app.name != "pixelcanvas":
|
||||
startApp("pixelcanvas")
|
||||
|
||||
x = msg["x"]
|
||||
y = msg["y"]
|
||||
|
||||
data = runner.app.datasource.getData().clone()
|
||||
data.created = time.time()
|
||||
data.buffer[y, x, 0] = msg["r"]
|
||||
data.buffer[y, x, 1] = msg["g"]
|
||||
data.buffer[y, x, 2] = msg["b"]
|
||||
if data.channels == 4 and "w" in msg:
|
||||
data.buffer[y, x, 3] = msg["w"]
|
||||
runner.app.datasource.pushData(data)
|
||||
elif msg["ty"] == "fill":
|
||||
if runner.app.name != "pixelcanvas":
|
||||
startApp("pixelcanvas")
|
||||
|
||||
data = runner.app.datasource.getData().clone()
|
||||
data.created = time.time()
|
||||
data.buffer[..., 0] = msg["r"]
|
||||
data.buffer[..., 1] = msg["g"]
|
||||
data.buffer[..., 2] = msg["b"]
|
||||
if data.channels == 4 and "w" in msg:
|
||||
data.buffer[..., 3] = msg["w"]
|
||||
runner.app.datasource.pushData(data)
|
||||
|
||||
|
||||
@bottle.route("/")
|
||||
def index():
|
||||
return bottle.static_file("index.html", root='html')
|
||||
@@ -523,7 +630,7 @@ def main():
|
||||
runner.start()
|
||||
|
||||
# runner.setFilter("5_crazy", MakeBrightnessExprFilter("0.5+0.25*sin(x/3)/x"))
|
||||
bottle.run(host=config.WebHost, port=config.WebPort)
|
||||
bottle.run(host=config.WebHost, port=config.WebPort, server=GeventWebSocketServer)
|
||||
|
||||
########################################################################
|
||||
# Shutdown #
|
||||
|
Reference in New Issue
Block a user