From 33addf364ed437c5a3fbde1c51a2109c56c6ce9c Mon Sep 17 00:00:00 2001
From: Fischertechnik-OpenSource <fischertechnik-opensource@online.de>
Date: Wed, 29 Nov 2023 15:21:04 +0100
Subject: [PATCH] Update to

---
 Dockerfile                                    |  26 +--
 DockerfilePublish                             |  20 --
 README.md                                     |  27 ---
 apache/controller.crt                         |  21 --
 apache/controller.key                         |  28 ---
 apache/default.conf                           |  10 -
 community-fw/README.md                        |  54 -----
 community-fw/api-app/icon.png                 | Bin 3812 -> 0 bytes
 community-fw/api-app/manifest                 |  11 -
 community-fw/api-app/refresh.py               |  10 -
 community-fw/api-app/start.py                 |  78 -------
 community-fw/deploy/colorized_output.py       |  27 ---
 community-fw/deploy/deployment.py             |  36 ---
 community-fw/deploy/mounts.py                 |  37 ----
 community-fw/deploy/python_environment.py     |  54 -----
 community-fw/deploy/repository.py             |  29 ---
 community-fw/fabfile.py                       |  57 -----
 docker-compose.yml                            |  13 --
 entrypoint.sh                                 |   6 -
 openapi.json                                  | 208 +++++++++++++++++-
 start-dev.sh                                  |   7 -
 txt2/api-app/icon.png                         | Bin 3812 -> 0 bytes
 txt2/api-app/manifest                         |  11 -
 txt2/api-app/refresh.py                       |  10 -
 txt2/api-app/start.py                         |  78 -------
 txt2/deploy/colorized_output.py               |  27 ---
 txt2/deploy/deployment.py                     |  24 --
 txt2/deploy/repository.py                     |  28 ---
 txt2/fabfile.py                               |  39 ----
 txtapi/.openapi-generator/FILES               |   5 +-
 txtapi/requirements.txt                       |   5 +-
 txtapi/setup.py                               |   7 +-
 txtapi/txtapi/cache.py                        |   6 +-
 .../txtapi/controllers/remote_controller.py   |  55 ++++-
 txtapi/txtapi/controllers/util_controller.py  |   2 +-
 txtapi/txtapi/handlers/remote_handler.py      |  60 ++++-
 txtapi/txtapi/handlers/util_handler.py        |  15 +-
 txtapi/txtapi/handlers/workspace_handler.py   |  41 +++-
 txtapi/txtapi/models/__init__.py              |   5 +-
 txtapi/txtapi/models/attribute.py             |  90 ++++++++
 txtapi/txtapi/models/controller_file.py       | 116 ++++++++++
 txtapi/txtapi/models/file.py                  |  90 --------
 txtapi/txtapi/models/remote_event.py          |  92 ++++++++
 txtapi/txtapi/models/voice_event.py           |  64 ++++++
 txtapi/txtapi/openapi/openapi.yaml            | 198 ++++++++++++++++-
 45 files changed, 929 insertions(+), 898 deletions(-)
 delete mode 100644 DockerfilePublish
 delete mode 100644 apache/controller.crt
 delete mode 100644 apache/controller.key
 delete mode 100644 apache/default.conf
 delete mode 100644 community-fw/README.md
 delete mode 100755 community-fw/api-app/icon.png
 delete mode 100755 community-fw/api-app/manifest
 delete mode 100755 community-fw/api-app/refresh.py
 delete mode 100755 community-fw/api-app/start.py
 delete mode 100755 community-fw/deploy/colorized_output.py
 delete mode 100644 community-fw/deploy/deployment.py
 delete mode 100755 community-fw/deploy/mounts.py
 delete mode 100755 community-fw/deploy/python_environment.py
 delete mode 100755 community-fw/deploy/repository.py
 delete mode 100755 community-fw/fabfile.py
 delete mode 100644 docker-compose.yml
 delete mode 100755 entrypoint.sh
 delete mode 100755 start-dev.sh
 delete mode 100755 txt2/api-app/icon.png
 delete mode 100755 txt2/api-app/manifest
 delete mode 100755 txt2/api-app/refresh.py
 delete mode 100755 txt2/api-app/start.py
 delete mode 100755 txt2/deploy/colorized_output.py
 delete mode 100644 txt2/deploy/deployment.py
 delete mode 100755 txt2/deploy/repository.py
 delete mode 100755 txt2/fabfile.py
 create mode 100644 txtapi/txtapi/models/attribute.py
 create mode 100644 txtapi/txtapi/models/controller_file.py
 delete mode 100644 txtapi/txtapi/models/file.py
 create mode 100644 txtapi/txtapi/models/remote_event.py
 create mode 100644 txtapi/txtapi/models/voice_event.py

diff --git a/Dockerfile b/Dockerfile
index 5e038a2..92fea90 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,26 +1,20 @@
-FROM python:3.5.6-stretch
+FROM python:3.8 AS builder
 
-ARG REPO_USER
-ARG REPO_PASSWORD
-ARG DOWNLOAD_URL=https://${REPO_USER}:${REPO_PASSWORD}@update.fischertechnik-cloud.com/repository/ft-txt-lib/simple/
+ARG TWINE_USERNAME
+ARG TWINE_PASSWORD
 
-RUN apt-get update && apt-get install -y apache2 apache2-dev python-smbus
+ENV TWINE_USERNAME=$TWINE_USERNAME
+ENV TWINE_PASSWORD=$TWINE_PASSWORD
 
-ADD apache/default.conf /etc/apache2/sites-enabled/000-default.conf
-ADD apache/controller.crt /etc/ssl/crt/controller.crt
-ADD apache/controller.key /etc/ssl/crt/controller.key
+WORKDIR /usr/src/app
 
-ADD ./txtapi /var/www/txtapi
-RUN mkdir -p /var/www/txtapi/workspace
+RUN pip install --no-cache-dir twine
 
-RUN chown -R www-data:www-data /var/www/txtapi
+ADD . /usr/src/app
 
-RUN a2enmod ssl proxy proxy_http
+RUN cd /usr/src/app/txtapi && \
+    ./publish.sh
 
-RUN pip3 install --extra-index-url $DOWNLOAD_URL txtapi numpy opencv-python
 
-WORKDIR /var/www/txtapi
 
-COPY entrypoint.sh ./
 
-CMD ./entrypoint.sh
diff --git a/DockerfilePublish b/DockerfilePublish
deleted file mode 100644
index 92fea90..0000000
--- a/DockerfilePublish
+++ /dev/null
@@ -1,20 +0,0 @@
-FROM python:3.8 AS builder
-
-ARG TWINE_USERNAME
-ARG TWINE_PASSWORD
-
-ENV TWINE_USERNAME=$TWINE_USERNAME
-ENV TWINE_PASSWORD=$TWINE_PASSWORD
-
-WORKDIR /usr/src/app
-
-RUN pip install --no-cache-dir twine
-
-ADD . /usr/src/app
-
-RUN cd /usr/src/app/txtapi && \
-    ./publish.sh
-
-
-
-
diff --git a/README.md b/README.md
index 0a7c1b1..3db4feb 100644
--- a/README.md
+++ b/README.md
@@ -19,33 +19,6 @@ Konfiguriere einen Git-Hook, sodass die Versionsnummern automatisch inkrementier
 
 > git config --local core.hooksPath .githooks/
 
