-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun.py
More file actions
executable file
·210 lines (167 loc) · 6.41 KB
/
run.py
File metadata and controls
executable file
·210 lines (167 loc) · 6.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#
# run.py
#
# This program takes a txt project name as parameter and tries
# to run it.
#
# Currently supported are:
# - Display (interpret display.qml and run display.py)
# - Controller
# - Switch inputs
# - photo resistor inputs
# - LED outputs
# - Execution of main python code
# TODO:
# - use semaphore instead of queue
#
#
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import QApplication, QLabel
from PyQt5.QtCore import QThread, QObject, QTimer, pyqtSignal, pyqtSlot
import sys, os, time, queue, select
import json, traceback
# fischertechnik seems to have placed their own custom
# widgets in one of the standard modules which they import
# Dirty hack: We add a local QtQuick/Window.2.0 to place
# our objects there to not mess with the qt installation
import ftgui
from fischertechnik.control.VoiceControl import VoiceControl
class AppRunnerThread(QThread):
finished = pyqtSignal()
def __init__(self, app):
super(AppRunnerThread,self).__init__()
print("Run:", app.split("/")[-1]);
# load app code
with open(app, "r") as f:
self.code = f.read()
def run(self):
# run if there's code
if self.code:
try:
exec(self.code, {})
except Exception as e:
print(traceback.format_exc())
self.finished.emit()
class RunApplication(QApplication):
doExecScript = pyqtSignal(str, bool)
# handle user triggered gui events (e.g. "Wenn Schieberegler bewegt)"
def handler(self, eid, parm):
# parm has the form "item:state" if present
event = { }
# expected event parameter: "value", "checked", "text"
if parm:
name,val = parm.split(":")
# make "checked" a bool ...
if name == "checked":
val = val == "true"
# ... and "value" an int
if name == "value":
val = int(val)
event[name] = val
if eid in self.handlers:
self.handlers[eid](event)
else:
print("No such handler:", eid)
def install_handler(self, id, event, handler):
# set object name, so it's addressable from the python side
self.doExecScript.emit(id+".objectName = \""+id+"\"", False);
# it may take some time for this to show effect
obj = None
while not obj: obj = self.engine.rootObjects()[0].findChild(QObject, id)
self.handlers[id+":"+event] = handler
obj.onEvent.connect(self.handler)
def execScript(self, code):
self.doExecScript.emit(code, False)
# this is also new
def set_attr(self, item, value):
# RP-C converts everything to a string. How do we know if it's
# actually a string?
if value == "true" or value == "false":
self.doExecScript.emit(item + "= "+str(value)+"", False);
else:
self.doExecScript.emit(item + "= \""+value+"\"", False);
def get_attr(self, item):
# simply invoke attribute name. This will return its value
self.doExecScript.emit(item, True);
# wait for result
while self.queue.empty(): pass
# return result
return self.queue.get()
def openProject(self):
# try to open project file
with open(self.path + ".project.json") as f:
self.project = json.load(f)
print("Name:", self.project["name"])
print("Mode:", self.project["mode"])
print("UUid:", self.project["uuid"])
# if display code is present, then run it
self.runDisplay()
self.runApp()
def runApp(self):
# make sure app code finds its local imports
sys.path.append(self.path)
# run background thread for main program itself
self.thread = AppRunnerThread(self.path + self.project["name"]+".py")
self.thread.start()
def execResult(self, str):
self.queue.put(str)
def runDisplay(self):
qml = os.path.join(self.path, "lib/display.qml")
if not os.path.isfile(qml):
print("No display.qml file")
return
self.engine = QQmlApplicationEngine()
self.engine.addImportPath(os.path.dirname(os.path.realpath(__file__)))
self.engine.load(qml)
win = self.engine.rootObjects()[0]
if len(self.path.split("/")) >= 2:
win.setTitle(self.path.split("/")[-2])
self.doExecScript.connect(win.execScript)
win.execResultStr.connect(self.execResult)
win.execResultBool.connect(self.execResult)
# check if something was loaded
return bool(self.engine.rootObjects())
def on_timer(self):
if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
try:
line = sys.stdin.readline()
except:
line = None
data = None
if line:
# whatever we are reading here is supposed to be json encoded
# and since we are reading a full line it
# xyz
try:
data = json.loads(line)
except:
# ignore any malformed json input
pass
if data:
# currently only remote commands are supported
if "remote" in data:
for listener in VoiceControl.listeners:
listener(data["remote"])
def __init__(self, args):
QApplication.__init__(self, args)
self.handlers = { }
# create a timer to periodically check stdin for input
self.timer = QTimer()
self.timer.timeout.connect(self.on_timer)
self.timer.start(10)
# queue to pass attribute results from the gui into the app
self.queue = queue.Queue()
# register self with gui connector
ftgui.fttxt2_gui_connector.app = self
# get project name
self.path = os.path.dirname(os.path.realpath(__file__))+"/workspaces/"+args[1]+"/"
self.openProject()
self.exec_()
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Please provide a project name");
sys.exit(-1)
RunApplication(sys.argv)
sys.exit(0)