diff --git a/config.py b/config.py
index 5f03917..5f1e686 100644
--- a/config.py
+++ b/config.py
@@ -97,4 +97,6 @@ Apps = [
# App(guiname="Snake", name="snake", cmd="./snake.py"),
# App(name="gif", cmd="./gif.sh"),
# App(name="colormap", cmd="./colormap.py"),
+
+ AppConfig(guiname="Pixel Canvas", name="pixelcanvas", cmd=""),
]
diff --git a/html/draw.html b/html/draw.html
new file mode 100644
index 0000000..e56568e
--- /dev/null
+++ b/html/draw.html
@@ -0,0 +1,465 @@
+
+
+
+
+
+
+
+
diff --git a/main.py b/main.py
index c36f390..6a90cb6 100755
--- a/main.py
+++ b/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/