-## Docker
-Start der Api:
-```code
-docker-compose up --build
-```
-Danach erreich unter **http://localhost/api/v1/ui/**
-
-## Community FW
-Vorbereitung:
-1. Auf dem eigenen Rechner müssen Python3 und pip vorhanden sein
-2. Per pip fabric installieren (pip install fabric)
-3. Auf dem TXT muss die Community Firmware installiert sein und das WLAN eingerichet sein
-
-Ablauf:
-1. fabric deploy -h 'ip-adresse'
-* Auf dem Controller muss der SSH und root Zugriff der Tastendruck bestätigt werden
-* An einer Stelle wird nach Credentials gefragt, hier bitte die Accountdaten für update.fischertechnik-cloud.com eingeben
-2. Im Menü des Controller die Applikation TXT-API ausführen
-3. Sobald der Status auf 'running' steht, kann der Controller genutzt werdne
-4. ROBOPro2 öffnen (nicht die Webapplikation)
-* [https://update.fischertechnik-cloud.com/#browse/browse:ft-roboticstudio]
-5. Ãœber das kleine Router Symbol kann der Controller verbunden werden
-* Im Dialogfeld die Ip-Adresse des Controllers und den Port 8080 eingeben (Beispiel: 192.168.1.2:8080)
-* Der Port 8080 ist aktuell noch notwendig, da in der Community Firmware automatisch ein Server startet, der Port 80 allokiert
-6. Wenn alles funktioniert hat, sollte der Router nun grün sein (rot im Fehlerfall)
-7. Über den Start- und den Stopknopf kann nun Code ausgeführt und beendet werden.
-
 ## Release durchführen
 
 * Prüfen ob der letzte Stand ohne Änderungen lokal vorhanden ist 
diff --git a/apache/controller.crt b/apache/controller.crt
deleted file mode 100644
index b547741..0000000
--- a/apache/controller.crt
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDeDCCAmCgAwIBAgIJAKH0imK5rYHkMA0GCSqGSIb3DQEBCwUAMFExCzAJBgNV
-BAYTAkRFMQwwCgYDVQQIDANOUlcxETAPBgNVBAcMCERvcnRtdW5kMSEwHwYDVQQK
-DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTkxMDA5MTE0MTE0WhcNMjAx
-MDA4MTE0MTE0WjBRMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMREwDwYDVQQH
-DAhEb3J0bXVuZDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIB
-IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwGTI5U+BiY0fbIFCyvHsIC2E
-4R/TmaUCAZ4EA1l9VeSNzhlmR/F2vsSrnQtyHBqeTrHM4k06QO4Pbb5/eXYot/Im
-A7UuFmxNzYVj/AGCLjLXPxdQcSCuBgaUdDYDF0gjppY/TBuYt2Uizrb9U0HJIo+u
-RoiIqz2qk2L9VPE1X6PeGDUdUdqnlvf4xj/YWKtrh83AhY8E5uBnIjNj7sy/t3sL
-li8k31p+/sa0QQCE/JPW3z/VyZDoIBTPCDTYr2mTvZGeXw+qFgY48+SsVwmWQOlg
-J0O5xh+nRqWRN6BXj7uDSDMwBk/NVAezK8o8brZJELEVha4avCQ7eWi4lmwMFwID
-AQABo1MwUTAdBgNVHQ4EFgQUSabsfhwSDoHTRRrJXnWGzEil5F8wHwYDVR0jBBgw
-FoAUSabsfhwSDoHTRRrJXnWGzEil5F8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
-9w0BAQsFAAOCAQEAb8J1q1bKoXeUiX2bVEmoNMNEI4MfKOEiSWvkaCie3w814TWN
-VFfVRBEr8PdiOC2lNgOOBag4CwSJYTYvZLyZEf+Yca6xgkMc0BwP9wW4Y72/r9X0
-sRQjk1/4nMpnFZap7VW+kJHLjRHYTS367RFxAKVyLwKDE4E1ZN2DabfE1GAM09za
-BvrDXi5qRA/ZF6fepaaHMagIRDJ+p14eibCgFAY+09ivDAM7jW3yEa1z36zfCjQZ
-Z7c6hpi2nal93eI6CuebQLsiR6tNkVvrYi/fDFXLkyP+QrcvKLdDLFHWm37XsZt4
-MltWDFtNUg52wULK2IUG0ffr/uAf18mc7lZcZQ==
------END CERTIFICATE-----
diff --git a/apache/controller.key b/apache/controller.key
deleted file mode 100644
index beb127f..0000000
--- a/apache/controller.key
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAZMjlT4GJjR9s
-gULK8ewgLYThH9OZpQIBngQDWX1V5I3OGWZH8Xa+xKudC3IcGp5OscziTTpA7g9t
-vn95dii38iYDtS4WbE3NhWP8AYIuMtc/F1BxIK4GBpR0NgMXSCOmlj9MG5i3ZSLO
-tv1TQckij65GiIirPaqTYv1U8TVfo94YNR1R2qeW9/jGP9hYq2uHzcCFjwTm4Gci
-M2PuzL+3ewuWLyTfWn7+xrRBAIT8k9bfP9XJkOggFM8INNivaZO9kZ5fD6oWBjjz
-5KxXCZZA6WAnQ7nGH6dGpZE3oFePu4NIMzAGT81UB7MryjxutkkQsRWFrhq8JDt5
-aLiWbAwXAgMBAAECggEAcUonnhgOIDAwg9UtvpFsSJplN6dlE4E9yDQDCvHkQvK9
-qdH9D3oZQUZQA77cp73o4FAEZHGzTs6f2TCmoaA5Y++8AMzsYZnOyqm0cY112bxo
-USdqX+MtdjDjs/amJUx+abbpeOh7Kers7yqDy0XeqXJP3grB54v9aKKOqDkNA85W
-IG480T8HaOmwKBOYqI6cZT3g14PsxR6jVbmPJ0aSwrB41IuPYkRYpnq8iKESlvDe
-4MMaLv/5MKKQcPyRp0R9NsIDgiXndHv48p/NKuNd2bDM93DUyoxriMZI+ji5V1Kr
-65ao9kLhLX8pBl5RZU5FsgAvl3Ci6eM8JnPVbMlvMQKBgQDj9f03YOadt12OSi5a
-yLHVZ8pWZOboYt8koBtHdRtLlsGIitKDvnWQltQSjQuCXFe2fWnryQegiEpv/xnR
-FsBk1X2Wn15ScpP7GuAqoCKj3AstQGweUpIVf3l6XvCFcwIkFrkw6PBH87wU8w33
-W+PovMwq3+WyNJqj86BcxXmQ2QKBgQDYDtvf0KzDFf+m5ifXUKDUBlo8SUoajvRT
-5+iRinVRtDdV/rjbmUwUKoLC5u0Z+fIRw5U3W03BJO/PqIu5kMd0w5yHkLeMJeUC
-A8KQEK2/Fr0opq7E0QG7Kgj3MZ6pj21EzYQ8OzSvObmYHyxyXufSq6UrcqEMZBKo
-E18wcqFubwKBgGLfSZGgZMYZRum1QP+9NmySFM99+izm3VPcYZiPsWQKoixf0ci0
-bfdlg9v78vb6qEyNfsh2q+kz091Zrs/iZ1YUxuDyhI2MBqUN1haG2B9sDCj3XS2V
-sKjEXmL2FKo85LvUY0RUdAsxKu2HMhKMd4B1irQ54j111XCw9Wfner+ZAoGBAMhr
-GCkI0IlzCaBPVVvVDis/7UqJDbWPMDP5JvKDBosQ6lfaHq6OCFWYjY2wWvbCtXsm
-+27LQlhNJCt0BJDRLUQyBCx44NqfeEyjIkMzRYjB1hw0amBmJP3yYziyjaRqSIkP
-P4ADx75XhMMI/9jkEpWI5YFlJuFwnyHMPnuZigf/AoGAQ41H+ZPYo5LP+H2nlBxo
-8hkp9SidlvPevmTvTqWJSEzmy2p4OzxHrWsr7/Jl5YqCW8CxPr6gl8kizotN3V9p
-0/y0jJ6KCs5M8+O/mm6jVkey2sle78Fnv5IOWDo0CkOJ3kRKJBzV80iHxKTGCJkK
-IlfDd465De99tYAOzQuiVBY=
------END PRIVATE KEY-----
diff --git a/apache/default.conf b/apache/default.conf
deleted file mode 100644
index 418909b..0000000
--- a/apache/default.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-<VirtualHost *:80>
-    ErrorLog /dev/stdout
-    LogLevel info
-    <Proxy *>
-        Order allow,deny
-        Allow from all
-    </Proxy>
-    ProxyPass /api http://localhost:8080/api
-    ProxyPassReverse /api http://localhost:8080/api
-</VirtualHost>
\ No newline at end of file
diff --git a/community-fw/README.md b/community-fw/README.md
deleted file mode 100644
index 30b31fd..0000000
--- a/community-fw/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# Fischertechnik TXT
-
-Dieses Projekt dient dem vollständig automatischen Einrichten des Fischertechnik-TXT für die Entwicklung des RoboticStudio. Dazu wird ein SSH-Zugriff ohne Login auf den TXT eingerichtet, entsprechende Berechtigungen hinterlegt, Python installiert und der eigentliche Quellcode auf dem Server hinterlegt. Das Projekt gliedert sich im Wesentlichen in 3 Ordner:
-
-- deploy: Hier sind die Skripte zum Deployment auf dem TXT untergebracht.
-- flaskApp: Hier befindet sich die grafische Oberfläche, mit der der Swagger-Server auf dem TXT gesteuert werden kann.
-- flaskConnnexion: Hier liegt der eigentliche Swagger-Server.
-
-Üblicherweise wird beim ersten Einrichten des TXT einmal das komplette Deployment durchgeführt; nach einem Neustart lediglich der Task `mount`. Wird am Swagger-Server entwickelt, so kann mit `deploy_flask_app` der Python-Code der grafischen Oberfläche und des Swagger-Servers aktualisiert werden.
-
-## Vorraussetzungen
-Auf dem Rechner, von dem aus deployt werden soll, muss `python3` installiert sein. Zusätzlich wird die Python-Bibliothek `fabric` (https://github.com/fabric/fabric) benötigt. Darüber hinaus muss der TXT im selben WLAN sein und die Fischertechnik-CommunityEdition gebootet sein.
-
-## Ausführen des Skriptes
-Ist die Bibliothek `fabric` lokal installiert, steht der Command `fab` zum Ausführen der Tasks aus der sogenannten Fabfile zur Verfügung. Diese beinhaltet fünf verschiedene Tasks zum Deployment, die im folgenden erklärt werden. Es können alle Tasks mit dem Befehl `fab --list` angezeigt werden
-
-### Komplettes Deployment durchführen: deploy
-Der auszuführende Task lautet:
-```bash
-fab deploy -h '<ip>'
-```
-Dieser ist ein Sammeltask und führt die folgenden 4 Tasks nacheinander aus. Das Skript beinhaltet einige Print-Ausgaben und Anweisungen, sodass der Prozess des Einrichtens auf der lokalen Kommandozeile dokumentiert wird.
-
-### SSH-Key auf den Server kopieren: copy_ssh_key
-Der auszuführende Task lautet:
-```bash
-fab copy_ssh_key -h '<ip>'
-```
-Damit die Python-Skripte auf dem Controller ausgeführt werden können, muss der Public-SSH-Key des Rechners, von dem deployt werden soll, auf dem TXT in der Datei `.ssh/authorized_keys` liegen. Ist dies nicht der Fall werden je nach eigener SSH-Konfiguration unterschiedliche Fehler geworfen und das Deployment läuft **nicht** durch. Das Kopieren des SSH-Keys übernimmt das Skript automatisch; es lädt die Datei `.ssh/id_rsa.pub` und kopiert diese mittels SCP auf den Server in die entsprechende Datei. Hierzu ist zweimal das Erlauben des SSH-Logins über die Nutzeroberfläche des TXT notwendig.
-
-### Prüfen der Mounts: mount
-Der auszuführende Task lautet:
-```bash
-fab mount -h '<ip>'
-```
-Damit die Python Skripte korrekt ausgeführt werden können, müssen die Mount-Points richtig initialisiert werden. Das Skript prüft automatisch, ob die Mounts in der `/etc/mtab` eingetragen sind und wenn nicht versucht es diese hinzuzufügen. Dazu muss auf dem Display zweimal der Root-Zugriff bestätigt werden. Stellt das Skript darüber hinaus fest, dass die Mount-Points noch nicht einmal initialisiert wurden, erstellt es die Verzeichnisse unter `~/mounts` und kopiert die Dateien in diese. Eine Besonderheit stellt dabei die Datei `/usr/bin/sudo`, also der Sudo-Command, dar. Diese Datei muss den Eigentümer root und das setuid-Bit muss gesetzt sein. Um diesen Schritt kümmert sich das Skript ebenfalls automatisiert, jedoch ist hier zusätzlich zweimal der Root-Zugriff auf dem Display zu bestätigen.
-
-### Prüfen von Pip und den benötigten Dependencies: install_python
-Der auszuführende Task lautet:
-```bash
-fab install_python -h '<ip>'
-```
-Damit alle Anforderungen für den Flask-Server vorhanden sind, werden alle Dependencies auf dem TXT geprüft. Im ersten Schritt wird geprüft, ob pip installiert ist. Ist dies nicht der Fall, wird pip auf dem TXT installiert. Danach werden mittels des Commands `pip list --format=freeze` einmal alle installierten Bibliotheken ausgegeben und mit der lokalen Datei `flaskConnexion/requirements.txt` abgeglichen. Fehlen Bibliotheken, werden diese nachinstalliert. Dies bedeutet auch, dass nach einer Anpassung der lokalen Requirements die neuen Bibliotheken auch auf dem TXT installiert werden.
-
-### Hochladen des Flask-Servers: deploy_flask_app
-Der auszuführende Task lautet:
-```bash
-fab deploy_flask_app -h '<ip>'
-```
-Zuletzt muss noch der Quellcode auf den TXT geladen werden. Hierzu werden diw Order `flaskConnexion` (Flask-Server) und `flaskApp` (GUI-App) mithilfe von tar zu einem Archiv gepackt, dann auf den TXT hochgeladen und wieder entpackt. Die GUI-App wird derart angepasst, dass diese ausführbar ist. Zusätzlich wird die Oberfläche des TXT neu geladen, damit die neue App erscheint. Anschließend wird die IP in der `swagger.yml` auf die aktuelle IP des TXT gesetzt.
-
-## Verwendung der Flask-App
-
-Um die App zu Starten geht man auf dem TXT in den Ordner Demos und startet die Anwendung `Flask`. Die Anwendung läuft in zwei separaten Prozessen; einer für die grafische Oberfläche und ein zweiter für den Swagger-Server. Die grafische Oberfläche hat eine Status-Anzeige sowie jeweils einen Start- und Stop-Button. Mit dem Start Button wird der Swagger-Server gestartet; dies dauert ca. 25 Sekunden. Danach kann die grafische Oberfläche verlassen werden; der Swagger-Server läuft im Hintergrund weiter. Die Swagger-UI ist unter http://ip-of-my-txt:8080/v1/ui erreichbar; die API selbst unter steht unter http://ip-of-my-txt:8080/v1 zur Verfügung. Mit dem Stop-Button wird der Swagger-Server beendet.
\ No newline at end of file
diff --git a/community-fw/api-app/icon.png b/community-fw/api-app/icon.png
deleted file mode 100755
index 3b3a38a4bfb2f011b6345ee2963517cc6e8f1e3b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3812
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
aZT<&I5A)s^{jOC20000<MNUMnLSTX@*cgTY

diff --git a/community-fw/api-app/manifest b/community-fw/api-app/manifest
deleted file mode 100755
index 3b1e3e1..0000000
--- a/community-fw/api-app/manifest
+++ /dev/null
@@ -1,11 +0,0 @@
-[app]
-name: TXT-API
-category: IDE
-author: Beemo
-icon: icon.png
-desc: TXT-API for fischertechnik
-exec: start.py
-managed: yes
-uuid: 191fe5a6-313b-4083-af65-d1ad7fd6d281
-version: 0.1
-firmware: 0.9
\ No newline at end of file
diff --git a/community-fw/api-app/refresh.py b/community-fw/api-app/refresh.py
deleted file mode 100755
index 787e9d3..0000000
--- a/community-fw/api-app/refresh.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import socket
-
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-try:
-    print("Requesting rescan ...")
-    # Connect to server and send data
-    sock.connect(("localhost", 9000))
-    sock.sendall(bytes("rescan\n", "UTF-8"))
-except socket.error as msg:
-    print("Unable to connect to Launcher: ", msg)
\ No newline at end of file
diff --git a/community-fw/api-app/start.py b/community-fw/api-app/start.py
deleted file mode 100755
index a41778e..0000000
--- a/community-fw/api-app/start.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#! /usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import glob
-import os
-import signal
-import sys
-import time
-
-from TouchStyle import *
-
-class FtcGuiApplication(TouchApplication):
-    def __init__(self, args):
-        TouchApplication.__init__(self, args)
-
-        self.process = "txtapi"
-
-        # Creates an empty MainWindow
-        w = TouchWindow("TXT-API")
-
-        # Create a label
-        vbox = QVBoxLayout()
-
-        status = QLabel()
-        status.setText('Server status:')
-        status.setAlignment(Qt.AlignCenter)
-        vbox.addWidget(status)
-
-        self.info = QLabel()
-        self.info.setAlignment(Qt.AlignCenter)
-        vbox.addWidget(self.info)
-
-        self.btnStart = QPushButton('Start')
-        self.btnStart.clicked.connect(self.startBackgroundServer)
-        vbox.addWidget(self.btnStart)
-
-        self.btnStop = QPushButton('Stop')
-        self.btnStop.clicked.connect(self.stopBackgroundServer)
-        vbox.addWidget(self.btnStop)
-
-        w.centralWidget.setLayout(vbox)
-
-        self.checkServerStatus()
-
-        w.show()
-        self.exec_()
-
-    def initBackgroundServer(self):
-        self.info.setText('booting')
-        self.info.setStyleSheet('color: orange; background-color: white')
-        self.btnStart.setDisabled(True)
-        self.processEvents()
-
-    def startBackgroundServer(self):
-        self.initBackgroundServer()
-        os.system("cd /home/ftc/txtapi && {0} &".format(self.process))
-        time.sleep(25)
-        self.checkServerStatus()
-
-    def stopBackgroundServer(self):
-        os.system("killall -9 {0}".format(self.process))
-        self.checkServerStatus()
-
-    def checkServerStatus(self):
-        processes = os.popen("ps -Af").read()
-        if self.process in processes:
-            self.info.setText('running')
-            self.info.setStyleSheet('color: green; background-color: white')
-            self.btnStart.setDisabled(True)
-            self.btnStop.setDisabled(False)
-        else:
-            self.info.setText('not running')
-            self.info.setStyleSheet('color: red; background-color: white')
-            self.btnStart.setDisabled(False)
-            self.btnStop.setDisabled(True)
-
-if __name__ == "__main__":
-    FtcGuiApplication(sys.argv)
diff --git a/community-fw/deploy/colorized_output.py b/community-fw/deploy/colorized_output.py
deleted file mode 100755
index cb1305c..0000000
--- a/community-fw/deploy/colorized_output.py
+++ /dev/null
@@ -1,27 +0,0 @@
-class Color:
-    HEADER = '\033[95m'
-    OKBLUE = '\033[94m'
-    OKGREEN = '\033[92m'
-    WARNING = '\033[93m'
-    FAIL = '\033[91m'
-    ENDC = '\033[0m'
-    BOLD = '\033[1m'
-    UNDERLINE = '\033[4m'
-
-def print_colored(string, color):
-    print(color + string + Color.ENDC)
-
-def print_bold(string):
-    print_colored(string, Color.BOLD)
-
-def print_warning(string):
-    print_colored(string, Color.WARNING)
-
-def print_success(string):
-    print_colored(string, Color.OKGREEN)
-
-def print_info(string):
-    print_colored(string, Color.OKBLUE)
-
-def print_error(string):
-    print_colored(string, Color.FAIL)
\ No newline at end of file
diff --git a/community-fw/deploy/deployment.py b/community-fw/deploy/deployment.py
deleted file mode 100644
index b7e371c..0000000
--- a/community-fw/deploy/deployment.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import os
-
-from deploy import colorized_output as co
-from deploy.mounts import Mounts
-from deploy.python_environment import PythonEnvironment
-from deploy.repository import Repository
-
-from fabric import Connection
-
-USER = 'ftc'
-
-class Deployment:
-    def __init__(self, host):
-        self.host = host
-        self.con = Connection(host = host, user = USER)
-
-    def copy_ssh_key(self):
-        co.print_warning('Please authorize ssh-login on txt to copy ssh-key to authorized keys')
-        os.system("ssh {0}@{1} 'mkdir -p .ssh' && scp $HOME/.ssh/id_rsa.pub {0}@{1}:.ssh/authorized_keys".format(USER, self.host))
-
-    def check_mounts(self):
-        mnts = Mounts(self.con)
-        mnts.check_mounts()
-
-    def check_pip_and_libraries(self):
-        pyenv = PythonEnvironment(self.con)
-        pyenv.check_pip()
-        pyenv.check_pip_libraries()
-        pyenv.add_ftc_path()
-
-    def deploy_flask_app(self):
-        repo = Repository(self.con)
-        repo.uploadFolder('../txtapi')
-        repo.uploadAppFolder('api-app')
-        repo.replace_ip(self.host)
-        repo.replace_env('environment_cfw.py')
diff --git a/community-fw/deploy/mounts.py b/community-fw/deploy/mounts.py
deleted file mode 100755
index 0b96293..0000000
--- a/community-fw/deploy/mounts.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from deploy import colorized_output as co
-
-class Mounts:
-    MOUNTED_SUDO = '/home/ftc/mounts/usr/bin/sudo'
-
-    def __init__(self, connection):
-        self.connection = connection
-        self.mounts = self.connection.run('cat /etc/mtab').stdout.strip().split()
-
-    def create_mounts(self):
-        co.print_info('Copying files to local mount folders')
-        self.copy_folder_to_mounts('/usr/lib/python3.7/.', '~/mounts/python3.7')
-        self.copy_folder_to_mounts('/usr/bin', '~/mounts/usr')
-        self.chown_mounted_sudo()
-
-    def copy_folder_to_mounts(self, src, dst):
-        self.connection.run("mkdir -p {0}".format(dst))
-        self.connection.run("cp -r {0} {1}".format(src, dst))
-
-    def chown_mounted_sudo(self):
-        self.connection.sudo("chown root:root {0}".format(Mounts.MOUNTED_SUDO))
-        self.connection.sudo("chmod 4755 {0}".format(Mounts.MOUNTED_SUDO))
-
-    def check_mounts(self):
-        if not self.connection.run('ls mounts', warn=True):
-            self.create_mounts()
-        self.check_mount('/usr/lib/python3.7', '/home/ftc/mounts/python3.7')
-        self.check_mount('/usr/bin', '/home/ftc/mounts/usr/bin')
-
-    def check_mount(self, mnt, mnt_dir):
-        co.print_info("Checking mount {0}".format(mnt))
-        if mnt in self.mounts:
-            co.print_success("--> Already mounted")
-        else:
-            co.print_warning("--> Not mounted. Please authorize sudo on txt to mount it")
-            self.connection.sudo("mount {0} {1}".format(mnt_dir, mnt))
-            co.print_success("--> Sucessfully mounted")
\ No newline at end of file
diff --git a/community-fw/deploy/python_environment.py b/community-fw/deploy/python_environment.py
deleted file mode 100755
index 683a544..0000000
--- a/community-fw/deploy/python_environment.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from deploy import colorized_output as co
-
-class PythonEnvironment:
-    def __init__(self, connection):
-        self.connection = connection
-
-    def add_ftc_path(self):
-        co.print_info('Add /opt/ftc to PYTHONPATH')
-        self.connection.run('echo "export PYTHONPATH=/opt/ftc" > /home/ftc/.profile', warn=True)
-        co.print_success('--> Important for ui applications')
-
-    def check_pip(self):
-        co.print_info("Checking pip-installation")
-        if self.connection.run('ls /usr/bin | grep pip3.7', warn=True):
-            co.print_success('--> Already installed')
-        else:
-            co.print_warning('--> Not installed. Starting installation of pip')
-            self.install_pip('~/tmp')
-            co.print_success('--> Successfully installed pip')
-
-    def install_pip(self, tmp_dir):
-        self.connection.run("mkdir -p {0}".format(tmp_dir))
-        self.connection.run("wget https://bootstrap.pypa.io/get-pip.py -O/home/ftc/tmp/get-pip.py".format(tmp_dir))
-        self.connection.run("python {0}/get-pip.py".format(tmp_dir))
-        self.connection.run("rm -rf {0}".format(tmp_dir))
-
-    def check_pip_libraries(self):
-        co.print_info('Checking pip libraries')
-        requirements = self.get_requirements()
-        pip_list = self.get_pip_list()
-        for requirement in requirements:
-            search = requirement.strip().replace(' ', '')
-            self.check_pip_library(pip_list, search)
-
-    def check_pip_library(self, dependencies, dependency):
-        co.print_info("Checking {0}:".format(dependency))
-        if dependency in dependencies:
-            co.print_success("--> Already installed")
-        else:
-            co.print_warning("--> Not installed. Starting installation")
-            if dependency.find('ft-') == -1 and dependency.find('ftrobopy') == -1 and dependency.find('txtapi') == -1:
-                self.connection.run("pip install {0}".format(dependency))
-            else:
-                co.print_info("Downloading from custom repository")
-                co.print_info("Enter credentials if needed")
-                self.connection.run("pip install --index-url https://update.fischertechnik-cloud.com/repository/ft-txt-lib/simple/ --no-deps {0}".format(dependency))
-            co.print_success("--> Successfully installed")
-
-    def get_pip_list(self):
-        return self.connection.run('pip list --format=freeze').stdout.strip().split()
-
-    def get_requirements(self):
-        fd = open('../txtapi/requirements.txt', 'r')
-        return fd.readlines()
diff --git a/community-fw/deploy/repository.py b/community-fw/deploy/repository.py
deleted file mode 100755
index 632c2e8..0000000
--- a/community-fw/deploy/repository.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import os
-from deploy import colorized_output as co
-
-class Repository:
-    def __init__(self, connection):
-        self.connection = connection
-
-    def uploadFolder(self, folder):
-        tar_folder = "{0}.tar".format(folder.split('/')[-1])
-        os.system("tar -cf {0} {1}/".format(tar_folder, folder))
-        self.connection.put("{0}".format(tar_folder))
-        self.connection.run("tar -xf {0}".format(tar_folder))
-        self.connection.run("rm {0}".format(tar_folder))
-        os.system("rm {0}".format(tar_folder))
-
-    def uploadAppFolder(self, folder):
-        self.uploadFolder(folder)
-        co.print_info('UploadAppFolder ' + folder)
-        self.connection.run("mkdir -p apps", warn=True)
-        self.connection.run("rm -rf apps/{0}/".format(folder), warn=True)
-        self.connection.run("mv -f {0} apps/".format(folder))
-        self.connection.run("chmod +x apps/{0}/start.py".format(folder))
-        self.connection.run("python apps/{0}/refresh.py".format(folder))
-
-    def replace_ip(self, ip):
-        self.connection.run("cd txtapi/txtapi/openapi && sed -i 's/^host:.*$/host: \"{0}:8080\"/g' openapi.yaml".format(ip))
-
-    def replace_env(self, env_file):
-        self.connection.run("mv txtapi/txtapi/environment/{0} txtapi/txtapi/environment/environment.py".format(env_file))
\ No newline at end of file
diff --git a/community-fw/fabfile.py b/community-fw/fabfile.py
deleted file mode 100755
index 23551bf..0000000
--- a/community-fw/fabfile.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import os
-
-from deploy import colorized_output as co
-from deploy.deployment import Deployment
-
-from invoke import task
-
-def set_ctx(ctx, host):
-    deployment = Deployment(host)
-    ctx.update({'deploy': deployment})
-
-@task(help={'host': "IP of the txt-controller"})
-def copy_ssh_key(ctx, host):
-    """
-    copy the own ssh-key to the txt-controller
-    """
-    set_ctx(ctx, host)
-    ctx.deploy.copy_ssh_key()
-
-@task(help={'host': "IP of the txt-controller"})
-def mount(ctx, host):
-    """
-    mounts the needed directories on the txt-controller
-    """
-    set_ctx(ctx, host)
-    ctx.deploy.check_mounts()
-
-@task(help={'host': "IP of the txt-controller"})
-def install_python(ctx, host):
-    """
-    installs pip and dependencies on the txt-controller
-    """
-    set_ctx(ctx, host)
-    ctx.deploy.check_pip_and_libraries()
-
-@task(help={'host': "IP of the txt-controller"})
-def deploy_flask_app(ctx, host):
-    """
-    deploys the flask-app on the txt-controller
-    """
-    set_ctx(ctx, host)
-    ctx.deploy.deploy_flask_app()
-
-@task(help={'host': "IP of the txt-controller"})
-def deploy(ctx, host):
-    """
-    Setup and deploy flask-server on txt-controller
-    """
-    set_ctx(ctx, host)
-    co.print_info("Step 1/4 >> Copying SSH-Key to TXT")
-    copy_ssh_key(ctx, host)
-    co.print_info("Step 2/4 >> Creating mounts")
-    mount(ctx, host)
-    co.print_info("Step 3/4 >> Installing pip and dependencies")
-    install_python(ctx, host)
-    co.print_success('Deployment successfully finished')
-    co.print_success("You can start the swagger server by running the 'TXT-API' application")
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 6073fd7..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-version: '3'
-services:
-  api:
-    build:
-      context: .
-      args:
-        # Needs to be url encoded
-        REPO_USER: 'repo-download'
-        REPO_PASSWORD: 'SieheEnpass'
-    ports:
-      - 80:80
-      - 443:443
-      - 8080:8080
diff --git a/entrypoint.sh b/entrypoint.sh
deleted file mode 100755
index c007968..0000000
--- a/entrypoint.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-
-apache2ctl -D BACKGROUND
-
-txtapi
-
diff --git a/openapi.json b/openapi.json
index 74f00c1..79084c1 100644
--- a/openapi.json
+++ b/openapi.json
@@ -385,7 +385,7 @@
                 "schema": {
                   "type": "array",
                   "items": {
-                    "$ref": "#/components/schemas/file"
+                    "$ref": "#/components/schemas/controller_file"
                   }
                 }
               }
@@ -491,7 +491,7 @@
             "content": {
               "application/json": {
                 "schema": {
-                  "$ref": "#/components/schemas/file"
+                  "$ref": "#/components/schemas/controller_file"
                 }
               }
             }
@@ -3176,7 +3176,8 @@
             "ApiKeyAuth": []
           }
         ],
-        "description": "send voicecommand",
+        "summary": "DEPRICATED - Add a text to the running program",
+        "description": "DEPRICATED - Adds a text to the running program on the controller.",
         "operationId": "send_command",
         "parameters": [
           {
@@ -3211,17 +3212,169 @@
         },
         "x-openapi-router-controller": "txtapi.controllers.remote_controller"
       }
+    },
+    "/remote/voice": {
+      "post": {
+        "tags": [
+          "remote"
+        ],
+        "security": [
+          {
+            "ApiKeyAuth": []
+          }
+        ],
+        "summary": "Add a text event to the running program",
+        "description": "Adds a text event to the running program on the controller.",
+        "operationId": "add_voice_event",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/voice_event"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "400": {
+            "$ref": "#/components/responses/BadRequest"
+          },
+          "404": {
+            "$ref": "#/components/responses/NotFound"
+          },
+          "412": {
+            "$ref": "#/components/responses/PreConditionFailed"
+          },
+          "500": {
+            "$ref": "#/components/responses/InternalServerError"
+          },
+          "default": {
+            "$ref": "#/components/responses/defaultError"
+          }
+        },
+        "x-openapi-router-controller": "txtapi.controllers.remote_controller"
+      }
+    },
+    "/remote": {
+      "post": {
+        "tags": [
+          "remote"
+        ],
+        "security": [
+          {
+            "ApiKeyAuth": []
+          }
+        ],
+        "summary": "Add a event to the running program",
+        "description": "Adds a event to the running program on the controller.",
+        "operationId": "add_remote_event",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/remote_event"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "400": {
+            "$ref": "#/components/responses/BadRequest"
+          },
+          "404": {
+            "$ref": "#/components/responses/NotFound"
+          },
+          "412": {
+            "$ref": "#/components/responses/PreConditionFailed"
+          },
+          "500": {
+            "$ref": "#/components/responses/InternalServerError"
+          },
+          "default": {
+            "$ref": "#/components/responses/defaultError"
+          }
+        },
+        "x-openapi-router-controller": "txtapi.controllers.remote_controller"
+      }
+    },
+    "/remote/stream": {
+      "get": {
+        "tags": [
+          "remote"
+        ],
+        "security": [
+          {
+            "ApiKeyQueryAuth": []
+          }
+        ],
+        "summary": "Event stream for remote control",
+        "description": "Message stream for events from running programs on the controller.",
+        "operationId": "remote_event_stream",
+        "parameters": [
+          {
+            "in": "query",
+            "name": "X-API-KEY",
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "text/event-stream": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            }
+          },
+          "400": {
+            "$ref": "#/components/responses/BadRequest"
+          },
+          "404": {
+            "$ref": "#/components/responses/NotFound"
+          },
+          "500": {
+            "$ref": "#/components/responses/InternalServerError"
+          },
+          "default": {
+            "$ref": "#/components/responses/defaultError"
+          }
+        },
+        "x-openapi-router-controller": "txtapi.controllers.remote_controller"
+      }
     }
   },
   "servers": [
     {
-      "url": "http://localhost/api/v1"
+      "url": "http://localhost/api/v1",
+      "description": "HTTP"
+    },
+    {
+      "url": "http://txt40.local/api/v1",
+      "description": "Local"
+    },
+    {
+      "url": "http://192.168.7.2/api/v1",
+      "description": "USB"
     },
     {
-      "url": "https://localhost/api/v1"
+      "url": "http://192.168.8.2/api/v1",
+      "description": "WLAN-AP"
     },
     {
-      "url": "http://192.168.7.2/api/v1"
+      "url": "http://192.168.9.2/api/v1",
+      "description": "Bluetooth"
     }
   ],
   "components": {
@@ -3319,7 +3472,7 @@
         },
         "x-body-name": "workspace"
       },
-      "file": {
+      "controller_file": {
         "type": "object",
         "properties": {
           "name": {
@@ -3327,9 +3480,12 @@
           },
           "path": {
             "type": "string"
+          },
+          "file_content":  {
+            "type": "string"
           }
         },
-        "x-body-name": "file"
+        "x-body-name": "controller_file"
       },
       "inline_response_default": {
         "type": "object",
@@ -3825,6 +3981,42 @@
           }
         },
         "x-body-name": "controller"
+      },
+      "voice_event": {
+        "type": "object",
+        "properties": {
+          "text": {
+            "type": "string"
+          }
+        },
+        "x-body-name": "voice_event"
+      },
+      "remote_event": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "string"
+          },
+          "attributes": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/attribute"
+            }
+          }
+        },
+        "x-body-name": "remote_event"
+      },
+      "attribute": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string"
+          },
+          "value": {
+            "type": "string"
+          }
+        },
+        "x-body-name": "attribute"
       }
     }
   }
diff --git a/start-dev.sh b/start-dev.sh
deleted file mode 100755
index 5456d10..0000000
--- a/start-dev.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-# stop instances
-docker-compose down
-
-# rebuild images and containers
-docker-compose up --build -d
\ No newline at end of file
diff --git a/txt2/api-app/icon.png b/txt2/api-app/icon.png
deleted file mode 100755
index 3b3a38a4bfb2f011b6345ee2963517cc6e8f1e3b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3812
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
aZT<&I5A)s^{jOC20000<MNUMnLSTX@*cgTY

diff --git a/txt2/api-app/manifest b/txt2/api-app/manifest
deleted file mode 100755
index 3b1e3e1..0000000
--- a/txt2/api-app/manifest
+++ /dev/null
@@ -1,11 +0,0 @@
-[app]
-name: TXT-API
-category: IDE
-author: Beemo
-icon: icon.png
-desc: TXT-API for fischertechnik
-exec: start.py
-managed: yes
-uuid: 191fe5a6-313b-4083-af65-d1ad7fd6d281
-version: 0.1
-firmware: 0.9
\ No newline at end of file
diff --git a/txt2/api-app/refresh.py b/txt2/api-app/refresh.py
deleted file mode 100755
index 787e9d3..0000000
--- a/txt2/api-app/refresh.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import socket
-
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-try:
-    print("Requesting rescan ...")
-    # Connect to server and send data
-    sock.connect(("localhost", 9000))
-    sock.sendall(bytes("rescan\n", "UTF-8"))
-except socket.error as msg:
-    print("Unable to connect to Launcher: ", msg)
\ No newline at end of file
diff --git a/txt2/api-app/start.py b/txt2/api-app/start.py
deleted file mode 100755
index a41778e..0000000
--- a/txt2/api-app/start.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#! /usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import glob
-import os
-import signal
-import sys
-import time
-
-from TouchStyle import *
-
-class FtcGuiApplication(TouchApplication):
-    def __init__(self, args):
-        TouchApplication.__init__(self, args)
-
-        self.process = "txtapi"
-
-        # Creates an empty MainWindow
-        w = TouchWindow("TXT-API")
-
-        # Create a label
-        vbox = QVBoxLayout()
-
-        status = QLabel()
-        status.setText('Server status:')
-        status.setAlignment(Qt.AlignCenter)
-        vbox.addWidget(status)
-
-        self.info = QLabel()
-        self.info.setAlignment(Qt.AlignCenter)
-        vbox.addWidget(self.info)
-
-        self.btnStart = QPushButton('Start')
-        self.btnStart.clicked.connect(self.startBackgroundServer)
-        vbox.addWidget(self.btnStart)
-
-        self.btnStop = QPushButton('Stop')
-        self.btnStop.clicked.connect(self.stopBackgroundServer)
-        vbox.addWidget(self.btnStop)
-
-        w.centralWidget.setLayout(vbox)
-
-        self.checkServerStatus()
-
-        w.show()
-        self.exec_()
-
-    def initBackgroundServer(self):
-        self.info.setText('booting')
-        self.info.setStyleSheet('color: orange; background-color: white')
-        self.btnStart.setDisabled(True)
-        self.processEvents()
-
-    def startBackgroundServer(self):
-        self.initBackgroundServer()
-        os.system("cd /home/ftc/txtapi && {0} &".format(self.process))
-        time.sleep(25)
-        self.checkServerStatus()
-
-    def stopBackgroundServer(self):
-        os.system("killall -9 {0}".format(self.process))
-        self.checkServerStatus()
-
-    def checkServerStatus(self):
-        processes = os.popen("ps -Af").read()
-        if self.process in processes:
-            self.info.setText('running')
-            self.info.setStyleSheet('color: green; background-color: white')
-            self.btnStart.setDisabled(True)
-            self.btnStop.setDisabled(False)
-        else:
-            self.info.setText('not running')
-            self.info.setStyleSheet('color: red; background-color: white')
-            self.btnStart.setDisabled(False)
-            self.btnStop.setDisabled(True)
-
-if __name__ == "__main__":
-    FtcGuiApplication(sys.argv)
diff --git a/txt2/deploy/colorized_output.py b/txt2/deploy/colorized_output.py
deleted file mode 100755
index cb1305c..0000000
--- a/txt2/deploy/colorized_output.py
+++ /dev/null
@@ -1,27 +0,0 @@
-class Color:
-    HEADER = '\033[95m'
-    OKBLUE = '\033[94m'
-    OKGREEN = '\033[92m'
-    WARNING = '\033[93m'
-    FAIL = '\033[91m'
-    ENDC = '\033[0m'
-    BOLD = '\033[1m'
-    UNDERLINE = '\033[4m'
-
-def print_colored(string, color):
-    print(color + string + Color.ENDC)
-
-def print_bold(string):
-    print_colored(string, Color.BOLD)
-
-def print_warning(string):
-    print_colored(string, Color.WARNING)
-
-def print_success(string):
-    print_colored(string, Color.OKGREEN)
-
-def print_info(string):
-    print_colored(string, Color.OKBLUE)
-
-def print_error(string):
-    print_colored(string, Color.FAIL)
\ No newline at end of file
diff --git a/txt2/deploy/deployment.py b/txt2/deploy/deployment.py
deleted file mode 100644
index ca74f18..0000000
--- a/txt2/deploy/deployment.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import os
-
-from deploy import colorized_output as co
-from deploy.repository import Repository
-
-from fabric import Connection
-
-USER = 'root'
-
-class Deployment:
-    def __init__(self, host):
-        self.host = host
-        self.con = Connection(host = host, user = USER)
-
-    def copy_ssh_key(self):
-        co.print_warning('Please authorize ssh-login on txt to copy ssh-key to authorized keys')
-        os.system("ssh {0}@{1} 'mkdir -p .ssh' && scp $HOME/.ssh/id_rsa.pub {0}@{1}:.ssh/authorized_keys".format(USER, self.host))
-
-    def deploy_flask_app(self):
-        repo = Repository(self.con)
-        repo.uploadFolder('../txtapi')
-        repo.uploadAppFolder('api-app')
-        repo.replace_ip(self.host)
-        repo.replace_env('environment_txt2.py')
diff --git a/txt2/deploy/repository.py b/txt2/deploy/repository.py
deleted file mode 100755
index 06500aa..0000000
--- a/txt2/deploy/repository.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-from deploy import colorized_output as co
-
-class Repository:
-    def __init__(self, connection):
-        self.connection = connection
-
-    def uploadFolder(self, folder):
-        tar_folder = "{0}.tar".format(folder.split('/')[-1])
-        os.system("tar -cf {0} {1}/".format(tar_folder, folder))
-        self.connection.put("{0}".format(tar_folder))
-        self.connection.run("tar -xf {0}".format(tar_folder))
-        self.connection.run("rm {0}".format(tar_folder))
-        os.system("rm {0}".format(tar_folder))
-
-    def uploadAppFolder(self, folder):
-        self.uploadFolder(folder)
-        co.print_info('UploadAppFolder ' + folder)
-        self.connection.run("mkdir -p apps", warn=True)
-        self.connection.run("rm -rf apps/{0}/".format(folder), warn=True)
-        self.connection.run("mv -f {0} apps/".format(folder))
-        self.connection.run("chmod +x apps/{0}/start.py".format(folder))
-
-    def replace_ip(self, ip):
-        self.connection.run("cd txtapi/txtapi/openapi && sed -i 's/^host:.*$/host: \"{0}:8080\"/g' openapi.yaml".format(ip))
-
-    def replace_env(self, env_file):
-        self.connection.run("mv txtapi/txtapi/environment/{0} txtapi/txtapi/environment/environment.py".format(env_file))
\ No newline at end of file
diff --git a/txt2/fabfile.py b/txt2/fabfile.py
deleted file mode 100755
index d79e452..0000000
--- a/txt2/fabfile.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import os
-
-from deploy import colorized_output as co
-from deploy.deployment import Deployment
-
-from invoke import task
-
-def set_ctx(ctx, host):
-    deployment = Deployment(host)
-    ctx.update({'deploy': deployment})
-
-@task(help={'host': "IP of the txt-controller"})
-def copy_ssh_key(ctx, host):
-    """
-    copy the own ssh-key to the txt-controller
-    """
-    set_ctx(ctx, host)
-    ctx.deploy.copy_ssh_key()
-
-@task(help={'host': "IP of the txt-controller"})
-def deploy_flask_app(ctx, host):
-    """
-    deploys the flask-app on the txt-controller
-    """
-    set_ctx(ctx, host)
-    ctx.deploy.deploy_flask_app()
-
-@task(help={'host': "IP of the txt-controller"})
-def deploy(ctx, host):
-    """
-    Setup and deploy flask-server on txt-controller
-    """
-    set_ctx(ctx, host)
-    co.print_info("Step 1/2 >> Copying SSH-Key to TXT")
-    copy_ssh_key(ctx, host)
-    co.print_info("Step 2/2 >> Copying flaskConnexion-folder to TXT")
-    deploy_flask_app(ctx, host)
-    co.print_success('Deployment successfully finished')
-    co.print_success("You can start the swagger server by running the 'TXT-API' application")
diff --git a/txtapi/.openapi-generator/FILES b/txtapi/.openapi-generator/FILES
index f97d545..19e394c 100644
--- a/txtapi/.openapi-generator/FILES
+++ b/txtapi/.openapi-generator/FILES
@@ -7,19 +7,20 @@ tox.ini
 txtapi/controllers/__init__.py
 txtapi/encoder.py
 txtapi/models/__init__.py
+txtapi/models/attribute.py
 txtapi/models/ball_detector.py
 txtapi/models/base_model_.py
 txtapi/models/breakpoint.py
 txtapi/models/camera_config.py
 txtapi/models/color_detector.py
 txtapi/models/controller.py
+txtapi/models/controller_file.py
 txtapi/models/counter.py
 txtapi/models/debugger_arguments.py
 txtapi/models/debugger_response.py
 txtapi/models/enabled.py
 txtapi/models/error_message.py
 txtapi/models/expression.py
-txtapi/models/file.py
 txtapi/models/image_recognition_config.py
 txtapi/models/inline_response_default.py
 txtapi/models/input.py
@@ -30,7 +31,9 @@ txtapi/models/motor_all_of.py
 txtapi/models/output.py
 txtapi/models/program_location.py
 txtapi/models/rectangle.py
+txtapi/models/remote_event.py
 txtapi/models/servomotor.py
+txtapi/models/voice_event.py
 txtapi/models/workspace.py
 txtapi/openapi/openapi.yaml
 txtapi/test/__init__.py
diff --git a/txtapi/requirements.txt b/txtapi/requirements.txt
index cf17cb0..da027c3 100644
--- a/txtapi/requirements.txt
+++ b/txtapi/requirements.txt
@@ -4,10 +4,7 @@ python_dateutil==2.6.0
 setuptools==41.4.0
 Flask-Cors==3.0.6
 rpdb==0.1.6
-ftrobopy==1.92
-bme680==1.0.5
-ft-controllerlib==1.0.42.0
-imutils==0.5.3
+ft-controllerlib==6.2.1
 Werkzeug==0.16.1
 waitress>=1.4.4
 psutil==5.7.3
diff --git a/txtapi/setup.py b/txtapi/setup.py
index 9b6e145..e0ca077 100644
--- a/txtapi/setup.py
+++ b/txtapi/setup.py
@@ -20,16 +20,11 @@ REQUIRES = [
     "Flask-Cors==3.0.6",
     "rpdb==0.1.6",
     "websockets==7.0",
-    "ftrobopy==1.92",
-    "bme680==1.0.5",
-    "ft-controllerlib==6.0.8",
+    "ft-controllerlib==6.2.1",
     "jsonschema==3.2.0",
-    "imutils==0.5.3",
     "Werkzeug==0.16.1",
-    "smbus2>=0.2.1",
     "waitress>=1.4.4",
     "psutil==5.7.3",
-    "apds9960==0.2"
 ]
 
 setup(
diff --git a/txtapi/txtapi/cache.py b/txtapi/txtapi/cache.py
index 05ad112..07dfffe 100644
--- a/txtapi/txtapi/cache.py
+++ b/txtapi/txtapi/cache.py
@@ -21,7 +21,7 @@ class CachedSpecification(Specification):
                 cache = pickle.load(f)
                 if cache['md5_hash'] == md5_hash:
                     return cache['spec']
-        except OSError:
+        except:
             pass
 
         rv = cls._real_from_file(spec, arguments=arguments)
@@ -32,8 +32,8 @@ class CachedSpecification(Specification):
                     'spec': rv
                 }
                 pickle.dump(cache, f)
-        except OSError as e:
-            print('Could not store spec in cache: %s', e)
+        except:
+            print('Could not store spec in cache')
 
         return rv
 
diff --git a/txtapi/txtapi/controllers/remote_controller.py b/txtapi/txtapi/controllers/remote_controller.py
index 3d928bf..059a75b 100644
--- a/txtapi/txtapi/controllers/remote_controller.py
+++ b/txtapi/txtapi/controllers/remote_controller.py
@@ -1,20 +1,63 @@
 import connexion
 import six
-
+from flask import Response
 from txtapi.models.error_message import ErrorMessage  # noqa: E501
-from txtapi.models.inline_response_default import InlineResponseDefault  # noqa: E501
-from txtapi import util
+from txtapi.models.voice_event import VoiceEvent # noqa: E501
 from txtapi.handlers import remote_handler
+from txtapi import util
+
+
+def add_remote_event(remote_event):  # noqa: E501
+    """Add a event to the running program
+
+    Adds a event to the running program on the controller. # noqa: E501
+
+    :param remote_event: 
+    :type remote_event: dict | bytes
+
+    :rtype: None
+    """
+    if connexion.request.is_json:
+        remote_event = connexion.request.get_json()  # noqa: E501
+    return remote_handler.add_remote_event(remote_event)
+
+
+def add_voice_event(voice_event):  # noqa: E501
+    """Add a text event to the running program
+
+    Adds a text event to the running program on the controller. # noqa: E501
+
+    :param voice_event: 
+    :type voice_event: dict | bytes
+
+    :rtype: None
+    """
+    if connexion.request.is_json:
+        voice_event = VoiceEvent.from_dict(connexion.request.get_json())  # noqa: E501
+    return remote_handler.add_voice_event(voice_event.text)
+
+
+def remote_event_stream(x_api_key=None):  # noqa: E501
+    """Event stream for remote control
+
+    Message stream for events from running programs on the controller. # noqa: E501
+
+    :param x_api_key: 
+    :type x_api_key: str
+
+    :rtype: str
+    """
+    return Response(remote_handler.remote_event_stream(), mimetype='text/event-stream')
 
 
 def send_command(command):  # noqa: E501
-    """send_command
+    """DEPRICATED - Add a text to the running program
 
-    send voicecommand # noqa: E501
+    DEPRICATED - Adds a text to the running program on the controller. # noqa: E501
 
     :param command: The voicecommand to be executed
     :type command: str
 
     :rtype: None
     """
-    return remote_handler.send_command(command)
+    return remote_handler.add_voice_event(command)
diff --git a/txtapi/txtapi/controllers/util_controller.py b/txtapi/txtapi/controllers/util_controller.py
index 9347a9d..8b8d396 100644
--- a/txtapi/txtapi/controllers/util_controller.py
+++ b/txtapi/txtapi/controllers/util_controller.py
@@ -10,7 +10,7 @@ def ping():  # noqa: E501
 
     :rtype: str
     """
-    return jsonify(util_handler.ping())
+    return util_handler.ping()
 
 def stop():  # noqa: E501
     """stop
diff --git a/txtapi/txtapi/handlers/remote_handler.py b/txtapi/txtapi/handlers/remote_handler.py
index 23120c6..8ca7caf 100644
--- a/txtapi/txtapi/handlers/remote_handler.py
+++ b/txtapi/txtapi/handlers/remote_handler.py
@@ -1,16 +1,66 @@
+import json
+import time
 import socket
+
 from http import HTTPStatus
 from fischertechnik.control.VoiceControl import VoiceControl
+from fischertechnik.control.RemoteControl import RemoteControl
+
+
+def add_voice_event(event):
 
-def send_command(command):
-    status_code = HTTPStatus.OK
     try:
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         s.connect((VoiceControl.HOST, VoiceControl.PORT))
-        s.send(command.encode('utf-8'))
+        s.send(event.encode('utf-8'))
+    except Exception as e:
+        pass
+    finally:
+        s.close()
+
+    return None, HTTPStatus.OK
+
+
+def add_remote_event(event):
+
+    try:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.connect((RemoteControl.HOST, RemoteControl.IN_PORT))
+        message = json.dumps(event)
+        s.send(message.encode('utf-8'))
     except Exception as e:
-        status_code = HTTPStatus.NOT_FOUND
+        pass
     finally:
         s.close()
 
-    return None, status_code  
+    return None, HTTPStatus.OK
+
+
+def remote_event_stream():
+
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+    while True:
+        try: 
+            s.connect((RemoteControl.HOST, RemoteControl.OUT_PORT))
+            break
+        except Exception as e:
+            time.sleep(1)
+
+    running = True
+    while running:
+
+        try:    
+
+            data = s.recv(RemoteControl.BUFFERSIZE) 
+
+            if (data != b''):
+
+                yield 'data: %s\n\n' % str(data.decode())     
+
+        except Exception as e:
+            running = False
+
+    print("command stream closed")
+
+    return None, HTTPStatus.OK
diff --git a/txtapi/txtapi/handlers/util_handler.py b/txtapi/txtapi/handlers/util_handler.py
index 0c54a49..9f7d192 100644
--- a/txtapi/txtapi/handlers/util_handler.py
+++ b/txtapi/txtapi/handlers/util_handler.py
@@ -1,3 +1,4 @@
+import subprocess
 from http import HTTPStatus
 
 from txtapi.handlers import application_handler
@@ -8,9 +9,21 @@ from txtapi.utility.heartbeat.heartbeat import Heartbeat
 
 def ping():
     Heartbeat.get_instance().keep_alive()
-    return None, HTTPStatus.OK
+    role = get_controller_role()
+    if role == 'Master' or role == 'Single':
+        return None, HTTPStatus.OK
+    return 'Controller is in extension mode', HTTPStatus.CONFLICT
 
 def stop():
     application_handler.stop_applications()
     controller_handler.stop_controller()
     debugger_handler.stop_debuggers()
+
+def get_controller_role():
+    output = str(subprocess.Popen(['ftconfig', 'dump'], stdout=subprocess.PIPE,
+                                  stderr=subprocess.STDOUT).communicate()[0]).replace('\\n', '')
+    start = output.find('name ') + 5
+    end = output.find('sn', start)
+    return output[start:end]
+
+
diff --git a/txtapi/txtapi/handlers/workspace_handler.py b/txtapi/txtapi/handlers/workspace_handler.py
index 79c008c..f32a024 100644
--- a/txtapi/txtapi/handlers/workspace_handler.py
+++ b/txtapi/txtapi/handlers/workspace_handler.py
@@ -7,7 +7,7 @@ from http import HTTPStatus
 from flask import jsonify
 from txtapi.environment.environment import MANIFEST_FILENAME, PID_FILENAME, PROJECT_FILENAME
 from txtapi.environment.environment import WORKSPACE_BASEPATH, DEBUG
-from txtapi.models.file import File
+from txtapi.models.controller_file import ControllerFile
 from txtapi.models.workspace import Workspace
 from txtapi.txt.txt_state import TxtState
 
@@ -23,6 +23,8 @@ def create_workspace(workspace_name):
             if DEBUG:
                 print("Creating directory %s failed due to %s" % (path, str(exception)))
             return exception, HTTPStatus.INTERNAL_SERVER_ERROR
+    else:
+        delete_workspace(workspace_name)
     return build_workspace_response(workspace_name), HTTPStatus.CREATED
 
 
@@ -78,7 +80,10 @@ def delete_workspace(workspace_name):
 def get_file_of_workspace(workspace_name, filename):
     path = os.path.join(WORKSPACE_BASEPATH, workspace_name, filename)
     if os.path.exists(path):
-        return jsonify(File(filename, path)), HTTPStatus.OK
+        fd = open(path, 'r')
+        content = fd.read()
+        fd.close()
+        return jsonify(ControllerFile(filename, path, content)), HTTPStatus.OK
     else:
         return None, HTTPStatus.NOT_FOUND
 
@@ -89,10 +94,33 @@ def get_files_of_workspace(workspace_name):
         return None, HTTPStatus.NOT_FOUND
     files = []
     for file in os.listdir(path):
-        files.append(File(file, os.path.join(path, file)))
+        file_path = os.path.join(path, file)
+        if os.path.isdir(file_path):
+            if os.path.basename(file_path) != '__pycache__':
+                files.extend(_get_files_of_dir(file_path))
+        else:
+            fd = open(os.path.join(path, file), 'r')
+            content = fd.read()
+            fd.close()
+            files.append(ControllerFile(file, os.path.join(path, file), content))
     return files, HTTPStatus.OK
 
 
+def _get_files_of_dir(path):
+    files = []
+    for file in os.listdir(path):
+        file_path = os.path.join(path, file)
+        if os.path.isdir(file_path):
+            if os.path.basename(file_path) != '__pycache__':
+                files.extend(_get_files_of_dir(file_path))
+        else:
+            fd = open(os.path.join(path, file), 'r')
+            content = fd.read()
+            fd.close()
+            files.append(ControllerFile(file, os.path.join(path, file), content))
+    return files
+
+
 def upload_files_to_workspace(workspace_name, files):
     if ftlock.try_lock(workspace_name + '.py') == 0:
         ftlock.notify(TxtState.TRANSFERRING.value)
@@ -105,13 +133,13 @@ def upload_files_to_workspace(workspace_name, files):
         fileName = splitPath[-1]
         path = '/'.join(splitPath[:-1])
         file_path = os.path.join(WORKSPACE_BASEPATH, workspace_name, path)
-        
+
         if not os.path.exists(file_path):
             try:
                 os.makedirs(file_path)
             except Exception as exception:
                 return exception, HTTPStatus.INTERNAL_SERVER_ERROR
-        
+
         file_path = os.path.join(file_path, fileName)
         file.save(file_path)
     if ftlock.try_lock(workspace_name + '.py') == 0:
@@ -146,6 +174,7 @@ def get_pid(workspace_name: str) -> str:
         raise Exception("unable to find process with workspace_name " + workspace_name)
     return pid
 
+
 def build_workspace_response(name):
     uuid = None
     try:
@@ -153,7 +182,7 @@ def build_workspace_response(name):
             project_dict = json.load(project_file)
             if 'uuid' in project_dict:
                 uuid = project_dict['uuid']
-    except: 
+    except:
         pass
     path = os.path.join(WORKSPACE_BASEPATH, name)
     return Workspace(name, path, uuid)
diff --git a/txtapi/txtapi/models/__init__.py b/txtapi/txtapi/models/__init__.py
index 9ef1028..c4050b1 100644
--- a/txtapi/txtapi/models/__init__.py
+++ b/txtapi/txtapi/models/__init__.py
@@ -3,18 +3,19 @@
 # flake8: noqa
 from __future__ import absolute_import
 # import models into model package
+from txtapi.models.attribute import Attribute
 from txtapi.models.ball_detector import BallDetector
 from txtapi.models.breakpoint import Breakpoint
 from txtapi.models.camera_config import CameraConfig
 from txtapi.models.color_detector import ColorDetector
 from txtapi.models.controller import Controller
+from txtapi.models.controller_file import ControllerFile
 from txtapi.models.counter import Counter
 from txtapi.models.debugger_arguments import DebuggerArguments
 from txtapi.models.debugger_response import DebuggerResponse
 from txtapi.models.enabled import Enabled
 from txtapi.models.error_message import ErrorMessage
 from txtapi.models.expression import Expression
-from txtapi.models.file import File
 from txtapi.models.image_recognition_config import ImageRecognitionConfig
 from txtapi.models.inline_response_default import InlineResponseDefault
 from txtapi.models.input import Input
@@ -25,5 +26,7 @@ from txtapi.models.motor_all_of import MotorAllOf
 from txtapi.models.output import Output
 from txtapi.models.program_location import ProgramLocation
 from txtapi.models.rectangle import Rectangle
+from txtapi.models.remote_event import RemoteEvent
 from txtapi.models.servomotor import Servomotor
+from txtapi.models.voice_event import VoiceEvent
 from txtapi.models.workspace import Workspace
diff --git a/txtapi/txtapi/models/attribute.py b/txtapi/txtapi/models/attribute.py
new file mode 100644
index 0000000..dd1862d
--- /dev/null
+++ b/txtapi/txtapi/models/attribute.py
@@ -0,0 +1,90 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from txtapi.models.base_model_ import Model
+from txtapi import util
+
+
+class Attribute(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, name=None, value=None):  # noqa: E501
+        """Attribute - a model defined in OpenAPI
+
+        :param name: The name of this Attribute.  # noqa: E501
+        :type name: str
+        :param value: The value of this Attribute.  # noqa: E501
+        :type value: str
+        """
+        self.openapi_types = {
+            'name': str,
+            'value': str
+        }
+
+        self.attribute_map = {
+            'name': 'name',
+            'value': 'value'
+        }
+
+        self._name = name
+        self._value = value
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'Attribute':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The attribute of this Attribute.  # noqa: E501
+        :rtype: Attribute
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def name(self):
+        """Gets the name of this Attribute.
+
+
+        :return: The name of this Attribute.
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this Attribute.
+
+
+        :param name: The name of this Attribute.
+        :type name: str
+        """
+
+        self._name = name
+
+    @property
+    def value(self):
+        """Gets the value of this Attribute.
+
+
+        :return: The value of this Attribute.
+        :rtype: str
+        """
+        return self._value
+
+    @value.setter
+    def value(self, value):
+        """Sets the value of this Attribute.
+
+
+        :param value: The value of this Attribute.
+        :type value: str
+        """
+
+        self._value = value
diff --git a/txtapi/txtapi/models/controller_file.py b/txtapi/txtapi/models/controller_file.py
new file mode 100644
index 0000000..1dd8c6e
--- /dev/null
+++ b/txtapi/txtapi/models/controller_file.py
@@ -0,0 +1,116 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from txtapi.models.base_model_ import Model
+from txtapi import util
+
+
+class ControllerFile(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, name=None, path=None, file_content=None):  # noqa: E501
+        """ControllerFile - a model defined in OpenAPI
+
+        :param name: The name of this ControllerFile.  # noqa: E501
+        :type name: str
+        :param path: The path of this ControllerFile.  # noqa: E501
+        :type path: str
+        :param file_content: The file_content of this ControllerFile.  # noqa: E501
+        :type file_content: str
+        """
+        self.openapi_types = {
+            'name': str,
+            'path': str,
+            'file_content': str
+        }
+
+        self.attribute_map = {
+            'name': 'name',
+            'path': 'path',
+            'file_content': 'file_content'
+        }
+
+        self._name = name
+        self._path = path
+        self._file_content = file_content
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'ControllerFile':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The controller_file of this ControllerFile.  # noqa: E501
+        :rtype: ControllerFile
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def name(self):
+        """Gets the name of this ControllerFile.
+
+
+        :return: The name of this ControllerFile.
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this ControllerFile.
+
+
+        :param name: The name of this ControllerFile.
+        :type name: str
+        """
+
+        self._name = name
+
+    @property
+    def path(self):
+        """Gets the path of this ControllerFile.
+
+
+        :return: The path of this ControllerFile.
+        :rtype: str
+        """
+        return self._path
+
+    @path.setter
+    def path(self, path):
+        """Sets the path of this ControllerFile.
+
+
+        :param path: The path of this ControllerFile.
+        :type path: str
+        """
+
+        self._path = path
+
+    @property
+    def file_content(self):
+        """Gets the file_content of this ControllerFile.
+
+
+        :return: The file_content of this ControllerFile.
+        :rtype: str
+        """
+        return self._file_content
+
+    @file_content.setter
+    def file_content(self, file_content):
+        """Sets the file_content of this ControllerFile.
+
+
+        :param file_content: The file_content of this ControllerFile.
+        :type file_content: str
+        """
+
+        self._file_content = file_content
diff --git a/txtapi/txtapi/models/file.py b/txtapi/txtapi/models/file.py
deleted file mode 100644
index 7025cee..0000000
--- a/txtapi/txtapi/models/file.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# coding: utf-8
-
-from __future__ import absolute_import
-from datetime import date, datetime  # noqa: F401
-
-from typing import List, Dict  # noqa: F401
-
-from txtapi.models.base_model_ import Model
-from txtapi import util
-
-
-class File(Model):
-    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
-
-    Do not edit the class manually.
-    """
-
-    def __init__(self, name=None, path=None):  # noqa: E501
-        """File - a model defined in OpenAPI
-
-        :param name: The name of this File.  # noqa: E501
-        :type name: str
-        :param path: The path of this File.  # noqa: E501
-        :type path: str
-        """
-        self.openapi_types = {
-            'name': str,
-            'path': str
-        }
-
-        self.attribute_map = {
-            'name': 'name',
-            'path': 'path'
-        }
-
-        self._name = name
-        self._path = path
-
-    @classmethod
-    def from_dict(cls, dikt) -> 'File':
-        """Returns the dict as a model
-
-        :param dikt: A dict.
-        :type: dict
-        :return: The file of this File.  # noqa: E501
-        :rtype: File
-        """
-        return util.deserialize_model(dikt, cls)
-
-    @property
-    def name(self):
-        """Gets the name of this File.
-
-
-        :return: The name of this File.
-        :rtype: str
-        """
-        return self._name
-
-    @name.setter
-    def name(self, name):
-        """Sets the name of this File.
-
-
-        :param name: The name of this File.
-        :type name: str
-        """
-
-        self._name = name
-
-    @property
-    def path(self):
-        """Gets the path of this File.
-
-
-        :return: The path of this File.
-        :rtype: str
-        """
-        return self._path
-
-    @path.setter
-    def path(self, path):
-        """Sets the path of this File.
-
-
-        :param path: The path of this File.
-        :type path: str
-        """
-
-        self._path = path
diff --git a/txtapi/txtapi/models/remote_event.py b/txtapi/txtapi/models/remote_event.py
new file mode 100644
index 0000000..9d43bd9
--- /dev/null
+++ b/txtapi/txtapi/models/remote_event.py
@@ -0,0 +1,92 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from txtapi.models.base_model_ import Model
+from txtapi.models.attribute import Attribute
+from txtapi import util
+
+from txtapi.models.attribute import Attribute  # noqa: E501
+
+class RemoteEvent(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, id=None, attributes=None):  # noqa: E501
+        """RemoteEvent - a model defined in OpenAPI
+
+        :param id: The id of this RemoteEvent.  # noqa: E501
+        :type id: str
+        :param attributes: The attributes of this RemoteEvent.  # noqa: E501
+        :type attributes: List[Attribute]
+        """
+        self.openapi_types = {
+            'id': str,
+            'attributes': List[Attribute]
+        }
+
+        self.attribute_map = {
+            'id': 'id',
+            'attributes': 'attributes'
+        }
+
+        self._id = id
+        self._attributes = attributes
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'RemoteEvent':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The remote_event of this RemoteEvent.  # noqa: E501
+        :rtype: RemoteEvent
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def id(self):
+        """Gets the id of this RemoteEvent.
+
+
+        :return: The id of this RemoteEvent.
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this RemoteEvent.
+
+
+        :param id: The id of this RemoteEvent.
+        :type id: str
+        """
+
+        self._id = id
+
+    @property
+    def attributes(self):
+        """Gets the attributes of this RemoteEvent.
+
+
+        :return: The attributes of this RemoteEvent.
+        :rtype: List[Attribute]
+        """
+        return self._attributes
+
+    @attributes.setter
+    def attributes(self, attributes):
+        """Sets the attributes of this RemoteEvent.
+
+
+        :param attributes: The attributes of this RemoteEvent.
+        :type attributes: List[Attribute]
+        """
+
+        self._attributes = attributes
diff --git a/txtapi/txtapi/models/voice_event.py b/txtapi/txtapi/models/voice_event.py
new file mode 100644
index 0000000..7526670
--- /dev/null
+++ b/txtapi/txtapi/models/voice_event.py
@@ -0,0 +1,64 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from txtapi.models.base_model_ import Model
+from txtapi import util
+
+
+class VoiceEvent(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, text=None):  # noqa: E501
+        """VoiceEvent - a model defined in OpenAPI
+
+        :param text: The text of this VoiceEvent.  # noqa: E501
+        :type text: str
+        """
+        self.openapi_types = {
+            'text': str
+        }
+
+        self.attribute_map = {
+            'text': 'text'
+        }
+
+        self._text = text
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'VoiceEvent':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The voice_event of this VoiceEvent.  # noqa: E501
+        :rtype: VoiceEvent
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def text(self):
+        """Gets the text of this VoiceEvent.
+
+
+        :return: The text of this VoiceEvent.
+        :rtype: str
+        """
+        return self._text
+
+    @text.setter
+    def text(self, text):
+        """Sets the text of this VoiceEvent.
+
+
+        :param text: The text of this VoiceEvent.
+        :type text: str
+        """
+
+        self._text = text
diff --git a/txtapi/txtapi/openapi/openapi.yaml b/txtapi/txtapi/openapi/openapi.yaml
index 0a69f34..cf028fe 100644
--- a/txtapi/txtapi/openapi/openapi.yaml
+++ b/txtapi/txtapi/openapi/openapi.yaml
@@ -3,9 +3,16 @@ info:
   title: ft-controller-api
   version: 1.0.0
 servers:
-- url: http://localhost/api/v1
-- url: https://localhost/api/v1
-- url: http://192.168.7.2/api/v1
+- description: HTTP
+  url: http://localhost/api/v1
+- description: Local
+  url: http://txt40.local/api/v1
+- description: USB
+  url: http://192.168.7.2/api/v1
+- description: WLAN-AP
+  url: http://192.168.8.2/api/v1
+- description: Bluetooth
+  url: http://192.168.9.2/api/v1
 tags:
 - description: Everything for running applications
   name: application
@@ -2554,9 +2561,58 @@ paths:
       tags:
       - util
       x-openapi-router-controller: txtapi.controllers.util_controller
+  /remote:
+    post:
+      description: Adds a event to the running program on the controller.
+      operationId: add_remote_event
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/remote_event'
+        required: true
+      responses:
+        "200":
+          description: OK
+        "400":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Bad Request
+        "404":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Not Found
+        "412":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Pre Condition failed
+        "500":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Internal Server Error
+        default:
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/inline_response_default'
+          description: Unexpected Error!
+      security:
+      - ApiKeyAuth: []
+      summary: Add a event to the running program
+      tags:
+      - remote
+      x-openapi-router-controller: txtapi.controllers.remote_controller
   /remote/send-command/{command}:
     post:
-      description: send voicecommand
+      description: DEPRICATED - Adds a text to the running program on the controller.
       operationId: send_command
       parameters:
       - description: The voicecommand to be executed
@@ -2602,6 +2658,105 @@ paths:
           description: Unexpected Error!
       security:
       - ApiKeyAuth: []
+      summary: DEPRICATED - Add a text to the running program
+      tags:
+      - remote
+      x-openapi-router-controller: txtapi.controllers.remote_controller
+  /remote/stream:
+    get:
+      description: Message stream for events from running programs on the controller.
+      operationId: remote_event_stream
+      parameters:
+      - explode: true
+        in: query
+        name: X-API-KEY
+        required: false
+        schema:
+          type: string
+        style: form
+      responses:
+        "200":
+          content:
+            text/event-stream:
+              schema:
+                type: string
+          description: OK
+        "400":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Bad Request
+        "404":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Not Found
+        "500":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Internal Server Error
+        default:
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/inline_response_default'
+          description: Unexpected Error!
+      security:
+      - ApiKeyQueryAuth: []
+      summary: Event stream for remote control
+      tags:
+      - remote
+      x-openapi-router-controller: txtapi.controllers.remote_controller
+  /remote/voice:
+    post:
+      description: Adds a text event to the running program on the controller.
+      operationId: add_voice_event
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/voice_event'
+        required: true
+      responses:
+        "200":
+          description: OK
+        "400":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Bad Request
+        "404":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Not Found
+        "412":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Pre Condition failed
+        "500":
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/error_message'
+          description: Internal Server Error
+        default:
+          content:
+            '*/*':
+              schema:
+                $ref: '#/components/schemas/inline_response_default'
+          description: Unexpected Error!
+      security:
+      - ApiKeyAuth: []
+      summary: Add a text event to the running program
       tags:
       - remote
       x-openapi-router-controller: txtapi.controllers.remote_controller
@@ -2899,7 +3054,7 @@ paths:
             application/json:
               schema:
                 items:
-                  $ref: '#/components/schemas/file'
+                  $ref: '#/components/schemas/controller_file'
                 type: array
           description: OK
         "400":
@@ -3084,7 +3239,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/file'
+                $ref: '#/components/schemas/controller_file'
           description: OK
         "400":
           content:
@@ -3178,17 +3333,20 @@ components:
           type: string
       type: object
       x-body-name: workspace
-    file:
+    controller_file:
       example:
         path: path
+        file_content: file_content
         name: name
       properties:
         name:
           type: string
         path:
           type: string
+        file_content:
+          type: string
       type: object
-      x-body-name: file
+      x-body-name: controller_file
     inline_response_default:
       example:
         message: message
@@ -3596,6 +3754,30 @@ components:
           type: string
       type: object
       x-body-name: controller
+    voice_event:
+      properties:
+        text:
+          type: string
+      type: object
+      x-body-name: voice_event
+    remote_event:
+      properties:
+        id:
+          type: string
+        attributes:
+          items:
+            $ref: '#/components/schemas/attribute'
+          type: array
+      type: object
+      x-body-name: remote_event
+    attribute:
+      properties:
+        name:
+          type: string
+        value:
+          type: string
+      type: object
+      x-body-name: attribute
     motor_allOf:
       properties:
         direction:
-- 
GitLab